WPF unable to bind rectangle background image - c#

I want to create rectangle with dynamic background image but it somehow wont work. I am not sure if its problem with some types or bindings as its my first binding in life.
Crete new instance of my custom control
BitmapImage image = new(new Uri(#filePath));
ImageBrush imageBrush = new ImageBrush(image);
InputBlock inputBlock = new()
{
Height= image.Height + 20,
Width= image.Width,
ImageBrush = imageBrush
};
my cs
public partial class InputBlock : UserControl
{
public static readonly DependencyProperty ImageBrushProperty =
DependencyProperty.Register("ImageBrush", typeof(ImageBrush),
typeof(InputBlock),
new PropertyMetadata(null));
public ImageBrush ImageBrush
{
get { return (ImageBrush)GetValue(ImageBrushProperty); }
set { SetValue(ImageBrushProperty, value); }
}
public InputBlock()
{
InitializeComponent();
}
}
in xaml
<Rectangle x:Name="ImageRectangle" Grid.Column="0" Grid.Row="0" Fill="{Binding ImageBrush}" Width="500" Height="500">
</Rectangle>
It works when I use color like red but it wont work with my ImagePath please what I am doing wrong?

You have to specify the source object of the Binding, i.e. the object that owns the source property.
Because that is the UserControl instance, write
Fill="{Binding ImageBrush,
RelativeSource={RelativeSource AncestorType=UserControl}}"
People may tell you to set the DataContext of the UserControl instance to itself, e.g. by setting DataContext = this; in the constructor. This is however a big mistake, because it prevents that you can use standard DataContext-based Bindings of the properties of your UserControl.

Related

Cannot bind to public property inside ResourceDirectory WinUI3/UWP

So I am currently trying to bind a public static SolidColorBrush to a "Filled" property on a Rectangle inside a ResourceDirectory Style (inisde App.xaml).
But I always get the Exception
"Type 'x' used after '{' must be a Markup Extention. Error code 0x09c6."
My xaml code looks like this (inside App.xaml):
<Rectangle
x:Name="SelectionIndicator"
Width="4"
Height="24"
Fill="{x:Bind SomeNamespace:SomeClass.SomeColor}"/>
and my codebehind looks like this (inside public class called "SomeClass" in Namespace called "SomeNamespace"):
public static SolidColorBrush SomeColor = new(Colors.Red);
I know its possible to bind to a public static property with x:Bind in WinUI/UWP but this somehow doesnt work.
I really need to bind from some code behind so I can implement INotifyPropertyChanged so I cant just create a new color resource.
What is the correct way to implement my wanted behavior?
Thanks in advance!
You probably can't call your PropertyChanged event handler from a static property. You'll need an instance.
Consider storing the brush as a non-static property in a wrapper class. You can instantiate this class as a resource in your App.xaml file and use it across your application.
ColorStorer is a class to store the SolidColorBrush:
public class ColorStorer: INotifyPropertyChanged
{
private SolidColorBrush scb = new SolidColorBrush(Colors.Red);
public SolidColorBrush Scb
{
get
{
return scb;
}
set
{
if (value as SolidColorBrush != null)
{
scb = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Instantiate it in Application.Resources in App.xaml:
<Application.Resources>
<ResourceDictionary>
<local:ColorStorer x:Key="TestAppColorStorer" />
</ResourceDictionary>
</Application.Resources>
Use it in your views and controls:
<Rectangle
Width="100"
Height="50"
HorizontalAlignment="Center"
Fill="{Binding Scb, Source={StaticResource TestAppColorStorer}, Mode=OneWay}" />

UWP canvas dynamic content

I want to be able to dynamically add different (self-created) "widgets" to my canvas and position them using Canvas.Top and Canvas.Left.
I have been able to add items using Canvas.Children.Add(), but I can't figure out how to create a binding to the Top and Left values.
Would it be a better idea to somehow bind the contents of the Canvas to a list and create all the bindings in XAML? Then again, how would I do that?
If you don't know the bindings in UWP, you can see this document.
Depending on your situation, you can consider using MVVM for binding, creating a ViewModel in Code-Behind and using Binding in the xaml to bind related properties.
CanvasPageViewModel.cs
public class CanvasPageViewModel : INotifyPropertyChanged
{
private double _rectTop;
public double RectTop
{
get => _rectTop;
set
{
_rectTop = value;
OnPropertyChanged();
}
}
private double _rectLeft;
public double RectLeft
{
get => _rectLeft;
set
{
_rectLeft = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
CanvasPage.xaml.cs
public CanvasPageViewModel viewModel = new CanvasPageViewModel();
public CanvasPage()
{
this.InitializeComponent();
this.DataContext = viewModel;
viewModel.RectTop = 20;
viewModel.RectLeft = 100;
}
CanvasPage.xaml
<Grid>
<Canvas Width="500" Height="500" Background="White">
<Rectangle Width="50" Height="50" Fill="Blue"
Canvas.Top="{Binding RectTop}"
Canvas.Left="{Binding RectLeft}"/>
</Canvas>
</Grid>
This is a simple example. If you want to modify the position of the control later, you can directly modify the data source and the UI will synchronize.
Update
If you need to manually add child elements of Canvas, you can use this method:
var myRect = new Rectangle()
{
Height = 50,
Width = 100,
Fill = new SolidColorBrush(Colors.Blue)
};
myCanvas.Children.Add(myRect);
But if you want to bind the created Rectangle element, as you said, bind the Canvas.Left property, you can write:
public CanvasPageViewModel viewModel = new CanvasPageViewModel();
public CanvasPage()
{
this.InitializeComponent();
var myRect = new Rectangle()
{
Height = 50,
Width = 100,
Fill = new SolidColorBrush(Colors.Blue)
};
Binding leftBinding = new Binding()
{
Path = new PropertyPath("RectLeft"),
Mode=BindingMode.OneWay
};
myRect.SetBinding(Canvas.LeftProperty, leftBinding);
myCanvas.Children.Add(myRect);
}
Best regards.

How to bind to the control property itself, instead of DataContext value property?

I have a user control. With XAML. It has it's own properties, it has its ViewModel object set as DataContext:
<ComboBox ItemsSource="{Binding Items}" SelectedIndex="0">
<ComboBox.DataContext>
<vm:WindowsProfilePicker />
</ComboBox.DataContext>
Binding to its DataContext value properties is super easy and works as expected.
Let's say:
<Image Source="{Binding UserImage}" />
UserImage is a property of the ViewModel.
The Image element is however a part of my user control. The control has its own property named ImageSize defined as follows:
public static readonly DependencyProperty ImageSizeProperty
= DependencyProperty.Register(
"ImageSize",
typeof(double),
typeof(WindowsProfilePicker),
new FrameworkPropertyMetadata(126.0)
);
Of course we have getter and setter for it in code:
public double ImageSize {
get => (double)GetValue(ImageSizeProperty);
set => SetValue(ImageSizeProperty, value);
}
Now I would want to reference that property in my UserControl's XAML. It looks like this:
<Image
Width="
{Binding RelativeSource={RelativeSource AncestorType=local:WindowsProfilePicker},
Path=ImageSize}"
Height="{Binding RelativeSource={RelativeSource AncestorType=local:WindowsProfilePicker},
Path=ImageSize}"
Source="{Binding UserImage}" />
Nice? Not really, and it doesn't work. I get no errors, no warnings, but the image size is not set. The image in the control sets its size from the source bitmap size. When I replace my binding with a number, it works, the size is fixed. However, I want ImageSize property of my new control to be used as Image Width and Height. What am I doing wrong?
BTW, obviously I don't want the property to be bound with my ViewModel, because it's strictly presentation feature, unrelated to data.
The visuals like sizes must be set in XAML (optimally as styles), the data (in my case user profile pictures) in code, the control's ViewModel.
Try configuring property metadata somewhat like this:
public static readonly DependencyProperty ImageSizeProperty
= DependencyProperty.Register(
"ImageSize",
typeof(double),
typeof(WindowsProfilePicker),
new FrameworkPropertyMetadata((double)126.0,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault
| FrameworkPropertyMetadataOptions.AffectsRender
| FrameworkPropertyMetadataOptions.AffectsMeasure)
);
[TypeConverter(typeof(LengthConverter))]
public double ImageSize
{
get => (double)GetValue(ImageSizeProperty);
set => SetValue(ImageSizeProperty, value);
}
or
public static readonly DependencyProperty ImageSizeProperty
= DependencyProperty.Register(
"ImageSize",
typeof(double),
typeof(WindowsProfilePicker),
new FrameworkPropertyMetadata((double)126.0,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault
| FrameworkPropertyMetadataOptions.AffectsRender
| FrameworkPropertyMetadataOptions.AffectsMeasure)
{
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
(AffectsRender & AffectsMeasure may not be necessary though)

How to set a WPF usercontrol property from XAML?

I'm trying to set the fill property of several instances of the same usercontrol from XAML in order to distinguish them. I'm using a dependency property in the C# codebehind of the control and referring to that in the XAML when I instantiate the control. Here's a simplified example of what I've tried, first the XAML of the user control:
<UserControl x:Class="RectangleFillUserControlTest.RectangleFillTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="150">
<Grid>
<Rectangle x:Name="rect" HorizontalAlignment="Left" Height="50" Stroke="Black" VerticalAlignment="Top" Width="150"/>
</Grid>
</UserControl>
Now the codebehind:
namespace RectangleFillUserControlTest
{
public partial class RectangleFillTest : UserControl
{
SolidColorBrush fillBrush;
public static readonly DependencyProperty FillColourProperty = DependencyProperty.Register
("FillColour", typeof(string), typeof(RectangleFillTest), new PropertyMetadata(string.Empty));
public string FillColour
{
get { return (string)GetValue(FillColourProperty); }
set
{
SetValue(FillColourProperty, value);
if (value == "red") fillBrush = new SolidColorBrush(Colors.Red);
else fillBrush = new SolidColorBrush(Colors.Green);
rect.Fill = fillBrush;
}
}
public RectangleFillTest()
{
InitializeComponent();
}
}
}
I instantiate the control in the main window and try to set the fill colour to red:
<Window x:Class="RectangleFillUserControlTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RectangleFillUserControlTest"
Title="MainWindow" Height="350" Width="525">
<Grid Background="#FF1D2CC3">
<local:RectangleFillTest FillColour="red"/>
</Grid>
</Window>
But the rectangle remains unfilled, even when I run the project. Can anyone help please?
Cheers,
Tim
There are two things wrong with your dependency property.
First, its type should be Brush, not string, because that is the type used by properties of WPF controls like Shape.Fill or Control.Background. WPF provides automatic type conversion from strings like "Red" or "#FFFF0000" in XAML to type Brush.
Second, you should not have anything else than a call to SetValue in the setter method of the CLR wrapper. The reason is explained in the XAML Loading and Dependency Properties article on MSDN:
Because the current WPF implementation of the XAML processor behavior
for property setting bypasses the wrappers entirely, you should not
put any additional logic into the set definitions of the wrapper for
your custom dependency property. If you put such logic in the set
definition, then the logic will not be executed when the property is
set in XAML rather than in code.
So your dependency property declaration should look like this:
public static readonly DependencyProperty FillBrushProperty =
DependencyProperty.Register(
"FillBrush", typeof(Brush), typeof(RectangleFillTest));
public Brush FillBrush
{
get { return (Brush)GetValue(FillBrushProperty); }
set { SetValue(FillBrushProperty, value); }
}
To react to property changes, you would now register a PropertyChangedCallback with property metadata. But you don't need to do that here, because you could simply bind the property in the UserControl's XAML like this:
<Rectangle Fill="{Binding FillBrush,
RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" ... />
I will explain why is is not working and how to solve.
1.- A Dependency Property is only called when the usercontrol has that dependency property in the visual tree.
In case you want to do in that way, you need to add for instance :
new PropertyMetadata(string.Empty, ValueChanged));
and there change the value:
public static readonly DependencyProperty FillColourProperty = DependencyProperty.Register
("FillColour", typeof(string), typeof(RectangleFillTest), new PropertyMetadata(string.Empty, ValueChanged));
private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as RectangleFillTest;
var fillBrush = new SolidColorBrush();
if (control.FillColour == "red")
fillBrush = new SolidColorBrush(Colors.Red);
else
fillBrush = new SolidColorBrush(Colors.Green);
control.rect.Fill = fillBrush;
}
public string FillColour
{
get
{
return (string)GetValue(FillColourProperty);
}
set
{
SetValue(FillColourProperty, value);
}
}
That is explicit for your logic, in case you need a more generic code for any color, etc using binding the property to the rectangle, just tell me.
You need to bind your Dependency Property to the Fill property of your Rectangle in the xaml of your UserControl. You'll have Something like this :
<Rectangle x:Name="rect" Fill="{Binding FillColour, RelativeSource={RelativeSource FindAncestor, AncestorType=RectangleFillTest}}" HorizontalAlignment="Left" Height="50" Stroke="Black" VerticalAlignment="Top" Width="150"/>
Also, in your dependency property, it's type should be Brush, and not String.

WPF Dependency property binding breaks and always uses the default value

I have a dependency property defined as below. It is defined in xaml.cs of Childusercontrol. It always uses the default value of RGB(255,0,0) ie. Red.
public Color ForeColor
{
get {return (Color)this.GetValue(ForeColorProperty); }
set {this.SetValue(ForeColorProperty, value);}
}
public static readonly DependencyProperty ForeColorProperty = DependencyProperty.Register("ForeColor", typeof(Color), typeof(Childusercontrol), new PropertyMetadata(Color.FromRgb(255,0,0), OnCurrentForeColorPropertyChanged));
private static void OnCurrentForeColorPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
Childusecontrol control = source as Childusecontrol;
Color fcolor= (Color)e.NewValue;
}
The value is passed through xaml from parent usercontrol as
<UC:Childusercontrol ForeColor="{Binding ChildForeColor}"/>
ChildForeColor is a property of type Color in ViewModel of ParentUserControl and is defined as below.
private Color _ChildForeColor;
public Color ChildForeColor
{
get
{
return _ChildForeColor ;
}
set
{
if (_ChildForeColor != value)
{
_ChildForeColor = value;
RaisePropertyChanged(()=> ChildForeColor );
}
}
}
And ChildForeColor property is set as below, in the parentusercontrol's constructor.
The value being passed as constructor parameter is blue.
public Parentusercontrol(System.Drawing.Color ForeColor)
{
ChildForeColor = Color.FromRgb(ForeColor.R, ForeColor.B, ForeColor.G);
}
But, the InitializeComponent(); of Parent control's xaml.cs clears the value of dependency property and hence, only the default value is used.
Do I have to change the definition of the dependency property? How to fix this bug?
This worked perfectly fine for me!
ChildControl
I gave the UserControl a Name in Xaml i.e
<UserControl ... (all normal namespaces)... x:Name="Child">
<Border>
<Border.Background>
<SolidColorBrush Color="{Binding ForeColor, ElementName=child}"/>
</Border.Background>
</Border>
</UserControl>
The property "ForeColor" is a dependency property as you defined it yourself. This control works perfectly on its own too.
ParentControl
I did the same as with ChildControl. i.e. gave it a name.
<UserControl ... (Usual NS)... x:Name="parent">
<Border BorderThickness="2">
<local:ChildUserControl Margin="5" ForeColor="{Binding ChildForeColor, ElementName=parent}"/>
<Border.BorderBrush>
<SolidColorBrush Color="{Binding ChildForeColor, ElementName=parent}"/>
</Border.BorderBrush>
</Border>
</UserControl>
This also works fine with testing the C# Class looks as follows
public ParentUserControl(System.Drawing.Color c)
{
InitializeComponent();
Color c2 = Color.FromRgb(c.R, c.G, c.B);
ChildForeColor = c2;
}
private Color _ChildForeColor = Color.FromRgb(0, 255, 0);
public Color ChildForeColor
{
get { return _ChildForeColor; }
set
{
if (value != _ChildForeColor)
{
_ChildForeColor = value;
OnPropertyChanged(() => ChildForeColor);
}
}
}
I have assigned the _ChildForeColor a value just for testing, but this is not needed. Please note however that if you run a NotifyPropertyChanged event this cannot happen before InitializeComponent(); This I guess is because nothing yet has been initialized to listen to the change. Therefore you have 2 options. Remove OnPropertyChanged and assign color before InitializeComponent, or use OnPropertyChanged but only assign color after InitializeComponent. The first solution will still work because the property value is changed before the components go and look for the value.
Window for using constructing the controls
This is a bit more tricky as you have assigned a constructor that takes a variable. So my code looks as follows:
public Control ParContent
{
get { return (ContentControl)GetValue(ParContentProperty); }
set { SetValue(ParContentProperty, value); }
}
//Register Dependency ParContent Property
public static readonly DependencyProperty ParContentProperty = DependencyProperty.Register("ParContent", typeof(ContentControl), typeof(MainWindow), new PropertyMetadata( ));
public MainWindow()
{
InitializeComponent();
ParContent = new ParentUserControl(System.Drawing.Color.Blue);
}
and in Xaml
<Window ...Title="MainWindow" Height="478.784" Width="736.87" x:Name="win">
<Grid>
<local:ChildUserControl HorizontalAlignment="Left" Height="100" Margin="122,298,0,0" VerticalAlignment="Top" Width="100"/>
<ContentControl x:Name="Parent" Content="{Binding ParContent,ElementName=win}" HorizontalAlignment="Left" Margin="106,49,0,0" VerticalAlignment="Top" Height="79" Width="93"/>
</Grid>
</Window>
As I said this worked perfectly fine by me and all the properties keep their values.
Possible solutions:
Make sure the parent's childForeColor has a color assigned to it especially when using ordinary properties.
If you use ordinary properties in Parent control make sure INotifyPropertyChange is called if the color is changed after Initialize (Which I guess you subscribe to already)
perhaps use FrameworkPropertyMetadata instead and then add flag AffectsRender - don't think this is the problem, but worth a shot
Play around with the Binding Mode - although I do not think this is the real issue either
If you are working with 2 x controls where 1 property is most likely going to inherit from another use Inherited properties rather - http://msdn.microsoft.com/en-us/library/ms753197(v=vs.110).aspx
Bottom line I have a suspicion that the Parent's "ChildForeColor" might be causing the problem as the above seems ok to me at first glance.
EDIT
Try doing the following. In xaml give your parent control a name x:name="Parent" then in the binding mode do this
<UC:Childusercontrol ForeColor="{Binding ChildForeColor, ElementName="Parent"}"/>
This should sort out any binding issues if the problem lies with the binding.
However you say "Parent control's xaml.cs clears the value of dependency property and hence, only the default value is used." Which indicates that the problem is not with binding or with the child control as far as I can gather...
I also assumed you have stepped through the code so after you hit this
ChildForeColor = Color.FromRgb(ForeColor.R, ForeColor.B, ForeColor.G);
ChildForeColor appears correct and then if you override OnInitialized() and evaluate the value of ChildForeColor after base.OnInitialized(e); has run the ForeColor is still unchanged?
With this I also assume you have not removed InitializeComponent(); from the constructor, and InitializeComponent(); comes after ChildForeColor = ....! In your constructor you do not show where InitializeComponent() is and I assumed it was just for easy reading purpose.
If ForeColor remained unchanged at this point and assuming base.OnInitialized is the first method that runs in OnInitialized. Then Initialization is not the problem, then the alternative suggestion is to change ChildForeColor to a proper dependency property:
public Color ChildForeColor
{
get { return (Color)GetValue(ChildForeColorProperty); }
set { SetValue(ChildForeColorProperty, value); }
}
//Register Dependency ChildForeColor Property
public static readonly DependencyProperty ChildForeColorProperty = DependencyProperty.Register("ChildForeColor", typeof(Color), typeof(ParentControl), new FrameworkPropertyMetadata());
and see if that changes it.

Categories

Resources