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.
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.
In my MainWindow VM i open the Views from my UserControls like this.
Usercontrol1 is the name of the View made in Xaml.
In my ViewModel of my MainWindow:
private static Grid _myMainGrid;
public static Grid MyMainGrid
{
get { return _myMainGrid; }
set { _myMainGrid = value; }
}
private void OpenUserControl(UserControl myUS)
{
if (MyMainGrid.Children.Count > 0)
{
MyMainGrid.Children.RemoveAt(0);
}
Grid.SetColumn(myUS, 1);
Grid.SetRow(myUS, 0);
MyMainGrid.Children.Add(myUS);
}
private void FindGrid(object obj)
{
var myGrd = obj as Grid;
if (myGrd != null)
{
MyMainGrid = myGrd;
}
}
The command binding to the Button executes this.
private void ExecuteCMD_OpenUserControl1(object obj)
{
FindGrid(obj);
Usercontrol1 _ucItem = new Usercontrol1();
OpenUserControl(_ucItem);
}
Now i want to open Usercontrol2 replacing Usercontrol1 in MyMainGrid from my MainWindow by pressing a button in Usercontrol1. So i have to access the parent Window.
Tried using this methode but can't get it to work in my case.
Let's say you have two children; it's trivial to generalize this to any number of children. Fortunately you've already got viewmodels and views, so we're most of the way there. It's just a matter of wiring it all together in a way that works well with WPF.
Here's a set of skeletal viewmodels. Main, and two children. MainViewModel creates its two child instances. ChildOneViewModel has a Next button command, bound to a Button in ChildOneView.xaml.
When the user clicks that button, we want to switch the active view to child two. Rather than have dependencies going in all directions, ChildOneViewModel is ignorant of what "next" really means; its parent, MainViewModel, is in charge of that. Everybody knows about his children; you've found that in programming, making a class know about its siblings or its parent creates problems.
So all ChildOneViewModel does is expose an event so MainViewModel knows when the button is clicked, and can take any action it likes when that happens. This is cool because what if we could be going to one of two different "next" pages, depending on what the user did in ChildOne? If we move that responsibility to the parent, everything becomes simpler. Easier to write, easier to reuse in a different context.
#region MainViewModel Class
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
ChildOne = new ChildOneViewModel();
ChildTwo = new ChildTwoViewModel();
ActiveChild = ChildOne;
ChildOne.NextButtonClicked += (s, e) => ActiveChild = ChildTwo;
}
#region ActiveChild Property
private INotifyPropertyChanged _activeChild = default(INotifyPropertyChanged);
public INotifyPropertyChanged ActiveChild
{
get { return _activeChild; }
set
{
if (value != _activeChild)
{
_activeChild = value;
OnPropertyChanged();
}
}
}
#endregion ActiveChild Property
public ChildOneViewModel ChildOne { get; private set; }
public ChildTwoViewModel ChildTwo { get; private set; }
}
#endregion MainViewModel Class
#region ChildOneViewModel Class
public class ChildOneViewModel : ViewModelBase
{
public event EventHandler NextButtonClicked;
// You already know how to implement a command, so I'll skip that.
protected void NextButtonCommandMethod()
{
NextButtonClicked?.Invoke(this, EventArgs.Empty);
}
}
#endregion ChildOneViewModel Class
#region ChildTwoViewModel Class
public class ChildTwoViewModel : ViewModelBase
{
}
#endregion ChildTwoViewModel Class
And here's the XAML that translates all of that into actual UI at runtime:
<Window.Resources>
<!--
These are "implicit datatemplates": They have no x:Key, so they'll be
automatically used to display any content of the specified types.
-->
<DataTemplate DataType="{x:Type local:ChildOneViewModel}">
<local:ChildOneView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ChildTwoViewModel}">
<local:ChildTwoView />
</DataTemplate>
</Window.Resources>
<Grid>
<!-- other stuff -->
<UserControl
Content="{Binding ActiveChild}"
/>
<!-- other stuff -->
You don't need OpenUserControl(), FindGrid(), any of that stuff.
I don't fully understand the structure of your application and there are most probably better ways of doing whatever you are trying to do, but you could get a reference to any open window in your application using the Application.Current.Windows property if that's your main issue:
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
Inside a loaded UserControl, you should also be able to get a reference to the parent window of this UserControl like this:
var mainWindow = Window.GetWindow(this) as MainWindow;
I have two custom controls, a and b,
Custom controla has a Dependency property of type aClass
Custom control b has a set of a on it and has a Dependency property List<aClass> named ItemSourceUI
There is another class bClass that has on observable collection of type aClass, this is used on my view model.
public class MyViewModel : INotifyPropertyChanged
{
public MyViewModel()
{
MyBClassInstance = new bClass();
}
private bClass _MyBClassInstance;
public bClass MyBClassInstance
{
get { return _MyBClassInstance; }
set { SetProperty(ref _MyBClassInstance, value); }
}
....
// Here Implement INotifyPropertyChanged
}
This is my view
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:UILib="clr-namespace:Gramas.UI;assembly=Gramas" x:Class="MyProy.Views.myView"
Title="{Sample}" Height="600" Width="1200">
<Grid>
<UILIb:b Margin="10" ItemSourceUI="{Binding MyBClassInstance}"/>
</Grid>
</Window>
And at code behind constructor:
this.DataContext = new MyViewModel();
My problem is that Dependency property Setter of ItemSourceUI at b custom control never happen.
What I'm missing ?
UPDATE: This is the dependency property of b
public bClass ItemSourceUI
{
get { return (bClass)GetValue(ItemSourceUIProperty ); }
set
{
SetValue(ItemSourceUIProperty , value);
DataContext = value;
}
}
public static readonly DependencyProperty ItemSourceUIProperty =
DependencyProperty.Register("ItemSourceUI", typeof(bClass), typeof(b), new PropertyMetadata(default(bClass)));
This code causes all the issues you have. You screw up the DataContext and Binding needs proper DataContext in order to work.
public bClass ItemSourceUI
{
get { return (bClass)GetValue(ItemSourceUIProperty ); }
set
{
SetValue(ItemSourceUIProperty , value);
DataContext = value;
}
}
Remove that DataContext from setter and you should be fine.
If you have any questions about this feel free to ask :)
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
I'm working on a custom controller for a Windows 8 store app in C#.
I added some DependencyProperties; some simple ones (like Radius below) and a collection of custom items used to construct and draw various shapes (NinjaSource).
<StackPanel>
<cc:NinjaControl Margin="120,0,0,0" NinjaSource="{Binding NinjaCollection}" Radius="45"/>
</StackPanel>
The collection looks like this
public ObservableCollection<Ninja> NinjaCollection{ get; set; }
And the Ninja class basically has some properties and implements INotifyPropertyChanged
public class Ninja : INotifyPropertyChanged
{
private string _name;
private double _value;
private Path _path;
private bool _showName;
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
...
Whenever a simple property, like Radius, is changed, my custom control picks it up and redraws like this:
public sealed partial class NinjaControl: UserControl
{
public static readonly DependencyProperty RadiusProperty =
DependencyProperty.Register("Radius", typeof (double), typeof (NinjaControl),
new PropertyMetadata(default(double), PropertyChangedCallback));
...
private static void PropertyChangedCallback(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var instance = o as NinjaControl;
if (instance == null) return;
instance.RedrawMyControl();
}
This works great, I can bind Radius to whatever I want and the PropertyChangedCallback is called whenever it changes.
I want the same thing to occur whenever any values inside the NinjaCollection change.
I have a DependencyProperty registered for the actual collection, with a property wrapper, but I believe it will only look at changes to the actual collection and not the values within.
public static readonly DependencyProperty NinjaSourceProperty =
DependencyProperty.Register("NinjaSource", typeof(ObservableCollection<Ninja>), typeof(NinjaControl), new PropertyMetadata(new ObservableCollection<Ninja>(), PropertyChangedCallback));
Any help appreciated.
what you want to do is instead of exposing ObservableCollection as NinjaSource. Create a custom object that contains the observable collection. Expose specific Add Remove methods which will allow you to then raise events.