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 :)
Related
I've created an application that need a lot of access to UI controls, now what I did firstly is create an interface scalable, in particular I created different controls as UserControl and one class ViewModel that manage all method of this control for update the UI. Actually all working good in the Main thread. In particular the followin scenario working perfect:
MainWindow XAML
xmlns:MyControls="clr-namespace:HeavyAPP"
...
<!-- I use the control in the following way: -->
<Grid>
<MyControls:Scheduler x:Name="Sc"/>
</Grid>
so for example the Scheduler control contains this Data Binding:
<StackPanel Orientation="Horizontal">
<Label x:Name="NextSync" Content="{Binding NextSynchronization, IsAsync=True}" ></Label>
</StackPanel>
ViewModel structure
public class ViewModelClass : INotifyPropertyChanged
{
private CScheduler scheduler;
public ViewModelClass()
{
scheduler = new Scheduler();
}
public string NextSynchronization
{
get
{
return scheduler.GetNextSync();
}
}
}
How you can see in the ViewModel I've an instance of the Scheduler control and a property called NextSyncrhonization as the binding, so this property return a result from the method of the control instance.
For use this in the MainWindow I did the following:
public MainWindow()
{
ViewModelClass viewModel = new ViewModelClass();
DataContext = viewModel;
}
this automatically fill the control property. Now the problem's that I use a BackgroundWorker for perform some task, what I need is use the DataContext of MainWindow from different classes, (not Window, but classes).
For solve this situation I though to do something like this:
MainWindow.AppWindow.Sc.SyncLog.Dispatcher.Invoke(
new Action(() =>
{
ViewModelClass viewModel = new ViewModelClass();
var dataContext = System.Windows.Application.Current.MainWindow.DataContext;
dataContext = viewModel;
viewModel.SynchronizationLog = "this is a test from other thread"}));
now SynchronizationLog is another property that append the text to the Control, just for precision, is this:
private string _text;
public string SynchronizationLog
{
get
{
return _text += _text;
}
set
{
_text = value;
OnPropertyChanged();
}
}
this is the implementation of INotifyPropertyChanged:
`public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}`
this working only in the MainWindow, but in the external classes I can't update the UI, what am I doing wrong?
I reiceve no error, anyway.
Try the following:
extend your ViewModel as follow:
public class ViewModelClass : INotifyPropertyChanged
{
private CScheduler scheduler;
//Add this:
public static ViewModelClass Instance {get; set;} //NEW
public ViewModelClass()
{
scheduler = new Scheduler();
}
public string NextSynchronization
{
get
{
return scheduler.GetNextSync();
}
}
}
This changes your code in the xaml.cs to:
public MainWindow()
{
ViewModelClass.Instance = new ViewModelClass();
DataContext = viewModel.Instance;
}
In your external code you then DONT create a new Instance of the ViewModelClass - instead you use the existing one:
[...].Dispatcher.Invoke(() =>
{
if(ViewModelClass.Instance != null)
{
//Why you need the "var datacontext" in your example here ?
ViewModelClass.Instance.SynchronizationLog = "this is a test from other thread"
}
}));
Basically what you do here is setting a property in your ViewModel from outside of your viewModel. This can be done from everywhere.
What is different to your approach:
We dont create a new Instance of the ViewModel (different bindings in the UI aren't resetted anymore)
We created an Instance so there can always be ONLY ONE viewModel at a time
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}"/>
Using the Prism Library I've setup a Model inheriting BindableBase (INotiftyPropertyChanged).
Then In the ViewModel, in the constructor I create a new object of my ReadConnections class which will return a List of connection names from a local XML file.
Then I set the Connections property from the method GetIdNode in the ReadConnections class.
Finally I bind the Listbox ItemSource to the Connections property. When I run the application the Listbox is not populated with any listbox items. I'm not sure if binding ItemSource is correct, I've never bound anything to a listbox before.
Model Class:
public class LoginDialogModel : BindableBase
{
private List<string> _connections;
public List<string> Connections
{
get { return _connections; }
set { _connections = value; }
}
}
ViewModel Class Constructor:
public LoginDialogVM()
{
ReadConnections read = new ReadConnections();
LoginModel.Connections = read.GetIdNodes();
}
XAML:
<ListBox HorizontalAlignment="Left" Height="247" Margin="10,10,0,0"
VerticalAlignment="Top" Width="135" ItemsSource="{Binding LoginModel.Connections}"/>
Your propery (with BindableBase inheriting) should looks like this
public class LoginDialogModel : BindableBase
{
private List<string> _connections;
public List<string> Connections
{
get { return _connections; }
set { SetProperty(ref _connections,value); }
}
}
And as you see, SetProperty method takes care about NotifyProperty
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'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