WPF XAML Binding not calling changed event - c#

My program has a custom DependencyObject to which I bind values of another DependencyObject which are set in code:
<TabControl
Grid.Column="1"
Grid.Row="1">
<TabItem
Header="XML">
<TextBox
Text="{Binding Asset.Xml, ElementName=window}"
IsReadOnly="True" />
</TabItem>
<TabItem
Header="Texture">
<we:DXImage>
<we:DXImage.Renderer>
<we:TextureRenderer
Source="{Binding Asset.Image, ElementName=window}" />
</we:DXImage.Renderer>
</we:DXImage>
</TabItem>
</TabControl>
The TextBox binding to Asset.Xml works flawlessly, also if I replace the xaml of the second item with a TextBox it also displays the content of Asset.Image (a path to an image of type string).
The Source Property of the renderer looks like this:
private static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(string), typeof(TextureRenderer),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, SourceChanged));
public string Source
{
get { return (string)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
private static void SourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
// Do stuff
}
However the SourceChanged event is never called.
I have updated the project on GitHub:
https://github.com/Qibbi/WrathEd/tree/master/WrathEd2
the xaml code is located in the WrathEd2 project while the DXImage, Renderer, and other support classes are in WrathEd.Windows
The current MainWindow is a mess code behind wise atm, I plan to refactor it into appropriate parts when finishing the project.

The problem is that your we:TextureRenderer is not a part of the VisualTree (as it is inside a property). Therefore, the binding cannot find the source Element.
According to ElementName Binding is failing, you can use
Source={x:Reference window}
instead of ElementName=window.

According to the MSDN documentation here
You need to set NotifyOnSourceUpdated to true for your binding

Related

WPF - Binding to custom UserControl's DependencyProperty doesn't work

I am currently working on a MVVM project that uses a Window (with my ViewModel) and my own UserControl. The UserControl is nearly empty in the .xaml file because all of its functionality comes from code-behind, which draws different shapes. I wanted to bind a property from ViewModel to a DependencyProperty in the UserControl, but no matter what I do, i cannot get it to work. I have read tons of answers here and on different websites and noticed that it might be something with the UserControl's DataContext, but I eventually failed to fix the problem anyway. The way I raise the PropertyChanged event in my ViewModel is correct. I can successfully bind my property to other controls (like TextBoxes etc.), but not to my one. I would be grateful if you could explain to me why it is not working and how to fix that. Regards!
MainWindow.xaml binding:
<Grid Margin="10">
<local:FretboardControl Grid.Row="0" Fretboard="{Binding CurrentFretboard, Mode=TwoWay}"/>
</Grid>
FretboardControl.xaml:
<UserControl ...>
<Grid>
<TextBlock Text="{Binding Path=Fretboard, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:FretboardControl}}"/>
//the TextBlock above is just a test
<Canvas.../>
</Grid>
</UserControl>
FretboardControl.xaml.cs (code-behind):
public static readonly DependencyProperty FretboardProperty = DependencyProperty.Register
(nameof(Fretboard), typeof(Fretboard), typeof(FretboardControl), new PropertyMetadata(new Fretboard(), PropertyChangedCallback));
public Fretboard Fretboard {
get {
return GetValue(FretboardProperty) as Fretboard;
}
set {
SetValue(FretboardProperty, value);
}
}
protected static void PropertyChangedCallback(DependencyObject o, DependencyPropertyChangedEventArgs e) {
//breakpoint here. It is reached only once during runtime:
//at start, when the default value is inserted
if (o is FretboardControl) {
(o as FretboardControl).RefreshFretboard();
}
}
Okay, so apparently i probably found the seed of my problem. My CurrentFretboard setter raised the PropertyChanged event, but i did not change the reference of the object itself. The object was modified, but it was still the same object. I thought that this would not matter and it would be sent anyway to the binding, but it looks like the PropertyChangedCallback is called only if the reference was changed. I guess i can replace the reference on each set or just listen to the PropertyChanged event already in the UserControl. Thanks for help!

How to use ElementName binding from resource?

I've added a MenuFlyout to a button in ItemsControl.ItemTemplate. Also I was able to bind current item as CommandParameter.
Now I want to bind Command to a MenuFlyoutItem.
In codebehind :
LayoutRoot.DataContext = this;
So if i bind to LayoutRoot I will actually bind to my current UserControl. But the following binding is not working:
Command="{Binding ActivateProfileCommand, ElementName=LayoutRoot}"
It gives me not errors in Output but it's not working.
Here's the full example:
<controls:HeaderDecorator x:Uid="AccountsHeader" Text="Accounts" x:Name="LayoutRoot" Name="LayoutRoot">
<controls:HeaderDecorator.Resources>
<MenuFlyout x:Key="AccountMenuFlyout">
<MenuFlyoutItem Text="Activate" Name="Activate"
Command="{Binding ActivateProfileCommand, ElementName=LayoutRoot}"
CommandParameter="{Binding}" />
</MenuFlyout>
</controls:HeaderDecorator.Resources>
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding Settings.Profiles}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<HyperlinkButton Content="{Binding}" FlyoutBase.AttachedFlyout="{StaticResource AccountMenuFlyout}" >
<i:Interaction.Behaviors>
<ic:ShowFlyoutBehavior />
</i:Interaction.Behaviors>
</HyperlinkButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</controls:HeaderDecorator>
Seems the problem is i'm trying to use shared object in Resources. Can I do it? And why not?
The issue you are seeing here is the MenuFlyoutItem is no longer in the datacontext you perhaps think it is. I'll try and explain this as best I can as a few I know who work with xaml have come across this and hit their heads off walls for days about it. It's also known to not show errors in your particular scenario; further increasing confusion.
In a nutshell. When the MenuFlyout is added inside the ItemTemplate of each item in your collection, it does not have access to the datacontext you perhaps think it does. In this case, the datacontext that the control now resides is actually the individual item within the collection it is sitting in.
There is however a solution to this. I have something similar to yourself. An ItemsControl which has it's ItemsTemplate defined that includes a UIElement who's FlyoutBase AP references a MenuFlyout defined in a resource dictionary.
The xaml is pretty much the same except I don't need the ElementName in the binding.
However, I have now turned my attention to the type that the collection holds. I have code that looks something like this.
public class AnItemToList
{
public AnItemToList(Action commandDel)
{
TestCommand = new RelayCommand(commandDel);
}
public string Name { get; set; }
public RelayCommand TestCommand { get; set; }
}
Note that the command is being defined in the item itself and that I'm passing the method that the command will execute via the constructor.
All I have to do for the command binding on the MenuFlyoutItem is
<MenuFlyoutItem Text="Activate"
Name="Activate"
Command="{Binding TestCommand}"/>
I don't have a command param set here as I just quickly put together a basic template Windows Phone app and the pre-packed ICommand implementation doesn't have a delegate set to take the param.
If you now stick a break point in the method the command is calling, you'll see it will be called from any of the MenuFlyoutItem's bound to the command that references it.
Bare in mind that this isn't the only way of solving this problem; but it is one I use myself on occasion. For example, in WPF XAML you can make use of RelativeSource to go looking for the command on a parent control's datacontext.
Hope this helps.
Here's a general "Pair" object:
public class Pair : DependencyObject
{
public static readonly DependencyProperty FirstProperty = DependencyProperty.Register("First",
typeof(object), typeof(Pair), new PropertyMetadata(null));
public static readonly DependencyProperty SecondProperty = DependencyProperty.Register("Second",
typeof(object), typeof(Pair), new PropertyMetadata(null));
public object First
{
get { return GetValue(FirstProperty); }
set { SetValue(FirstProperty, value); }
}
public object Second
{
get { return GetValue(SecondProperty); }
set { SetValue(SecondProperty, value); }
}
}
In ItemTemplate i put something like this:
<DataTemplate>
<Grid>
<Grid.Resources>
<viewModel:Pair x:Key="Tuple" First="{Binding DataContext, ElementName=LayoutRoot}"
Second="{Binding}" />
</Grid.Resources>
<HyperlinkButton Content="{Binding Second.ProfileName}"
DataContext="{StaticResource Tuple}"
FlyoutBase.AttachedFlyout="{StaticResource AccountMenuFlyout}"
</HyperlinkButton>
</Grid>
</DataTemplate>
Now I can easily reference Tuple elements from my Resource like this:
<MenuFlyoutItem Text="Activate" Name="Activate"
Command="{Binding First.ActivateProfileCommand}"
CommandParameter="{Binding Second}" />

Binding to DependencyProperty with no result

I have problem with binding to dependency property of my new control.
I decided to write some tests to examine this issue.
Binding from TextBox.Text to another TextBox.Text
XAML code:
<TextBox Name="Test" Text="{Binding ElementName=Test2, Path=Text, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Name="Test2" Grid.Row="2" />
The result is good - when I writing something in first TextBox -> second TextBox is updating (conversely too).
I created new control -> for example "SuperTextBox" with dependency property "SuperValue".
Control XAML code:
<UserControl x:Class="WpfApplication2.SuperTextBox"
...
Name="Root">
<TextBox Text="{Binding SuperValue, ElementName=Root, UpdateSourceTrigger=PropertyChanged}" />
</UserControl>
Code behind:
public partial class SuperTextBox : UserControl
{
public SuperTextBox()
{
InitializeComponent();
}
public static readonly DependencyProperty SuperValueProperty = DependencyProperty.Register(
"SuperValue",
typeof(string),
typeof(SuperTextBox),
new FrameworkPropertyMetadata(string.Empty)
);
public string SuperValue
{
get { return (string)GetValue(SuperValueProperty); }
set { SetValue(SuperValueProperty, value); }
}
}
Ok, and now tests!
Binding from TextBox.Text to SuperTextBox.SuperValue
<TextBox x:Name="Test1" Text="{Binding ElementName=Test2, Path=SuperValue, UpdateSourceTrigger=PropertyChanged}" />
<local:SuperTextBox x:Name="Test2" Grid.Row="2"/>
Test is correct too!
When I writing something in TextBox, SuperTextBox is updating.
When i writing in SuperTextBox, TextBox is updating.
All is ok!
Now a problem:
Binding from SuperTextBox.SuperValue to TextBox.Text
<TextBox x:Name="Test1"/>
<local:SuperTextBox x:Name="Test2" SuperValue="{Binding ElementName=Test1, Path=Text, UpdateSourceTrigger=PropertyChanged}" Grid.Row="2"/>
In this case, when I writing something in SuperTextBox, TextBox is not updating!
How can I fix this?
PS: Question is very very long, I am sorry for that, but i tried exactly describe my problem.
The reason why one works and the other doesn't is because the Text dependency property of TextBox is defined to bind TwoWay by default, while your dependency property SuperValue isn't. You need to use TwoWay-binding if you want the destination to update the source in addition to the source updating the destination.
To fix this, you can add FrameworkPropertyMetadataOptions.BindsTwoWayByDefault to SuperValue's metadata like so:
public static readonly DependencyProperty SuperValueProperty = DependencyProperty.Register(
"SuperValue",
typeof(string),
typeof(SuperTextBox),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
Change binding mode into TwoWay.
Since in first two cases Test1 knows when it needs to update itself but not in third case. Only Test2 knows that when it should update in third case. That's why TwoWay mode is required in third case.
EDIT
First case is working since behind the scenes,xaml hook to
AddValueChanged event exposed by the PropertyDescriptor. For the
reason it's working refer to this link here.

How to add variable Image on User Control

I'm trying to have a user control where an image is passed in from its containing element. The purpose is so that I can reuse a common set of visual elements while only changing the image. For example:
The control usage:
<DataTemplate DataType={x:Type myType}>
<local:MyControl PlotIconSource="..\Images\Scatter.png"/>
</DataTemplate>
The Image inside the control
<UserControl x:Class="MyControl">
<Image Source="{Binding PlotIconSource}"/>
</UserControl>
Finally the dependency property for PlotIconSource in the code-behind for MyControl.xaml.cs.
public ImageSource PlotIconSource
{
get { return (ImageSource)GetValue(PlotIconSourceProperty); }
set { SetValue(PlotIconSourceProperty, value); }
}
public static readonly DependencyProperty PlotIconSourceProperty =
DependencyProperty.Register(
"PlotIconSource",
typeof(ImageSource),
typeof(PlotHeader),
new UIPropertyMetadata());
I'm sure I've missed something along the way so any help would be appreciated.
You might want to bind via RelativeSource or with ElementName:
<UserControl x:Class="MyControl" Name="control">
<Image Source="{Binding PlotIconSource, ElementName=control}"/>
</UserControl>
(Do not set the DataContext, it will be invisible from the outside and mess with bindings meant for an inherited DataContext)
Looks right to me, are you getting an error message or something?

Usercontrol databinding property changed MVVM

I am working with WPF and using data binding.
I would like to make a UserControl which has a property that could be used for data binding.
Also, I want to update some other property in the UserControl if the property changed.
For example,
public class MyControl : UserControl
{
....
....
....
....
public ViewStyles CurrentView
{
get { return (ViewStyles)GetValue(CurrentViewProperty); }
set
{
SetValue(CurrentViewProperty, value);
UpdateView();
}
}
public static readonly DependencyProperty CurrentViewProperty = DependencyProperty.Register("CurrentView", typeof(ViewStyles), typeof(ComboView));
....
.....
.....
.....
}
Problems comes:
A ViewModel is used and in which, there is a property ViewStyle which binded to the CurrentView in the above.
Another control combobox is also data-binded with ViewStyle in the ViewModel.
Actually, I want to use a combobox to choose the different view of my control. How to make it possible in MVVM?
I tried the above method. However, the UI (the different ViewStyles of MyControl) didn't change. It only change when I click on it using the mouse.
Thank you.
XAML: (MyControl)
<Views:MyControl Grid.Column="1" Grid.Row="1" Height="505" HorizontalAlignment="Left" Margin="2,0,0,0" Name="comboView1" VerticalAlignment="Top" Width="983"
ViewStyle="{Binding Path=CurrentView}" BorderThickness="5" BorderBrush="Black" ItemsSource="{Binding Path=Images}"
SelectedIndex="{Binding Path=CurrentIndex}" Foreground="White"
</Views:MyControl>
XAML: (ComboBox)
<ComboBox Margin="0,3,1,0" Width="178" HorizontalAlignment="Right" Name="ViewDDBox" FontSize="13" Foreground="#FFF6F3F3" Background="#FF444444"
BorderThickness="2" Height="23" VerticalAlignment="Top" Grid.Column="1"
ItemsSource="{Binding Path=ViewTypes}" IsEnabled="True" SelectedValue="{Binding Path=CurrentView, Mode=TwoWay}">
</ComboBox>
It is supposed that the view (some UI effect) will be changed of MyControl after choosing in the Combobox. But now, it only change when I click on MyControl using mouse.
The UpdateView() in your CurrentView property setter raises a HUGE red flag! You should never have any content other than SetValue in a dependency property setter, as certain aspects of xaml call the SetValue directly instead of going through the property. Always use the coerce property callback (if you want to validate the data before it's set) or the property changed callback (if you want to act after the property is changed, as I show in the example below).
You should do this instead:
public static DependencyProperty CurrentViewProperty =
DependencyProperty.Register("CurrentView", typeof(ViewStyles), typeof(ComboView),
new FrameworkPropertyMetadata(CurrentViewPropertyChanged));
private static void CurrentViewPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyControl mc = (MyControl)d;
mc.UpdateView();
}
Instead of binding the view, why not create a templated control and then bind the control's view to the property on your viewmodel?
You may also have to use data template triggers on your template to get the desired functionality.
Check out this article for help on template basics and this one for a more in depth discussion.

Categories