PropertyChanged event null after data context is set - c#

So I've tried all ways I know how to data bind, but I can't seem to get my property changed event to bind properly
I have a simple user control with the code behind being the following:
public partial class EnableForms : INotifyPropertyChanged
{
private GenericViewData _thisGenericViewData;
public GenericViewData ThisGenericViewData
{
get { return _thisGenericViewData; }
set
{
_thisGenericViewData = value;
OnPropertyChanged();
}
}
public EnableForms()
{
InitializeComponent();
//DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
the view is the following XAML:
<UserControl x:Class="namespace.EnableForms"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"+
xmlns:local="clr-namespace:viewNamespace"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource self}}">
<!--d:DesignHeight="300" d:DesignWidth="300">-->
<Grid>
<TextBlock Text="{Binding Source=ThisGenericViewData}"></TextBlock>
<!-- <TextBlock Text="{Binding ThisGenericViewData, RelativeSource={RelativeSource AncestorType={x:Type local:EnableForms}}}" /> -->
</Grid>
using some old navigation logic I create the view and navigate to it thusly:
MainWindow.WindowControlHost.Navigate(new viewNamespace.EnableForms
{
ThisGenericViewData = viewData
});
I know the navigation logic works fine, and I can see that ThisGenericViewData is being set to valid data. My issue is that in my code behind, the propertychanged event is never set, it is always null.
I've tried in the code behind setting the datacontext to this (DataContext = this) but that didn't work either. I've tried doing relative binding to self in the textblock but it doesn't work either. I know it is biding to the correct source because I can right click and go to source (when using the relative binding) and it navigates to the property.
Can someone please shed some light on the situation and show me what I'm doing wrong

You should set the Path (and not the Source) property of the Binding to "ThisGenericViewData":
<TextBlock Text="{Binding Path=ThisGenericViewData}"></TextBlock>
This should work provided that you set the DataContext of the UserControl to itself:
DataContext="{Binding RelativeSource={RelativeSource self}}"
The Path specifies the name of the property to bind to and the source specifies the source object where that property is defined.

using this answer, the mention of element name the user thinks is a better way to do databinding to oneself. This is what worked for me. changing only the XAML it now looks like this
<UserControl x:Class="viewNamespace.EnableForms"
Name="EnableFormsView"
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"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
>
<!--d:DesignHeight="300" d:DesignWidth="300">-->
<Grid>
<TextBlock Text ="{Binding ThisGenericViewData, ElementName=EnableFormsView}" />
</Grid>

Related

WPF - trying to update a label from a textbox update using INotifyPropertyChanged

I have delved into the magic and mystery of WPF and Binding. It was going OK then I hit a brick wall and need to ask those much cleverer than me for help please.
I cut this back to a simple app removing all the other items in my code. The UI has a text box and a label. When the text in the textbox changes then I want to update the label. Somewhere I am missing a link and I guess it is the binding as I never seem to get into the set. Here is the code
Mainwindow.xaml.cs
using System.ComponentModel;
using System.Windows;
namespace Databinding3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string myBindedVal = "....";
public MainWindow()
{
InitializeComponent();
}
//Create properties for our variable _myBindedVal
public string MyBindedVal
{
get => myBindedVal;
set
{
NotifyPropertyChanged(nameof(MyBindedVal));
myBindedVal = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (propertyName != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Mainwindow.xml
<Window x:Class="Databinding3.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:Databinding3"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBox x:Name="txtbx_name" Text="Textbox" HorizontalAlignment="Center" Height="57" TextWrapping="Wrap" VerticalAlignment="Center" Width="594"/>
<Label Content="{Binding MyBindedVal, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" HorizontalAlignment="Center" Height="44" Grid.Row="1" VerticalAlignment="Center" Width="594"/>
</Grid>
</Window>
Thanks for your help
You did not bind the Text property of the TextBox. It should look like shown below, where the UpdateSourceTrigger ensures that the source property is updated immediately when you type into the TextBox.
<TextBox Text="{Binding MyBoundVal, UpdateSourceTrigger=PropertyChanged}" .../>
The above Binding does not explicitly specify a source object, and therefore uses the Window's DataContext as source. Set the DataContext like this:
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
The Label Binding would then just be
<Label Content="{Binding MyBoundVal}" .../>
Be aware that you would typically use a TextBlock, not a Label, to show text:
<TextBlock Text="{Binding MyBoundVal}" .../>
The execution order in the property setter is also important. Assign the backing field value before firing the PropertyChanged event.
public string MyBoundVal
{
get => myBoundVal;
set
{
myBoundVal = value;
NotifyPropertyChanged(nameof(MyBoundVal));
}
}
Finally, the NotifyPropertyChanged method should look like shown below. Testing the propertyName argument is pointless, but you should test the PropertyChanged event for null, usually by using the null-propagation operator ?.:
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

How to handle Master-Detail screen communication in WPF with MVVM architecture?

I'm trying to build my first app with WPF and in order to fully understand MVVM I'm not using any framework, the only helper I use is Microsoft.Toolkit.Mvvm
I have thi app with 2 pages, one is the master and the other one is the detail.
I did set up navigation as it's explained in WPF MVVM navigate views
Now I don't understand how I should tell to the detail screen which data it should display, since I'm not allowed to pass parameters to the viewmodel that I am instantiating in the datacontext.
My MainWindow.xaml
<Window x:Class="AlgsManagerDesktop.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:AlgsManagerDesktop"
xmlns:views="clr-namespace:AlgsManagerDesktop.Views"
xmlns:viewModel="clr-namespace:AlgsManagerDesktop.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:MasterViewModel}">
<views:MasterView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:DetailsViewModel}">
<views:DetailsView />
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<viewModel:MainWindowViewModel />
</Window.DataContext>
<Grid>
<ContentControl Content="{Binding ViewModel}" />
</Grid>
</Window>
MainWindowViewModel.cs
public class MainWindowViewModel : ObservableObject
{
private BaseViewModel viewModel;
public BaseViewModel ViewModel
{
get => viewModel;
set => SetProperty(ref viewModel, value);
}
public RelayCommand SwitchToDetailsCommand { get; }
public MainWindowViewModel()
{
ViewModel = new MasterViewModel();
SwitchToDetailsCommand = new RelayCommand(SwitchToDetails);
}
private void SwitchToDetails()
{
ViewModel = new DetailsViewModel();
}
}
MasterViewModel.cs
public class MasterViewModel : BaseViewModel
{
private ItemModel selectedItem;
public ItemModel SelectedItem
{
get => selectedItem;
set
{
SetProperty(ref selectedItem, value);
DeleteCommand.NotifyCanExecuteChanged();
}
}
public ObservableCollection<ItemModel> items { get; set; }
public RelayCommand DeleteCommand { get; }
public MasterViewModel()
{
DeleteCommand = new RelayCommand(RemoveItem, ItemIsSelected);
}
private void RemoveItems()
{
AlgSets.Remove(SelectedItem);
}
private bool ItemIsSelected()
{
return SelectedItem != null;
}
}
MasterView.xaml
<UserControl x:Class="AlgsManagerDesktop.Views.MasterView"
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"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:AlgsManagerDesktop.Views"
xmlns:viewModel="clr-namespace:AlgsManagerDesktop.ViewModel"
xmlns:root="clr-namespace:AlgsManagerDesktop"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<viewModel:MasterViewModel/>
</UserControl.DataContext>
<!-- ListBox here that updates a SelectedItem property -->
<!-- this button handles navigation to details screen, I'd like to pass SelectedItem to the next screen -->
<Button Command="{Binding DataContext.SwitchToDetailsCommand,
RelativeSource={RelativeSource AncestorType={x:Type root:MainWindow}},
Mode=OneWay}">
Open Selected
</Button>
</UserControl>
DetailsView.xaml
<UserControl x:Class="AlgsManagerDesktop.Views.DetailsView"
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"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:AlgsManagerDesktop.Views"
xmlns:viewModel="clr-namespace:AlgsManagerDesktop.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<viewModel:DetailsViewModel/>
</UserControl.DataContext>
<-- Item details here, I'd like to take them from an Item property in the DetailsViewModel -->
</UserControl>
The DetailsView should inherit the DataContext from the ViewModel property of the MainWindowViewModel which it will if you remove the following XAML markup from it, i.e. you should not set the DataContext of the UserControl explicitly somewhere:
<UserControl.DataContext>
<viewModel:DetailsViewModel/>
</UserControl.DataContext>
It's then up to the MainWindowViewModel to initialize and set the state of the DetailsViewModel.
You created a SelectedItem property in MasterViewModel, presumably to bind to the SelectedItem property of your presumable ListBox that's missing from your XAML, but that is a dead-end view model. In fact I'd argue that you shouldn't split your view model in three (the actual view model, the master one and the details one) because they're all linked together -- they're one view split in a view and 2 sub-views, so logically you should have one view model.
It should be immediately obvious that your approach isn't going to work because when you create the master/details view models in your code you don't link them together at all, you just create throw-aways.
The alternative if you want to keep your 3 view models separate for whatever reason is to keep a property link to the main view model in both of them, and to move the SelectedItem property to the main view model, then bind to it in both sub-views.

Navigation from one view to another in WPF MVVM

I wrote code which should navigate between user controls in WPF application using MVVM, but I realised that this code doesn't work.
From window LoginView I want to change the view to VotingCardView.
Actually, after clicking on the button in the LoginView, the method DisplayVCV gets executed, but the view is not going to change. What am I doing wrong?
MainView.xaml:
<Window x:Class="ElectionCalculator.View.MainView"
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:ElectionCalculator"
xmlns:v="clr-namespace:ElectionCalculator.View"
xmlns:vm="clr-namespace:ElectionCalculator.ViewModel"
mc:Ignorable="d"
Title="Election calculator" Height="350" Width="525">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<ContentControl Content="{Binding ViewModel}" />
</Window>
LoginView.xaml:
<UserControl x:Class="ElectionCalculator.View.LoginView"
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"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ElectionCalculator.View"
xmlns:vm="clr-namespace:ElectionCalculator.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Button Command="{Binding DataContext.DisplayVC, RelativeSource={RelativeSource AncestorType={x:Type Window}}, Mode=OneWay}" Margin="161,147,47,124" />
</Grid>
</UserControl>
MainViewModel.cs
class MainViewModel : BaseViewModel
{
public BaseViewModel ViewModel { get; set; }
public MainViewModel()
{
ViewModel = new LoginViewModel();
}
public ICommand DisplayVC { get { return new RelayCommand(DisplayVCV); } }
public void DisplayVCV()
{
ViewModel = new VotingCardViewModel();
MessageBox.Show("DisplayVCCommandExecuted");
}
}
Your ViewModel property implementation doesn't raise a PropertyChanged event when the value changes. This is usually done via an INotifyPropertyChanged implementation. Because of that, your view doesn't get notified that something has changed.
In your case, this means that you need a backing field for your ViewModel property and implement your ViewModel property similar to this:
private BaseViewModel _viewModel;
public BaseViewModel ViewModel
{
get { return _viewModel; }
set
{
if(_viewModel != value)
{
_viewModel = value;
OnPropertyChanged("ViewModel");
}
}
}
Since you are already deriving from BaseViewModel I assume that the method OnPropertyChanged (or some method with a similar name) is implemented there. It is also quite common that you don't have to specify the property name ("ViewModel") as an argument, since lots of implementations use the [CallerMemberName] attribute for this purpose.

Windows store app - two elements one object binding

What is the best way to acchieve this, what I am going to describe bellow.
I have two textboxes with twoway bindings on the same object and same property.
Now, when I update text in one textbox I wish other textbox to grab the same value again from object. Is that even possible, or I have to do this manually. For an example, I can use TextChanged event and set this value.
Yes you can bind a single property to two controls
If this class is your DataContext (viewmodel)
public class Bind : INotifyPropertyChanged
{
private string _text1;
public string text1
{
get
{
return _text1;
}
set
{
_text1=value;
NotifyPropertyChanged("text1");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
In XAML
<UserControl x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="350" Width="525"
xmlns:ViewModel="clr-namespace:WpfApplication1">
<UserControl.DataContext>
<ViewModel:Class1/>
</UserControl.DataContext>
<Grid>
<TextBox Width="150" Height="50" Text="{Binding text1, Mode=TwoWay}"/>
<TextBox Text="{Binding text1, Mode=TwoWay}" Margin="0,232,0,0"/>
</Grid>
</UserControl>

WPF binding issues while using MVVM

Time for my first question :)
I have the following:
public class BuilderViewModel : INotifyPropertyChanged
{
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private double _contentScale = 1.0;
public double ContentScale
{
get { return _contentScale; }
set
{
_contentScale = value;
NotifyPropertyChanged("ContentScale");
}
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#region Commands
bool CanZoomIn() { return true; }
void ZoomInExecute()
{
ContentScale += 1.0;
}
public ICommand ZoomIn { get { return new RelayCommand(ZoomInExecute, CanZoomIn); } }
#endregion
}
And the corresponding view:
<UserControl x:Class="PS_IDE.FormBuilder.View.Builder"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PS_IDE.FormBuilder.ViewModel">
<UserControl.DataContext>
<local:BuilderViewModel />
</UserControl.DataContext>
<TextBox Text="{Binding ContentScale}" Width="100" />
</UserControl>
I'm trying to have the ZoomIn command in BuilderViewModel update the text box value in it's view. The command is being fired from another user control, UIBuilder, which includes Builder. If I debug and fire the command from UIBuilder, I can see it updating ContentScale properly.
However, my text box value does not get updated (it only says "1", which is the initial value of ContentScale).
I know I'm missing something and hope someone can point me in the right direction.
EDIT: Added the control that is firing the command
<UserControl x:Class="PS_IDE.FormBuilder.UIBuilder"
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"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PS_IDE.FormBuilder"
xmlns:ViewModel="clr-namespace:PS_IDE.FormBuilder.ViewModel"
xmlns:View="clr-namespace:PS_IDE.FormBuilder.View" mc:Ignorable="d">
<UserControl.DataContext>
<ViewModel:BuilderViewModel />
</UserControl.DataContext>
<DockPanel LastChildFill="True">
....
<ToolBarTray DockPanel.Dock="Bottom" HorizontalAlignment="Right">
<ToolBar>
<Button Height="24" Width="24" ToolTip="Zoom In" Command="{Binding ZoomIn}">
<Image Source="Images/ZoomIn.png" Height="16"/>
</Button>
....
</ToolBar>
</ToolBarTray>
<View:Builder x:Name="builder" />
</DockPanel>
</UserControl>
With the setting in both view:
<UserControl.DataContext>
<local:BuilderViewModel />
</UserControl.DataContext>
you are basically creating two viewmodels, one for each view. So when your Command updates the property it does it on one of the viewmodel but your textbox is bound to a different viewmodel.
To resolve it remove the DataContext setting from the Builder.xaml
Additionally you need to pass your DataContext to your Builder control (with this both view will share the same viewmodel).
So modify your UIBuilder.xaml:
<View:Builder x:Name="builder" DataContext="{Binding}" />
Use Mode TwoWay in your binding
Text ="{Binding ElementName=BuilderViewModel,
Path=ContentScale,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Nota : use observable collection in order to send notify

Categories

Resources