WPF mvvm View won't update when called from different ViewModel - c#

I've been trying to update a property inside a usercontrol with it's own VM.
<Label Content="{Binding Path =LabelText ,UpdateSourceTrigger=PropertyChanged }" Grid.Column="0"/>
The Label itself works when I set it's text in the VM's constructor, and also updates if I use a command within the VM.
But changing it from outside won't do anything:
private LabelTextBoxVM _testThing;
public LabelTextBoxVM TestThing
{
get => _testThing;
set
{
_testThing = value;
NotifyPropertyChanged("TestThing");
}
}
private void Update()
{
TestThing.LabelText = "Eureka";
TestThing.TextBoxText = "Wooosh";
NotifyPropertyChanged("TestThing");
}
From all I've read, calling NotifyPropertyChanged for TestThing should update the content of both Label and Textbox, but nothing happens.

#Clemens
I found the answer, provided by you actually, in another question:
wpf-mvvm-usercontrol-binding
I have to set my DataContext in the parent view, binding to a property in the parent, and delete the Datacontext inside the childVM
So:
<myViewes:UserControlName DataContext="{Binding VMProperty}/>"
And this part cannot be in my UserControls:
<UserControl.DataContext>
<viewModels:UserControlVM/>
</UserControl.DataContext>
I am guessing this was what you meant by "inheriting" the VM
Thank you!

Related

Avalonia: Binding Command Property to UserControl

So I created a custom button control, let's call it MyButton, using Avalonia. MyButton is a collection of several controls including a Avalonia.Controls.Button looking like this (MyButton.xaml):
<Border xmlns="https://github.com/avaloniaui"
.....
x:Class="myProject.myControls.MyButton">
<Button x:Name="button"
Background="Transparent"
....
BorderThickness="0">
<Panel >
.....
</Panel>
</Button>
</Border>
(Yes my custom control inherits from Avalonia.Controls.Border instead of Avalonia.Controls.UserControl)
My plan is to pass the buttons Command property (the one with the x:Name="button" attribute) further up and make it accessable via MyButton.
So when I want to use MyButton in the MainWindow.xaml I'd to be able to do the following:
<Window ... >
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<myControls:MyButton Command="{Binding MyButton_Click}"/>
</Window>
where the view model MainWindowViewModel.cs looks like this:
public partial class MainWindowViewModel : ViewModelBase
{
public void MyButton_Click()
{
// do stuff...
}
}
The way I tried to do this in MyButton.xaml.cs is the following:
public class MyButton : Border
{
private readonly Button button;
public MyButton()
{
InitializeComponent();
button = this.FindControl<Button>("button");
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public static readonly StyledProperty<ICommand> CommandProperty =
AvaloniaProperty.Register<MyButton, ICommand>(nameof(Command));
public ICommand Command
{
get { return GetValue(CommandProperty); }
set
{ // this setter is never executed as can be seen when running with debugger attached
if (button != null)
{
button.Command = value;
SetValue(CommandProperty, value);
}
else
{
Debug.WriteLine("MyButton error: unable to set Command: control not initialized!");
}
}
}
}
However when running the application and clicking the button the target method MyButton_Click is never executed. Attaching the debugger it seems like the MyButton.Command setter is never executed either, which I think would be due to incorrect Binding? (There are no binding errors or anything related to this on the debug console)
After a few hours of trial and error I found a workaround using Reflection and a custom OnClick() Eventhandler on the button element. It works but is kinda ugly and requires a static target method so my question is:
How does one properly bind a Command on a UserControl to a method contained in the ViewModel of the main Window?
Also: Could my reflection-based approach also be viable? (I assume Avalonia bindings are also based on reflection somehow?)
Do not use getters and setters for styled properties, they won't be called when property is altered via bindings, styles or animations (it's the same for WPF, UWP and Xamarin.Forms). Instead you need to either bind your nested Button's command via <Button Command="{Binding $parent[myControls:MyButton]}" /> (preferable) or subscribe to property change notification from the static constructor like the original Button does.
More on dependency properties (which work mostly the same way as StyledProperty in Avalonia): https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/dependency-properties-overview

Passing SelectedItem of a TreeView UserControl to the calling Window

I've created a dialog (Window) in which I'm using a UserControl with a TreeView. Now I need the SelectedItem of the TreeView (the Getter) in my dialog/window.
I have tried it with a DependencyProperty, but if I set a BreakPoint to the SelectedItem property of the dialog, it doesn't trigger.
My Window:
<itemViewer:ItemViewerControl DataContext="{Binding ListOfWorldItems}"
SelectedItem="{Binding SelectedWorldItem, Mode=TwoWay}"/>/>
with CodeBehind:
public WorldItem SelectedWorldItem
{
get { return selectedWorldItem; }
set
{
selectedWorldItem = value;
NotifyPropertyChanged("SelectedWorldItem");
}
}
My UserControl
<itemViewer:ItemViewerControl DataContext="{Binding ListOfWorldItems}" />
with CodeBehind:
<UserControl ... >
<UserControl.Resources>
...
</UserControl.Resources>
<Grid>
<TreeView x:Name="WorldItemsTreeView"
SelectedItemChanged="TreeView_SelectedItemChanged"
ItemsSource="{Binding}" />
</Grid>
</UserControl>
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(WorldItem), typeof(ItemViewerControl), new UIPropertyMetadata(null));
public ItemViewerControl()
{
InitializeComponent();
DataContext = this;
}
public WorldItem SelectedItem
{
get { return (WorldItem)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItem = (WorldItem)WorldItemsTreeView.SelectedItem;
}
The issue that you are running into is that you are setting the DataContext on your UserControl and then attempting to declare a binding. By default the binding will source its value from the DataContext of the element which contains the binding. In this case that is ListOfWorldItems, not the dialog. So the binding of the SelectedItem property on the UserControl actually fails (you can see this in the output window when debugging the application).
One way to resolve this is to explicitly set the source for the binding, instead of relying on the default behavior. If you change the line in your dialog to the following...
<itemViewer:ItemViewerControl DataContext="{Binding ListOfWorldItems}"
SelectedItem="{Binding SelectedWorldItem, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=Window}"/>
It should now look to your dialog as the source for the binding and correctly establish binding between your UserControl and the dialog. Be careful that any other bindings you establish between your UserControl and the dialog also explicitly establish the source, otherwise they will run into the same problem you encountered here.
It doesn't look like it is contributing to the issue but as an additional note you are setting the DataContext for your UserControl twice. Once in the constructor for the UserControl you are self-referencing and then it is overwritten when setting the DataContext in the dialog. In this case it doesn't look like it is causing a problem (aside from some minor inefficiency), but it could have unexpected side-effects if you ever changed how the DataContext for the UserControl is being set in the dialog.

WPF UserControl property binding

I'm trying to create a WPF UserControl which contains 2 buttons. I use this UserControl in a Window and apply a Window.Resource value to set the background of one button inside the user control.
Currently I have:
window.xaml
<Window.Resources>
<SolidColorBrush Color="Brown" x:Key="theBG"></SolidColorBrush>
</Window.Resources>
<theControl:TheControl
x:Name="TheControl"
buttonBG="{Binding Source={StaticResource theBG}}" />
usercontrol.xaml.cs
public SolidColorBrush buttonBG
{
get { return base.GetValue(buttonBGProperty) as SolidColorBrush; }
set { base.SetValue(buttonBGProperty, value); }
}
public static readonly DependencyProperty buttonBGProperty = DependencyProperty.Register("buttonBG", typeof(SolidColorBrush), typeof(DataPanel), null);
usercontrol.xaml
<Button ... Background="{Binding buttonBG}">
I was expecting this to work but the background is not the one I set in the window resource.
What am I doing wrong?
Background="{Binding buttonBG}"
Implies either that you changed the DataContext of the UserControl, which you should never do. Or that the binding is just wrong.
Use
Background="{Binding buttonBG, ElementName=control}"
Naming your UserControl root element control. RelativeSource works as well.
Try placing it in a separate model or even a viewmodel that has INotifyPropertyChanged. When you add view code in the cs for an xaml file, you need to bind with relativesource self and its hacky and goes against MVVM. I would create a seperate ViewModel with a Brush that has NotifyPropertyChanged baked into it. This will tell the UI to change everything its bound to on value change.
In the Window, bind your viewmodel to datacontext. In the viewmodel you can put:
private Brush _bgColor;
public Brush BgColor
{
get{return _bgColor;
}
set
{
_bgColor = value;
OnPropertyChanged("BgColor");
}
Create an ICommand, and bind your button to it like this in the viewmodel:
ICommand ChangeBgColor {get;set;
And in the XAML for the Button:
Command="{Binding Path=DataContext.ChangeBgColor,RelativeSource={RelativeSorce Mode=FindAncestor,AncestorType={x:Type UserControl}}"
This will fire the ICommand, bound to the viewmodel that is the datacontex of the window that you are working with.
And in that code for the ICommand change out your colors, you could do it like this:
private void OnChangeBgColor(object param){
var bc = new BrushConverter();
BgColor = (Brush)bc.ConvertFrom("#fff");
}
With the MVVM pattern, you want to get away from putting unnecessary code in the xaml.cs files and start putting them into viewmodels and models.

UserControl DataContext Binding

I have three projects in my solution:
My main WPF Application which contains a MainWindow + MainViewModel
UserControl Library with a UserControl (ConfigEditorView)
UIProcess class with the ViewModel for the UserControl (ConfigEditorViewModel)
In my MainWindow I want to use the UserControl with the ViewModel of UIProcess.
First I set the UserControl in my MainWindow:
<TabItem Header="Editor">
<Grid>
<cel:ConfigEditorView DataContext="{Binding ConfEditModel, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</TabItem>
I don't know which of these properties I need here, so I put all together but it still doesn't work.
Then I've set this in my MainViewModel:
public ConfigEditorViewModel ConfEditModel { get; set; }
With simple method that is bound to a Button:
private void doSomething()
{
ConfEditModel = new ConfigEditorViewModel("Hello World");
}
My ConfigEditorViewModel looks basically like this:
public class ConfigEditorViewModel : ViewModelBase
{
private string _Description;
public string Description
{
get
{
return _Description;
}
set
{
_Description = value;
base.RaisePropertyChanged();
}
}
public ConfigEditorViewModel(string t)
{
Description = t;
}
}
The description is bound to a TextBox in my UserControl.
<TextBox Grid.Row="1" Grid.Column="1" Margin="0,0,0,10" Text="{Binding Description}"/>
When I start the application and click the Button the TextBox should contain "Hello World" but it's empty.
What I've done wrong?
i gave you a general answer:
within a "real(a usercontrol you wanna use with different viewmodels with different property names)" usercontrol you bind just to your own DependencyProperties and you do that with ElementName or RelativeSource binding and you should never set the DataContext within a UserControl.
<UserControl x:Name="myRealUC" x:class="MyUserControl">
<TextBox Text="{Binding ElementName=myRealUC, Path=MyOwnDPIDeclaredInMyUc, Path=TwoWay}"/>
<UserControl>
if you do that you can easily use this Usercontrol in any view like:
<myControls:MyUserControl MyOwnDPIDeclaredInMyUc="{Binding MyPropertyInMyViewmodel}"/>
and for completeness: the Dependency Property
public readonly static DependencyProperty MyOwnDPIDeclaredInMyUcProperty = DependencyProperty.Register(
"MyOwnDPIDeclaredInMyUc", typeof(string), typeof(MyUserControl), new PropertyMetadata(""));
public bool MyOwnDPIDeclaredInMyUc
{
get { return (string)GetValue(MyOwnDPIDeclaredInMyUcProperty); }
set { SetValue(MyOwnDPIDeclaredInMyUcProperty, value); }
}
Your view models (and, optionally, models) need to implement INotifyPropertyChanged.
Binding's aren't magic. There is no inbuilt mechanism that allows for code to be notified when a plain old property's value changes. You'd have to poll it in order to check to see if a change happened, which would be very bad, performance-wise.
So bindings will look at the objects they are bound against and see if they implement INotifyPropertyChanged and, if so, will subscribe to the PropertyChanged event. That way, when you change a property and fire the event, the binding is notified and updates the UI.
Be warned, you must implement the interface and use it correctly. This example says it's for 2010, but it works fine.

How to group checkboxes in treeview wpf mvvm when selection range is [0,1]

I have made a tree View in wpf Using MVVM .
it is working fine but here is one problem that leaf node contains some checkboxes and user have only two options either to select one or none .
So here how i can restricted user to select maximum only one cold drink.
I did one trick but it didn't work that when i have already selected a drink and then i select another one than i set the last selected value in the observable collection to false but it doesn't affect on view and selected check boxes remains selected although in collection only one option's value is true.
I cant use radio button instedof checkbox becasue user can select none of the options and i cant give an additional option for none of the above.
If any one have any solution so please let me know I'll be very thankful.
updated question:
i think i didn't define my problem in a proper way so i am giving my code snipperts here hope by this i'll get the solution o f my problem...
My View Model Class
namespace TestViewModels
{
public class ViewModel :ViewModelBase
{
private ObservableCollection<AvailableProducts> _MyTreeViewProperty
public ObservableCollection<AvailableProducts> MyTreeViewProperty
{
get { return _MyTreeViewProperty
set { _MyTreeViewProperty value;
RaisePropertyChanged("MyTreeViewProperty");}
}
}
public class AvailableProducts
{
private string _BrandName;
public string BrandName
{
get { return _BrandName
set { _BrandName = value; }
}
private bool _IsExpanded;
public bool IsExpanded
{
get
{
return _IsExpanded;
}
set
{
_IsExpanded = value;
}
}
private ObservableCollection<ProductTypes> _MyProductTypes
public ObservableCollection<ProductTypes> MyProductTypes
{
get { return _MyProductTypes}
set { _MyProductTypes= value; }
}
}
public class ProductTypes
{
private string _ProductTypeName;
public string ProductTypeName
{
get { return _ProductTypeName;
set { _ProductTypeNamevalue; }
}
private ObservableCollection<ProductSubTypes> _ProdSubTypes;
public ObservableCollection<ProductSubTypes> ProdSubTypes
{
get { return _ProdSubTypes;}
set { _ProdSubTypes;= value; }
}
}
public class ProductSubTypes
{
private string _ProductSubTypeName;
public string ProductSubTypeName
{
get { return _ProductSubTypeName;
set { _ProductSubTypeName;}
}
private int _ParentID;
public int ParentID
{
get { return _ParentID;}
set { _ParentID;= value; }
}
private bool _IsAssigned;
public bool IsAssigned
{
get { return _IsAssigned; }
set
{
_IsAssigned = value;
if _ParentID;!= 0)
{
//updating data in database
//Calling and setting new collection value in property
//issue : updated collection sets in setter of MyTreeViewProperty but before calling getter
// it comes to IsAssigned getter so view doesnt get updated collection of MyTreeViewProperty
}
RaisePropertyChanged("IsAssigned");
}
}
}
}
View
<Page x:Class="ShiftManagerViews.Pages.ProductTreeSelection
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
DataContext="{Binding ProductsTree, Source={StaticResource Locator}}"
mc:Ignorable="d" Width="870" Height="665"
>
<TreeView Margin="10,10,0,13" ItemsSource="{Binding MyTreeViewProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="800" Height="Auto" MinHeight="400" MaxHeight="800">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:AvailableProducts}"
ItemsSource="{Binding MyProductTypes}">
<WrapPanel>
<Image Width="20" Height="20" Source="/ShiftManagerViews;component/Images/12.bmp"/>
<Label Content="{Binding BrandName}" FontSize="14"/>
</WrapPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:ProductTypes}"
ItemsSource="{Binding ProdSubTypes}">
<WrapPanel>
<Image Width="18" Height="15" Source="/ShiftManagerViews;component/Images/12.bmp"/>
<Label Content="{Binding ProductTypeName}" FontSize="13"/>
</WrapPanel>
</HierarchicalDataTemplate>
<!-- the template for showing the Leaf node's properties-->
<DataTemplate DataType="{x:Type local:ProductSubTypes}">
<StackPanel>
<CheckBox IsChecked="{Binding IsAssigned, Mode=TwoWay}" Content="{Binding ProductSubTypeName}" Height="25">
</CheckBox>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
What about using a ListBox to display sub-items instead of a TreeView? You can style that so the items contain a CheckBox to show IsSelected instead of highlighting the item.
I'd suggest your user interface is wrong. If the user can only pick one then it would be better to swap these for radio buttons and add a "None of the above" option. That'll then give you the behaviour you want for free and your UI will be more intuitive.
EDIT: Since you say you can't add a "None" option and want to use a checkbox (even though I strongly disagree on checkboxes where a radio button is more appropriate - a common UI error)...
The technical problem you are probably facing is that an ObservableCollection only raises notification events if the collection itself changes. i.e. Only if items are added or removed. It does not raised events when items within the collection change, therefore the changing the status of the checkbox in the code will not raise the event for the UI binding to act on.
One solution to this to write a custom class that extends ObservableCollection that does provide this behaviour
From MSDN:
If you need to know if someone has changed a property of one of the
items within the collection, you'll need to ensure that the items in
the collection implement the INotifyPropertyChanged interface, and
you'll need to manually attach property changed event handlers for
those objects. No matter how you change properties of objects within
the collection, the collection's PropertyChanged event will not fire.
As a matter of fact, the ObservableCollection's PropertyChanged event
handler is protected—you can't even react to it unless you inherit
from the class and expose it yourself. You could, of course, handle
the PropertyChanged event for each item within the collection from
your inherited collection
I upvoted Rachel's answer, it is a common way in WPF to databind sets of radio buttons or check boxes. If you still want to go the tree view way, below code works. All view related code is in the view, so below code follows MVVM principles. If you are a MVVM purist you can put the code behind and a TreeView control in a user control if you do not want any code behind.
XAML:
<TreeView ItemsSource="{Binding Path=Drinks}">
<TreeView.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding .}" Checked="OnCheckBoxChecked" Unchecked="OnCheckBoxUnchecked" Loaded="OnCheckBoxLoaded" />
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Code behind + VM:
public partial class Window1
{
public Window1()
{
InitializeComponent();
DataContext = new VM();
}
private void OnCheckBoxChecked(object sender, System.Windows.RoutedEventArgs e)
{
foreach (CheckBox checkBox in _checkBoxes.Where(cb => cb != sender))
{
checkBox.IsChecked = false;
}
(DataContext as VM).CurrentDrink = (sender as CheckBox).Content.ToString();
}
private void OnCheckBoxUnchecked(object sender, System.Windows.RoutedEventArgs e)
{
(DataContext as VM).CurrentDrink = null;
}
private void OnCheckBoxLoaded(object sender, System.Windows.RoutedEventArgs e)
{
_checkBoxes.Add(sender as CheckBox);
}
private List<CheckBox> _checkBoxes = new List<CheckBox>();
}
public class VM
{
public List<string> Drinks
{
get
{
return new List<string>() { "Coffee", "Tea", "Juice" };
}
}
public string CurrentDrink { get; set; }
}
I did one trick but it didn't work that when i have already selected a
drink and then i select another one than i set the last selected value
in the observable collection to false but it doesn't affect on view
and selected check boxes remains selected although in collection only
one option's value is true.
Make sure that your child objects (AvailableProducts
and SubProductTypes) also implement INotifyPropertyChanged, this will make sure that the UI receives changes when modify the object.
Once all of you objects update the UI properly you will be able to layer in, and test, whatever custom business logic you need.
So if you have a product type that can only have one sub chosen, you could add a property on ProductType called OnlyAllowOneChild. Whenever, a child object raises a IsAssigned changed event, the parent can set false all other children. This of course requires you to have the parent either register for the children's PropertyChangedEvent, or got grab an EventAggregator (MVVMLight Messenger, or PRISM EvenAggregator) and create a messaging system.
Finally i am succeeded to solve my problem.
on Is Assigned property i am updating my database values and calling a method in view using MVVM Light messaging and passing currently selected leaf's parent id in it as a parameter...
Added a property in class Product Types to expand the parent node of the last selected leaf..
In view's method i am refreshing data context's source and passing currently selected leaf's parent id tO the VM to set its Is Expanded property value to true...
By this my view is working perfectly as same as i want...
If any body have solution better than this than I'll be happy to know.

Categories