I'm creating a UserControl with a DependencyProperty but the DependencyProperty isn't getting the value that the caller is passing in.
I've discovered the following during my own investigation
If I use a built-in user control, such as TextBlock, everything works. This narrows the problem down to my UserControl's implementation (as opposed to the code that calls the UserControl)
My property changed callback that I register isn't even being called (well... at least the breakpoint isn't being hit)
If only see this problem when I use a binding to provide the dependency property, so this doesn't work:
<common:MyUserControl MyDP="{Binding MyValue}"/>
but I have no problems if I get rid of the binding and hardcode the value, so this works:
<common:MyUserControl MyDP="hardCodedValue"/>
Here's my UserControl's code behind:
public partial class MyUserControl : UserControl
{
public string MyDP
{
get { return (string)GetValue(MyDPProperty); }
set { SetValue(MyDPProperty, value); }
}
public static readonly DependencyProperty MyDPProperty =
DependencyProperty.Register(
"MyDP",
typeof(string),
typeof(MyUserControl),
new FrameworkPropertyMetadata(
"this is the default value",
new PropertyChangedCallback(MyUserControl.MyDPPropertyChanged)));
public static void MyDPPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((MyUserControl)obj).MyDP = (string)e.NewValue;
}
public MyUserControl()
{
InitializeComponent();
this.DataContext = this;
}
}
And here's the xaml
<Grid>
<TextBlock Text="{Binding MyDP}"/>
</Grid>
Since I'm able to use built-in user controls such as TextBlock, I don't think that the error lies in my host code, but here it is, just so that you have a complete picture:
<StackPanel>
<common:MyUserControl MyDP="{Binding MyValue}"/>
</StackPanel>
public class MainWindowViewModel
{
public string MyValue { get { return "this is the real value."; } }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
This line in the UserControl is wrong:
this.DataContext = this;
This makes the UserControl its own DataContext, so the binding is looking for a property called MyValue on the UserControl, and that property does not exist. You want the DataContext to be your view-model. If you don't set it explicitly, it will inherit the DataContext from its container (the Window in this case).
Delete that line, and you'll be closer. You also don't need that callback; remove that too.
You can update your control's view code like that:
<Grid>
<TextBlock x:Name="_textBlock"/>
</Grid>
And set a _textBlock's text property in MyDPPropertyChanged method:
public static void MyDPPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var control = ((MyUserControl)obj);
control.MyDP = (string)e.NewValue;
control._textBlock.Text = control.MyDP;
}
That will do the trick.
Kindly Implement INotifyPropertyChanged, and PropertyChangedEventHandler in side the control and also the viewmodel. secondly use SetCurrentValue method to set the value inside the control class rather setting it directly
Related
I have this user control codebehind:
public partial class MyControl : UserControl, INotifyPropertyChanged
{
public string MyProperty
{
get => (string)GetValue(MyPropertyProperty);
set
{
SetValue(MyPropertyProperty, value);
PropertyChanged?.Invoke(null, null);
}
}
public event PropertyChangedEventHandler PropertyChanged;
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register(nameof(MyProperty), typeof(string), typeof(string));
public MyControl()
{
InitializeComponent();
}
}
XAML:
<Grid>
<TextBox Text="{Binding MyProperty,
Mode=OneWayToSource,
UpdateSourceTrigger=PropertyChanged,
RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}"
VerticalAlignment="Center"
Height="20"
Margin="5"/>
</Grid>
I have one of those controls in my MainWindow and when I put a breakpoint on the "SetValue" line and change the value of the TextBox the breakpoint is hit and everything is right with the world. If I change the DP registering to:
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register(nameof(MyProperty), typeof(string), typeof(MyControl));
That breakpoint is no longer hit even though nothing else has been changed and afaik this is the better, correct way to register the DP.
Why is this happening and how to fix it?
Why is this happening and how to fix it?
Because you are violating WPF conventions.
And the XAML constructor (compiler) cannot guess how you are breaking this convention.
In fact, in the first option, you create a regular CLR property.
And, if you try to set a binding for it, you will most likely get a compilation error.
By convention, by which the XAML compiler works, the CLR wrapper should not contain ANY LOGIC, except for calling GetValue () and SetValue () and the owner of the property should be the class in which they are declared.
In this case, the compiler can find the original DependecyProperty and when setting / getting values it will use it without calling the CLR wrapper of the property.
And the CLR wrapper is designed for the convenience of the programmer when "hand-coding" in Sharp.
The INotifyPropertyChanged interface also looks completely pointless.
DependecyProperty has its own change notification mechanism and you don't need to duplicate it by calling PropertyChanged.
If you need to track changes in the DependecyProperty value, you must do this in the callback method specified when declaring DependecyProperty.
public partial class MyControl : UserControl
{
public string MyProperty
{
get => (string)GetValue(MyPropertyProperty);
set => SetValue(MyPropertyProperty, value);
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register(nameof(MyProperty), typeof(string), typeof(MyControl), new PropertyMetadata(string.Empty, MyPropertyChanged));
private static void MyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Here the logic for handling value change
MyControl myControl = (MyControl)d;
// Some Code
}
public MyControl()
{
InitializeComponent();
}
}
How would I achieve the same with the MyPropertyChanged callback?
This is very wrong, but if you need to, you can raise PropertyChanged in the callback method as well.
public partial class MyControl : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string MyProperty
{
get => (string)GetValue(MyPropertyProperty);
set => SetValue(MyPropertyProperty, value);
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register(nameof(MyProperty), typeof(string), typeof(MyControl), new PropertyMetadata(string.Empty, MyPropertyChanged));
private static readonly PropertyChangedEventArgs MyPropertyPropertyChangedArgs = new PropertyChangedEventArgs(nameof(MyProperty));
private static void MyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Here the logic for handling value change
MyControl myControl = (MyControl)d;
myControl.PropertyChanged?.Invoke(myControl, MyPropertyPropertyChangedArgs);
// Some Code
}
public MyControl()
{
InitializeComponent();
}
}
What would be the proper way to notify the ViewModel of the window the user control is in that the value of "MyProperty" has changed when the user types text in the TextBox?
If the VM needs to get the changed value of the MyProperty property, you need to set a binding to this property at the place where your control is used.
And already in the VM itself, process the change in its properties.
Usually, there is a corresponding method in a base class implementation for a VM.
But if there is no such method, then you can use the setter of this property.
Example:
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register(
nameof(MyProperty),
typeof(string),
typeof(MyControl),
new FrameworkPropertyMetadata(string.Empty, MyPropertyChanged) { BindsTwoWayByDefault = true });
<local:MyControl MyProperty="{Binding VmProperty}" .../>
public class MainViewModel: ....
{
public string VmProperty
{
get => _vmProperty;
set
{
if(Set(ref _vmProperty, value))
{
// Some code to handle value change.
}
}
}
}
Additional advice.
Your given implementation is not very suitable for UserControl.
You'd better change it to Custom Control.
I am trying to solve this issue for so many hours:
I have user custom control of grid named NewMazeGrid and I want to use it as a control in MainWindow. MainWindow contains MazeViewModel(mazeVM member).
I'm trying to set the values of the grid, when the property MazeViewModel:MySingleplay changes.
(I'm using the INotifyPropertyChanged for it, and it works perfectly fine. I guess, the problem is in the final binding)
The code:
This is the property MazeViewModel:MySingleplay getter:
public string MySingleplay
{
get
{
if (myModel.MySingleplay == null)
{
return "";
} else
{
return myModel.MySingleplay.ToString();//works perfect
}
}
}
this is the NewMazeGrid.xaml.cs:
namespace VisualClient.View.controls
{
public partial class NewMazeGrid : UserControl
{
private MazePresentation myMaze;
private string order; //dont really use it
//Register Dependency Property
public static readonly DependencyProperty orderDependency =
DependencyProperty.Register("Order", typeof(string), typeof(NewMazeGrid));
public NewMazeGrid()
{
myMaze = new MazePresentation();
InitializeComponent();
DataContext = this;
lst.ItemsSource = myMaze.MazePuzzleLists;
}
public string Order
{
get
{
return (string)GetValue(orderDependency);
}
set
{
SetValue(orderDependency, value);
myMaze.setPresentation(value); //(parsing string into matrix)
}
}
}
}
this is the MainWindow.xaml.cs:
public partial class MainWindow : Window
{
private MazeViewModel mazeVM;
public MainWindow()
{
InitializeComponent();
mazeVM = new MazeViewModel(new ClientMazeModel(new TCPClientConnection()));
DataContext = mazeVM;
mazeVM.connectToServer();
}
private void bu_Click(object sender, RoutedEventArgs e)
{
bool isC = mazeVM.isConnected();
mazeVM.openSingleplayGame("NewMaze");//works perfect
}
this is the MainWindow.xaml:
<Window x:Class="VisualClient.View.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:Controls ="clr-namespace:VisualClient.View.controls"
xmlns:vm ="clr-namespace:VisualClient.ViewModel"
xmlns:local="clr-namespace:VisualClient.View"
mc:Ignorable="d"
Title="Main Window" Height="350" Width="525" MinWidth="900" MinHeight="600">
<WrapPanel >
<Button Name ="bu" Content="Click_Me" Click="bu_Click"/>
<Grid Name="myGrid">
<Controls:NewMazeGrid Order="{Binding MySingleplay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</WrapPanel>
</Window>
I get this error on the binding line: Value cannot be null.
To sum:
It initialize fine the window in the ctor, but when the property changes it does not get into the Order property setter. therefor my grid never changes.
What should be the right syntax for binding in this case? how do I bind it to the right property?
Folders hierarchy explorer
WPF may not call the CLR wrapper of a dependency property, but just directly call the GetValue and SetValue methods of the underlying DependencyObject. This is why there should not be any logic except the GetValue and SetValue calls.
This is explained in XAML Loading and Dependency Properties:
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.
Similarly, other aspects of the XAML processor that obtain property
values from XAML processing also use GetValue rather than using the
wrapper. Therefore, you should also avoid any additional
implementation in the get definition beyond the GetValue call.
To get notified about property value changes, you can register a PropertyChangedCallback by property metadata. Note also that there is a naming convention for DependencyProperty fields. Yours should be called OrderProperty:
public static readonly DependencyProperty OrderProperty =
DependencyProperty.Register(
"Order", typeof(string), typeof(NewMazeGrid),
new PropertyMetadata(OnOrderChanged));
public string Order
{
get { return (string)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
private static void OnOrderChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((NewMazeGrid)obj).myMaze.setPresentation((string)e.NewValue);
}
Besides that, you must not set
DataContext = this;
in the constructor of NewMazeGrid. This effectively prevents inheriting the DataContext from the parent window, so that {Binding MySingleplay} won't work. Except under special circumstances you should never explicitly set a UserControl's DataContext.
So, remove the DataContext assignment from the constructor:
public NewMazeGrid()
{
myMaze = new MazePresentation();
InitializeComponent();
lst.ItemsSource = myMaze.MazePuzzleLists;
}
That said, there is also no need to set UpdateSourceTrigger=PropertyChanged on a one-way binding. It only has an effect in two-way (or one-way-to-source) bindings:
<Controls:NewMazeGrid Order="{Binding MySingleplay}"/>
Well, I am doing a small project and I found it wasn't necessary to implemente a full MVVM.
I am trying to bind some properties in code behind, but cannot manage to make it works.
The point is the use of DependencyProperties and Binding in code behind.
I tried to follow these links and questions in SO:
Bind Dependency Property in codebehind WPF
How to: Create a Binding in Code
Bind Dependency Property, defined in Code-Behind, through Xaml to a Property in the DataContext of a UserControl
But they are related to MVVM or at least I cannot adapt the code in my case.
The example should be very simple.
MainWindow.xaml
<Label Name="_lblCurrentPath"
Style="{StaticResource CustomPathLabel}"
ToolTip="{Binding CurrentPath}"
Content="{Binding CurrentPath, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
SetBindings();
}
#region Properties
public static readonly DependencyProperty CurrentPathProperty =
DependencyProperty.Register("CurrentPath", typeof(String), typeof(MainWindow), new PropertyMetadata(String.Empty, OnCurrentPathChanged));
public string CurrentPath
{
get { return (String)GetValue(CurrentPathProperty); }
set { SetValue(CurrentPathProperty, value); }
}
#endregion
#region Bindings
private void SetBindings()
{
// Label CurrentPath binding
Binding _currentPath = new Binding("CurrentPath");
_currentPath.Source = CurrentPath;
this._lblCurrentPath.SetBinding(Label.ContentProperty, _currentPath);
}
#endregion
#region Methods
private void Refresh()
{
MessageBox.Show("Refresh!");
}
private string Search()
{
WinForms.FolderBrowserDialog dialog = new WinForms.FolderBrowserDialog();
WinForms.DialogResult _dResult = dialog.ShowDialog();
switch(_dResult)
{
case WinForms.DialogResult.OK:
CurrentPath = dialog.SelectedPath;
break;
default:
break;
}
return CurrentPath;
}
#endregion
#region Events
private static void OnCurrentPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MainWindow instance = d as MainWindow;
instance.Refresh();
}
public void OpenSearchEclipsePath(object sender, RoutedEventArgs e)
{
CurrentPath = Search();
}
public void RefreshEclipsePath(object sender, RoutedEventArgs e)
{
Refresh();
}
Any idea?
.If this is a bad practice and I should use MVVM comments are welcome, of ourse.
.Also... Related to Command property. In this case where I don't want to use a MVVM approach, is it better to register events? I found the use of custom command bindings a little bit tedious.
First, you can totally use bindings without MVVM. I wouldn't reccommend it, as the code is a lot cleaner when you use MVVM, but it can be done. All you need to do is put this line in your constructor:
this.DataContext = this;
Now your view is also your view model! Like I said, not a good idea.
Now, the code you have has a DependencyProperty in your MainWindow class. Don't do that. It serves absolutely no purpose. DPs are there so parent controls can give a binding to them. MainWindow has no parent; so a DP is useless.
All you need to do is set up a regular property:
public string CurrentPath
{
get { return currentPath; }
set
{
currentPath = value;
NotifyPropertyChanged();
}
}
And then have MainWindow implement INotifyPropertyChanged (did I mention that it makes more sense to use a simple view model?).
To answer your Command question. Yes, if you are opposed to using commands, just register for the events. However, Command is a really nice way to get user clicks into the view model without breaking MVVM. The syntax isn't that bad. If you are going the "View as a View Model" approach anyways though, Command doesn't buy you much.
I'm creating a UserControl "UC". I have my class with data "AClass". I want an object of this class to be a DP in my UserControl. So i put the definition in UC:
public static readonly DependencyProperty AProperty =
DependencyProperty.Register("A", typeof(AClass),
typeof(UC), new FrameworkPropertyMetadata(new AClass()));
public AClass A
{
get { return (AClass)GetValue(AProperty); }
set { SetValue(AProperty, value); }
}
Here's how I create my control in XAML:
xmlns:l="clr-namespace:MyWorkspace"
// ...
<Grid>
<l:UC Height="100" Width="150" Activity="{Binding a}" />
</Grid>
I defined "a" in the code-behind file:
public partial class MainWindow : Window
{
public AClass a {get; set;}
public MainWindow()
{
DataContext = this;
a = // create an object
InitializeComponent();
}
}
Next, in my UC I want to refer to "A" and use one of its property:
private void DoSomethingInUC()
{
int size = A.AsProperty;
// ...
}
The problem is that every time I refer to "A" it is uninitialized (i.e. "AsProperty" contains default value).
What's wrong in that ?
1) How can your A DP be of type double and AClass?
2) You most likely want to avoid doing this :
new FrameworkPropertyMetadata(new AClass())
because the exact AClass instance you new up right there will be shared
as default by all of your UserControl instances.
3) You have to make your MainWindow implement INotifyPropertyChanged (prefer making
a MainWindowViewModel) and have your a property RaisePropertyChanged inside it's setter.
I obviously don't get this somewhere.
I have created a UserControl, the bare bones of which is:
private readonly DependencyProperty SaveCommandProperty =
DependencyProperty.Register("SaveCommand", typeof(ICommand),
typeof(ctlToolbarEdit));
private readonly DependencyProperty IsSaveEnabledProperty =
DependencyProperty.Register("IsSaveEnabled", typeof(bool),
typeof(ctlToolbarEdit), new PropertyMetadata(
new PropertyChangedCallback(OnIsSaveEnabledChanged)));
public ctlToolbarEdit()
{
InitializeComponent();
}
public bool IsSaveEnabled
{
get { return (bool)GetValue(IsSaveEnabledProperty); }
set { SetValue(IsSaveEnabledProperty, value); }
}
public static void OnIsSaveEnabledChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
((ctlToolbarEdit)d).cmdSave.IsEnabled = (bool)e.NewValue;
}
#region Command Handlers
public ICommand SaveCommand
{
get { return (ICommand)GetValue(SaveCommandProperty); }
set { SetValue(SaveCommandProperty, value); }
}
private void cmdSave_Click(object sender, RoutedEventArgs e)
{
if (SaveCommand != null)
SaveCommand.Execute(null);
}
#endregion
Excellent. You can see what I am doing ... handling the click event of the button, and basically firing up the command.
The form (lets call that Form1 for the time being ... but note that this is actually a UserControl: common practice, I believe, in MVVM) that is hosting the control has the following line:
<ctl:ctlToolbarEdit HorizontalAlignment="Right" Grid.Row="1"
SaveCommand="{Binding Save}" IsSaveEnabled="{Binding IsValid}" />
This works great. I have an ICommand in my ViewModel called 'Save' and the ViewModel is correctly presenting the IsValid property.
So far so very good.
Now I want to have my new usercontrol also on Form2 (which is also a usercontrol - common practice, I believe, on MVVM). As it happens, Form1 and Form2 are on the screen at the same time.
It compiles, but I get a runtime exception:
'SaveCommand' property was already registered by 'ctlToolbarEdit'."
... leading me to believe that I don't get 'commands' at all.
Why can I not use my usercontrol in more than one place?
If I cannot, what would you suggest is another way to do this?
Very frustrating!
Thanks for any help.
Try making your dependency properties static. Otherwise it is getting re-registered every time you instantiate a new control. Your usage of the MVVM commands looks good otherwise and sounds like you have a good grasp on it.