WPF Button Image Source Binding String Dependency Property - c#

This worked in UWP but I can't get the image to show using WPF XAML.
I start by defining a UserControl which binds the path of the image file:
<Grid Height="70" Width="70">
<Border Style="{StaticResource border}">
<Button Style="{StaticResource button}">
<Image Source="{Binding prop_image}"/>
</Button>
</Border>
</Grid>
I define the dependency property as:
public static readonly DependencyProperty prop_image =
DependencyProperty.Register("prop_image_path", typeof(string),
typeof(user_control), null);
public string prop_image_path
{
get { return (string)GetValue(prop_image); }
set { SetValue(prop_image, value); }
}
I then try to consume it as:
<local:user_control Grid.Column="1" Grid.Row="2"
prop_image_path="/Assets/my.png"/>
which is the exact same as UWP but with Binding instead of x:bind. when I create a button and set the image it works . . . but it doesn't display the alpha channel(which I guess means I have to use an alpha mask and have two files.) aside from this, moving a bunch of stuff from UWP to WPF XAML was a snap.

At first, you are using the wrong property path in {Binding prop_image}, which should be {Binding prop_image_path}. Since this Binding is in a UserControl's XAML to one of its own properties, you should also specify the source object of the Binding to be the UserControl instance, like:
<Image Source="{Binding prop_image_path,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
Besides that, the WPF dependency property system requires that you adhere to naming conventions for the dependency property identifier field.
It must be named like the property with a Property suffix:
public static readonly DependencyProperty prop_image_pathProperty =
DependencyProperty.Register(
"prop_image_path",
typeof(string),
typeof(user_control),
null);
You may also notice that your naming scheme is a little uncommon. According to widely accepted conventions, C#/.NET type and property names should use Pascal Casing, i.e.
public class MyUserControl
{
public static readonly DependencyProperty ImagePathProperty =
DependencyProperty.Register(
nameof(ImagePath),
typeof(string),
typeof(MyUserControl));
public string ImagePath
{
get { return (string)GetValue(ImagePathProperty); }
set { SetValue(ImagePathProperty, value); }
}
}

Related

Wpf usercontrol with embedded button : change button's content

I have a simple usercontrol with a button in it which i modified.
When I add this usercontrol to my mainwindow, I can only access the usercontrol's properties. How can I access the button content ? Ideally I'd like to have a custom property let's say "TheText" and I changed it like that
<local:MyButtonControl TheText="My text here will be the button content">
This is what I have in the usercontrol "MyButtonControl"
public object TheText
{
get => (object)GetValue(_text);
set => SetValue(_text, value);
}
public static readonly DependencyProperty _text =
DependencyProperty.Register("Text", typeof(object), typeof(MyButton), new UIPropertyMetadata(null));
But what Am I supposed to put for binding ? Can't figure it out. Here's the concerned button.
<Button x:Name="button" Content="{Binding ??? }" Style="{StaticResource RoundedButton}"/>
The Binding should look like this:
<Button Content="{Binding Text,
RelativeSource={RelativeSource AncestorType=UserControl}}" .../>
Note that a correct dependency property declaration would have to use the same name for both the dependency property and the CLR wrapper. There is also a convention to name the identifier field as <PropertyName>Property.
public object Text
{
get => (object)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(object), typeof(MyButton));
You should certainly also use string as type of a property that is called Text. Or you call the property ButtonContent or something like that.

Binding Image Source with Dependency Property

I'm working on a custom control. This custom control has an image in it like this:
<Image Source="{TemplateBinding Poster}" />
In my C# file, the DependencyProperty is the following:
public static readonly DependencyProperty PosterProperty = DependencyProperty.Register(
nameof(Poster),
typeof(ImageSource),
typeof(MovieButton),
new UIPropertyMetadata(null));
public ImageSource Poster
{
get { return (ImageSource)GetValue(PosterProperty); }
set { SetValue(PosterProperty, value); }
}
Now I can add the following to my user control in XAML: Poster="Images/NoPoster.png" or Poster = new BitmapImage(new Uri(#"pack://application:,,,/Images/NoPoster.png")) from code.
Everything is working fine. What I'm wondering is, why can't I declare Poster as BitmapImage or string instead of ImageSource?
You can of course declare it as BitmapImage. But that would be an unnecessary restriction, because the base class ImageSource is sufficient.
You can also use string or Uri as property type, but then you should replace the TemplateBinding by a regular Binding, because source and target property types no longer match, and built-in automatic type conversion should take place:
<Image Source="{Binding Poster, RelativeSource={RelativeSource TemplatedParent}}" />
Note that you do not need an explicit binding converter, because type conversion is performed automatically by the ImageSourceConverter class, which is registered as type converter for ImageSource.

WPF Dependency Control Updates

I have a user control called TitleBar which contains this DependencyProperty:
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(TitleBar), new PropertyMetadata(null));
public string Title
{
get
{
return (string)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
}
}
The xaml for the user control looks like:
<Label Content="{Binding Title, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
And my xaml of the View looks like:
<Components:TitleBar x:Name="customTitleBar" Title="Test" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="3"/>
When I set Title="Test" on the View that holds the TitleBar usercontrol, the changes do not show up in the designer, nor at runtime. How can I fix this?
Figured it out -- I was incorrectly handling the OnPropertyChanged event due to being confused about the parameters.
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(TitleBar), new PropertyMetadata(null
() => { (sender, e) ... }))
Worked after casting the parameters and calling view updates directly on them.
I don't have full code but my guess is that the problem is in binding context for Label, which by default will be DataContext and not UserControl.
<Label Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=Title}"
Changing binding source to be your UserControl should help and you shouldn't have to handle property updates from callback

TemplateBinding not working for textbox text

I have a custom control called EnhancedTextBox which is a UserControl that has a TextBox and a Button. To the consumer I want it to mostly look like a TextBox, so I did the following:
<UserControl.Template>
<ControlTemplate TargetType="textBoxes:EnhancedTextBox">
...
<TextBox Text="{TemplateBinding Text}"...
And in EnhancedTextBox I have
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof (String), typeof (EnhancedTextBox));
public String Text
{
get { return (String) GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
Yet, when I use it as the following:
<EnhancedTextBox Text="{Binding MyText, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}}" />
Then, MyText is never updated, as well as I inspect EnhancedTextBox.Text and it is null. What am I missing? I have been staring at this for a bit and can't figure out what is wrong. I even thought it might be the fact that I was using the same name, so create a property called Text1 which did not work....
Also of note, if I use a regular TextBox, then this all works. So, I am fairly certain the problem is with the EnhancedTextBox itself
I figured it out after reading this MSDN about TemplateBinding. Specifically,
A TemplateBinding is an optimized form of a Binding for template scenarios, analogous to a Binding constructed with {Binding RelativeSource={RelativeSource TemplatedParent}}.
So, I decided to do this explicitly...which would allow me to set the UpdateSourceTrigger (still not sure why it doesn't default to PropertyChanged)
<TextBox Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text, UpdateSourceTrigger=PropertyChanged}"....
And, now it is working. TemplateBinding does not even expose these properties....again, not sure why
You are missing the CallBack when you register the property.
Here's a sample code.
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
public void IsSelectedChangedCallback()
{
//actions when property changed
}
private static void OnSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
userControl.IsSelectedChangedCallback();
}
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register("IsSelected", typeof(bool), typeof(MyUserControl), new PropertyMetadata(new PropertyChangedCallback(OnSelectedChanged)));

WPF: initial visibility and template loading

I have encountered an issue where the bindings are not properly set on one of my user controls if that control is not visible when it is initialized. I have duplicated the issue with the following dumbed-down controls:
public class Test3 : Control
{
static Test3()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Test3), new FrameworkPropertyMetadata(typeof(Test3)));
}
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register("Test", typeof(string),
typeof(Test3), new UIPropertyMetadata("test3 default text"));
}
public class Test2 : Control
{
static Test2()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Test2), new FrameworkPropertyMetadata(typeof(Test2)));
}
public FrameworkElement Test3Control
{
get { return (FrameworkElement)GetValue(Test3ControlProperty); }
set { SetValue(Test3ControlProperty, value); }
}
public static readonly DependencyProperty Test3ControlProperty =
DependencyProperty.Register("Test3Control", typeof(FrameworkElement),
typeof(Test2), new UIPropertyMetadata(null));
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register("Test", typeof(string), typeof(Test2),
new UIPropertyMetadata("test2 default text"));
}
public partial class Test1 : UserControl
{
public Test1()
{
InitializeComponent();
}
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register("Test", typeof(string),
typeof(Test1), new UIPropertyMetadata("test1 default text"));
}
XAML for usercontrol:
<UserControl x:Class="Test.Test1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dummy="clr-namespace:WpfTestApplication.Test"
Name="ucThis">
<UserControl.Resources>
<Style TargetType="{x:Type test:Test2}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type test:Test2}">
<GroupBox Header="Test2">
<StackPanel>
<TextBox IsEnabled="False" Text="{TemplateBinding Test}"/>
<GroupBox Header="Test3">
<ContentPresenter Content="{TemplateBinding Test3Control}"/>
</GroupBox>
</StackPanel>
</GroupBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<test:Test2 Test="{Binding ElementName=ucThis,Path=Test}">
<test:Test2.Test3Control>
<test:Test3 Test="{Binding ElementName=ucThis,Path=Test}">
<test:Test3.Template>
<ControlTemplate TargetType="{x:Type test:Test3}">
<TextBox IsEnabled="False" Text="{TemplateBinding Test}"/>
</ControlTemplate>
</test:Test3.Template>
</test:Test3>
</test:Test2.Test3Control>
</test:Test2>
</Grid>
</UserControl>
... and XAML for main window (the guts of it, anyway):
<DockPanel>
<StackPanel>
<TextBox Name="tbInput"/>
<Expander Header="Initially Visible" IsExpanded="True">
<test:Test1 Test="{Binding ElementName=tbInput, Path=Text}" />
</Expander>
<Expander Header="Initially Collapsed" IsExpanded="False">
<test:Test1 Test="{Binding ElementName=tbInput, Path=Text}" />
</Expander>
</StackPanel>
</DockPanel>
I would expect that whatever text is entered into the textbox ("tbInput") would be displayed in the Test2 and Test3 boxes - and indeed it is for both instances of Test2 but only for the Test3 that is initially visible. The Test3 that is initially collapsed always displays the default text, even if it is visible when the text is entered.
I've tried to investigate this using Snoop, but the issue corrects itself when I evaluate the relevant parts of the tree w/ Snoop so it hasn't been much help.
What is causing this behavior? How can I correct it? Where can I read more about it?
UPDATE:
By watching the output window I discovered this error message:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=ucThis'. BindingExpression:Path=Test; DataItem=null; target element is 'Dummy3' (Name=''); target property is 'Test' (type 'String')
By handling the loaded and initialized events on these controls I can see that this error occurs after Dummy1 and Dummy2 have loaded at startup. Dummy 3 doesn't load until it is made visible.
I believe the problem is that, since the template has not been applied for the collapsed Test3 instance, it has not been inserted into the visual tree. It is therefore not within the name scope of the outer Test1 instance when the binding is created, and thus cannot resolve the name ucThis specified for ElementName.
You can deal with this by adding Test3Control to the logical tree of Test2. Try modifying the dependency property definition as follows:
public static readonly DependencyProperty Test3ControlProperty =
DependencyProperty.Register("Test3Control", typeof(FrameworkElement),
typeof(Test2),
new UIPropertyMetadata(null, OnTest3ControlPropertyChanged));
private static void OnTest3ControlPropertyChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var source = (Test2)d;
var oldValue = e.OldValue as FrameworkElement;
var newValue = e.NewValue as FrameworkElement;
if (oldValue != null)
source.RemoveLogicalChild(oldValue);
if (newValue != null)
source.AddLogicalChild(newValue);
}
In most cases when you add a new UIElement-based property to a control, you will want to ensure it gets added to the logical tree. This does not happen automatically. For that matter, it does not get added to the visual tree automatically either. In this case it is only loaded into the visual tree because it is explicitly inserted into the template.
Take a look at the internals of WPF's core controls, like Decorator (from which Border derives) and its Child property to see what kind of plumbing may be required when defining new control types.
Also note how many "child control" properties are not dependency properties. It's easy to run into problems with Visual-based dependency properties, particularly when you attempt to change them via setters or animate them. I find it's best to discourage developers from misusing the properties and creating headaches for themselves by simply exposing child controls as regular CLR properties.
I am not sure about your specific problem, but I'd move the Test2's implicit style definition to the Generic.xaml module which has to be in the "Themes" folder. The framework will scan automatically that file to look for implicit styles.

Categories

Resources