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

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!

Related

WPF MVVM Exception when Registering UserControl in DataContext

Im new to MVVM and try to follow all the guidelines I find to respect it. I would like to have a Busy-Animation on one of my usercontrols. I want to include it on the control like this.
The Usercontrol it is nested in is shown on the MainWindow using a DataTemplate for a ViewModel, for example like so:
<Window.Resources>
<DataTemplate DataType="{x:Type AppViews:AppConfigViewModel}">
<local:AppConfigView />
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding CurrentPageViewModel}" />
</Grid>
When running this, the Application is shown and I also see the view for the AppConfigViewModel which is bind correctly since underlying values are displayed correctly in the view.
Now I tried to register the Busy-Animation in the ViewModel (to control it from there) by doing this in the Constructor of the BusyAnimation:
(DataContext as PageViewModel).BusyAnim = this;
For some reason the DataContext is always null and the result of this line is an exception. What am I doing wrong here?
What I tried to did there is against the idea of MVVM.
I tried downcasting an object that is meant to be general.
A better aproach for the task I tried to achieve is implementing dependency properties in the busy animation component. Those are meant to be bound to from the viewmodel of the mainly displayed view. that way the busy animation can be shown when some property in the viewmodel changes. That could be for example a bool with the name "working".
this is the dependency property code in my busy animation:
public static readonly DependencyProperty ShowBusyProperty = DependencyProperty.Register("ShowBusy", typeof(Boolean), typeof(FortschrittView), new PropertyMetadata(false, OnShowBusyPropertyChanged));
public Boolean ShowBusy
{
get { return (Boolean)this.GetValue(ShowBusyProperty); }
set { this.SetValue(ShowBusyProperty, value); }
}
private static void OnShowBusyPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
FortschrittView myUserControl = dependencyObject as FortschrittView;
myUserControl.OnPropertyChanged("ShowBusy");
myUserControl.OnShowBusyPropertyChanged(e);
}
private void OnShowBusyPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if(ShowBusy)
{
Start();
}
else
{
Stop();
}
}
Yes its a lot of code, but I feel wpf wants it that way. Remember above code is in the busy-animation user control and triggers Start() Stop() functions which control storyboards.
Below xaml is in the control that uses the busyanimation, binding it to a viewmodel that the busy-animation should indicate background-work for:
<local:BusyAnimation ShowBusy="{Binding Model.IsBusy}"/>
That ShowBusy Property there is the Dependency Property implemented above. Of course IsBusy from the model should follow the observable pattern for everything to work.
/ps: I throughoutly documented the mistakes i did and how i solved them. Can I get rid of the negative points I got somehow for creating this question?

Binding Simple WPF TextBox Text TwoWay

I am very sorry that this question is very basic. I just learned WPF and I failed to make simple two way binding to textbox.text to string property.
XAML Code:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="StuInfo">
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="10,26,0,0" TextWrapping="Wrap" Text="{Binding Path=str,Mode=TwoWay}" VerticalAlignment="Top" Width="120"/>
<Button x:Name="button" Content="Check" HorizontalAlignment="Left" Margin="10,67,0,0" VerticalAlignment="Top" Width="75" Click="button_Click"/>
</Grid>
C# Code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
str = "OK";
}
public string str { get; set; }
private void button_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine(str);
}
}
First, the textbox does not show "OK", but it is blank. Then, I typed a different text into the textbox, for ex:"blablabla" without the quotes. Then I click the button to check if my str property has been updated. Apparently, str still contains "OK".
What did I do wrong here? What did I miss to make the binding work?
As a newcomer to WPF, all this Binding and DataContext jazz can be quite confusing. Let's start with your binding expression first...
<TextBox Text="{Binding Path=str, Mode=TwoWay}"/>
What this is saying is that you want to bind your Text property to whatever the DataContext of the TextBox is. DataContext is essentially the "thing" your TextBox is getting it's data from. Now here's the rub. DataContext is inherited from the element "above" it in the visual tree if not explicitly set. In your code, TextBox inherits it's DataContext from the Grid element, which in turn inherits it's DataContext from the Window element. Seeing that DataContext is not set in your Window the default value of the DataContext property will be applied, which is null. The DataContext is also not set in any of the child elements of your window, which, via inheritance, will set the DataContext of all children of that window to null.
It is important to note that you've left out the Source property in your binding expression.
<TextBox Text="{Binding Source=left_out, Path=str, Mode=TwoWay}"/>
When this property is left out, the binding's source is implied to be the elements DataContext, which in this case is null, for the reasons mentioned above. Basically, what your expression is saying here is that you want to bind your text property to DataContext.str which resolved by WPF is null.str.
OK, cool. Now, how do we set the DataContext of your TextBox.Text binding to the Code Behind for the window so we can get at that str property? There are several ways to do this, but for our purposes we'll focus on setting it explicitly in the binding of the TextBox.Text property. Now, there are three different "source" type properties of bindings. "Source" being where we want our control/element's binding to get it's data from. We have Source, RelativeSource, and ElementName. We're only going to focus on ElementName here, but the others are essential to research and understand.
So, let's name our Window element so we can access it through the ElementName property.
<Window x:Class="WpfApplication1.MainWindow"
x:Name="_window"
...
Now we can set the ElementName property on the TextBox.Text binding to refer to the window.
<TextBox Text="{Binding ElementName=_window, Path=str, Mode=TwoWay}"/>
This means the binding will look for the _window.str property when trying to resolve it's binding. At this point, you still probably won't see your str value reflected in the TextBox. This is because it's value is set after the InitializeComponent method in the window's constructor. This function is where bindings are resolved for the first time. If you were to set the value of str before calling InitializeComponent, you would see the value reflected in the TextBox.
This brings us to Dependency Properties. For now, just know that Dependency Properties have built in change notification, which your binding needs so it "knows" when the binding has changed and when to resolve the binding value again. Yes, you could use INotifyPropertyChanged in your code behind, but there are good arguments for using DependencyProperties in this case, which will only confuse the issue at this point. But, it is another one of those things that is essential to understand.
Here is the code for a DependencyProperty for your str property.
public static readonly DependencyProperty StrProperty
= DependencyProperty.Register("Str", typeof(string), typeof(MainWindow),
new FrameworkPropertyMetadata(FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Str
{
get{return (string)GetValue(StrProperty);}
set{SetValue(StrProperty,value);}
}
Now you'll be able to set the value like such and have it reflect through the binding to your TextBox.
public MainWindow()
{
InitializeComponent();
Str = "OK";
}
At this point, all should be well. I hope this helps out. It took me a while get the hang of WPF. My suggestion would be to read as much as you can on DataContext, Binding, and DependencyProperty as these are the core of WPF. Good luck!
The problem is that, you dont bind to codebehind of Window, but to DataContext.
Try this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new DC();
}
public class DC
{
public string str { get; set; }
public DC()
{
str = "OK";
}
}
}
Normally, you would have two different files, but for test, you can do it in one file.
After that, your DC (DataContext) should implement INotifyPropertyChanged interface.
Try to find some article about MVVM like this http://www.codeproject.com/Articles/165368/WPF-MVVM-Quick-Start-Tutorial

WPF XAML Binding not calling changed event

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

Dependency Property on a usercontrol that is bound to another VM

I've run into the following problem:
I'm currently creating an on screen keyboard that is a usercontrol that has its own viewmodel.
<UserControl.DataContext>
<Binding Source="{StaticResource Locator}" Path="AlphaNumericKeyboard" />
</UserControl.DataContext>
I'm attempting to add a dependency property called KeyboardAlphaMode that can be toggled by other view models that are using this usercontrol
public static readonly DependencyProperty KeyboardAlphaModeProperty = DependencyProperty.Register("KeyboardAlphaMode",
typeof(UIKeyboardAlphaMode), typeof(AlphaNumericKeyboardView),
new FrameworkPropertyMetadata(UIKeyboardAlphaMode.LowerCase, new PropertyChangedCallback(KeyboardAlphaModeCallBack)));
private static void KeyboardAlphaModeCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e){ ... }
But, when I attempt to bind to this property from another view, the callback was never fired ..
<k:AlphaNumericKeyboardView x:Name="alphaNumericKeyboard" KeyboardAlphaMode="{Binding KeyboardAlphaMode, UpdateSourceTrigger=PropertyChanged}">
</k:AlphaNumericKeyboardView>
What am I missing here? a setter? trigger?
Or this is just a thought, can a usercontrol that has dependency be bound to a viewmodel? or does it have to be bound to itself?
Edit - 10/10/2014 # 1:31pm
After rethinking the entire solution i came up with the following scenario for my problem.
I binded the Dependency Property to the view's viewmodel and let the viewmodels interact with each other instead having other viewmodel talking to this specific view ...
Here's the code for that ..
Binding alphaModeBinding = new Binding("KeyboardAlphaMode")
{
Mode = BindingMode.TwoWay,
TargetNullValue = UIKeyboardAlphaMode.LowerCase,
FallbackValue = UIKeyboardAlphaMode.LowerCase
};
this.SetBinding(KeyboardAlphaModeProperty, alphaModeBinding);
I also made the dependency property protected so no one else can access it.
Unless there is a better way to track property changes, i'm sticking with this for now.
Again, not sure this is the best solution but it gets the job done.
Try Mode=TwoWay on the binding.

Custom UserControl "IsEnabled" data binding not working

I have a kinda awful problem with my WPF application right now...
I have a custom UserControl used to edit details of a component. It should start by being not enabled, and become enabled as soon as the user chose a component to edit.
The problem is: the IsEnabled property does not even change.
Here is my code:
<my:UcComponentEditor Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
IsEnabled="{Binding EditorEnabled}"
DataContext="{Binding VmComponent}" />
EditorEnabled is a property in my ViewModel (VmComponent), and is by default false, becomes true when the user chose a component or created one
Just for the record, in my ViewModel:
private Boolean _editorEnabled = false;
public Boolean EditorEnabled
{
get { return _editorEnabled; }
set
{
_editorEnabled = value;
OnPropertyChanged("EditorEnabled");
}
}
When I try to launch my app, the UserControl is starting... enabled.
I added breakpoints everywhere, the EditorEnabled is false from the beginning.
I also did a horribly stupid thing to try to figure out what's happening: I created a converter (so useful -- converting a boolean to boolean -- eh), put a breakpoint on it, and... The code is never reached.
<my:UcComponentEditor Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
IsEnabled="{Binding EditorEnabled, Converter={StaticResource BoolConverter}}"
DataContext="{Binding VmComponent}" />
That probably means that the property isEnabled is never set, since the converter is never reached.
Do you see any kind of problem there? I started working in WPF about one week ago and therefore I may have missed something essential...
Thank you very much for your time :-)
You should add a DependencyProperty for the binding to work properly. See here for more information.
Code-behind:
public static readonly DependencyProperty EditorEnabledDependencyProperty = DependencyProperty.Register("EditorEnabled", typeof(bool), typeof(UcComponentEditor), new PropertyMetadata(false));
public bool EditorEnabled
{
get { return (bool)base.GetValue(UcComponentEditor.EditorEnabledDependencyProperty); }
set { base.SetValue(UcComponentEditor.EditorEnabledDependencyProperty, value); }
}
The issue I think is that there is a binding on the DataContext property of the user control. Which means the EditorEnabled property should be a property in the VmComponent object. At least that's what my problem was.
To get around it, I specified a proper source to the binding of IsEnabled. Once I did that the control started working as expected.
Hope that helps.
Encapsulating your control in a DockPanel (for example) will remove the need for a DependencyProperty.
You can then simply do your binding with the dockpanel instead of the custom control. Setting the variable bound to IsEnabled on the Dockpanel will automatically enable or disable the items contained in the Dockpanel.

Categories