I'm doing a very simple implementation of the MVC pattern in C# with a WPF interface.
I have a model that's keeping the state. I want to be able to notify the view form the model whenever anything about the state changes, so that the view can update itself accordingly.
What's the simplest best practice for doing this in WPF? I know there's such a thing as a PropertyChanged event, is that what I'm looking for or is that too specific for my situation?
Thanks!
Yes. Implement the interface INotifyPropertyChanged.
An example:
MainWindow.xaml
<Window x:Class="INotifyChangedDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Label Content="{Binding HitCount}"></Label>
<Button Grid.Row="1" Click="Button_Click">
Hit
</Button>
</Grid>
MainWindow.xaml.cs
namespace INotifyChangedDemo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MainViewModel _viewModel = new MainViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _viewModel;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_viewModel.HitCount = _viewModel.HitCount + 1;
}
}
}
MainViewModel.cs
namespace INotifyChangedDemo
{
public class MainViewModel : INotifyPropertyChanged
{
private int _hitCount;
public int HitCount
{
get
{
return _hitCount;
}
set
{
if (_hitCount == value)
return;
_hitCount = value;
// Notify the listeners that Time property has been changed
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("HitCount"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
For better implementation of INotifyChangedProperty, please refer to this thread: Automatically INotifyPropertyChanged.
If you wanna know more about the MVVM pattern, please see here: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
If your view binds to a property declared in your model, and your property raises the PropertyChanged event whenever it is changed, then your view will automatically be updated with the new value. For instance, your view might declare the following:
<TextBlock Text="{Binding Name}" />
And in your model you would have:
string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
This assumes that you are using some framework / helper that provides the RaisePropertyChanged method. I am taking this example from the Galasoft MVVM framework, but I assume that exactly the same principal applies in MVC.
Hope this helps.
Related
The ViewModel:
public class ConnectionStatusViewModel : BindableBase
{
private string _txtConn;
public string TextConn
{
get { return _txtConn; }
set { SetProperty(ref _txtConn, value); }
}
}
The XAML:
<UserControl x:Class="k7Bot.Login.Views.ConnectionStatus"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://www.codeplex.com/prism"
prism:ViewModelLocator.AutoWireViewModel="True" Width="300">
<Grid x:Name="LayoutRoot">
<Label Grid.Row="1" Margin="10,0,10,0">connected:</Label>
<TextBlock Text="{Binding TextConn}" Grid.Row="1" Grid.Column="1" Margin="10,0,10,0" Height="22" />
</Grid>
</UserControl>
The View:
public partial class ConnectionStatus : UserControl
{
public ConnectionStatus()
{
InitializeComponent();
}
}
In another module, I have an event listener, that eventually runs this code:
ConnectionStatusViewModel viewModel = _connectionView.DataContext as ConnectionStatusViewModel;
if (viewModel != null)
{
viewModel.TextConn = "Testing 123";
}
The code runs but the TextConn is updated and does not display in the UI
Are you sure TextConn does not update? Because it can update but the display could not change. You should implement the INotifyPropertyChanged interface and after you make any changes to TextConn call the implemented OnPropertyChanged("TextConn"); or whatever you name the function. This will tell the UI that the value has changed and it needs to update.
The UserControl's DataContext gets its value when the UC is initialized. Then you get a copy of the DataContext, cast it to a view model object, and change the property. I don't believe that the UC gets its original DataContext updated in this scenario.
Probably you need to use a message mediator to communicated changes between different modules.
After some troubleshooting, this code works, the issue was that I was running this code:
ConnectionStatusViewModel viewModel = _connectionView.DataContext as ConnectionStatusViewModel;
if (viewModel != null)
{
viewModel.TextConn = "Testing 123";
}
before the view was actually activated. Silly, but maybe it will help someone down the line.
I am trying to implement the Model View Viewmodel. However I can't get it to work.
This is my model and the base class:
namespace GridCity.GUI {
class DateInfoModel : PropertyChangedBase {
private string _Date = string.Empty;
public string Date {
get {
return _Date;
}
set {
if (_Date != value) {
_Date = value;
OnPropertyChanged(nameof(Date));
System.Console.WriteLine(_Date);
}
}
}
}
}
namespace GridCity.GUI {
using System.ComponentModel;
public abstract class PropertyChangedBase : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected internal void OnPropertyChanged(string propertyName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
System.Console.WriteLine("OnPropertyChanged: " + propertyName);
}
}
}
This is my viewmodel:
namespace GridCity.GUI {
class DateInfoViewModel : PropertyChangedBase {
public DateInfoViewModel(DateInfoModel model) {
Model = model;
}
public DateInfoModel Model { get; private set; }
}
}
And this is my view:
<UserControl x:Class="GridCity.GUI.DateInfoView"
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:GridCity.GUI"
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="150">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" FontWeight="ExtraBold" Content="{Binding Model.Date}"></Label>
<Label Grid.Row="1" Grid.Column="0" FontWeight="ExtraBold" Content="TEST"></Label>
</Grid>
</UserControl>
I set view.DataContext in my MainWindow constructor:
public MainWindow() {
InitializeComponent();
DateInfoView view = new DateInfoView();
Game.DateInfoModel = new DateInfoModel();
DateInfoViewModel viewModel = new DateInfoViewModel(Game.DateInfoModel);
view.DataContext = viewModel;
}
I can see the second label (TEST), so the user control seems to work in theory. However I don't see anything in the first label. I can see outputs on the console however when _Date is changed and OnPropertyChanged is called.
What am I missing?
You seem to have two instances of DateInfoView. One that you define in XAML and one that you create in code. You set DataContext of the latter but then don't use it. You can fix it in one of few ways
Solution 1
Set DataContext of whole Window which will pass down DataContext value to your UserControl
public MainWindow()
{
InitializeComponent();
Game.DateInfoModel = new DateInfoModel();
this.DataContext = new DateInfoViewModel(Game.DateInfoModel);
}
but as you say you want different context for different controls
Solution 2
Give UserControl name in XAML
<xx:DateInfoView ... x:Name="myUserControl"/>
and use it in code behind
public MainWindow()
{
InitializeComponent();
Game.DateInfoModel = new DateInfoModel();
myUserControl.DataContext = new DateInfoViewModel(Game.DateInfoModel);
}
I would however suggest 3rd solution where you create bigger view model, with smaller view models as its properties, set it as DataContext of the whole window and in XAML bind DataContext of specific UserControl(s) to properties of bigger view model. Like that you have to set DataContext only once and it will propagate to all other controls in the window.
Change
view.DataContext = viewModel;
to
this.DataContext = viewModel;
as you need to set the DataContext of MainWindow if the UserControl is located in that
I am coding an application, its a quiz, I have a main Window where I load different UserControls (Pages). so my problem is that I have one image on the MainWindow, I want to change the Visibility of this image from Collapsed to Visible from one of the UserControls but with no luck...
Here is my MainWindow:
<Window x:Class="MuseonQuiz_v3.PageSwitcher"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pages="clr-namespace:MuseonQuiz_v3.Pages"
xmlns:k="http://schemas.microsoft.com/kinect/2013"
Title="MainWindow" Height="710" Width="1127" IsEnabled="True" DataContext="{Binding}" FontFamily="KaiTi" ResizeMode="NoResize" WindowStyle="None"
WindowStartupLocation="CenterScreen" WindowState="Maximized">
<Grid>
<Grid>
<k:KinectRegion Name="kinectRegion">
<ContentControl x:Name="mainContentControl"/>
</k:KinectRegion>
</Grid>
<Grid>
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />
</Grid.Resources>
<k:KinectSensorChooserUI HorizontalAlignment="Center" VerticalAlignment="Top" Name="sensorChooserUi" />
<k:KinectUserViewer VerticalAlignment="Bottom" HorizontalAlignment="Center" k:KinectRegion.KinectRegion="{Binding ElementName=kinectRegion}" Height="600" Width="600" />
<Image Name="colorStreamImage" Width="640" Height="480" Visibility="Collapsed" HorizontalAlignment="Center" />
</Grid>
</Grid>
and this is my UserControl:
public partial class Selectie : UserControl, ISwitchable
{
string backgroundSelectie = "pack://application:,,,/MuseonQuiz_v3;component/Images/Selectie/selectie_background.jpg";
public Selectie()
{
InitializeComponent();
selectieBackground();
animatieButtons();
}
#region ISwitchable Members
public void UtilizeState(object state)
{
throw new NotImplementedException();
}
#endregion
}
My question is... how do I change the Visibility of the colorStreamImage that is located in the MainWindow from the UserControl... I have tried making an instance of the MainWindow, but that does not work, maybe I have to use some binding, but I am not sure, I appreciate any help you can provide!
As Clemens mentioned, your best bet is to go down the MVVM path. This is a good tutorial to get started In the Box – MVVM Training.
First, you can create a view model that implements INotifyPropertyChanged. In this case, you may want it to have at least one property of type Visibility.
public class MainViewModel : INotifyPropertyChanged
{
private Visibility _imageVisibility;
public Visibility ImageVisibility
{
get { return _imageVisibility; }
set { _imageVisibility = value; OnPropertyChanged("ImageVisibility"); }
}
private BitmapImage _imageSource;
public BitmapImage ImageSource{...}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler eventHandler = PropertyChanged;
if (eventHandler != null)
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
Now you'll want to set this view model as the data context on the main window. To do this, Paul Stovell has a good post on the different approaches: http://paulstovell.com/blog/mvvm-instantiation-approaches. Once we set it on the main window, the Selectie element will inherit the data context. Using the simplest approach:
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
Your Image element might then bind to the property like this:
<Image Visibility="{Binding ImageVisibility, UpdateSourceTrigger=PropertyChanged}" Source="{Binding ImageSource}" Height="200" Width="200"></Image>
The Selectie element can now change the ImageVisbility property on the view model since it shares the same data context as MainWindow. (I used the code-behind as an example. You'll probably want to push as much of that logic out of the view and into the view model or further downstream)
public partial class Selectie : UserControl
{
public Selectie()
{
InitializeComponent();
}
private void Selectie_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (((MainViewModel)this.DataContext).ImageVisibility == System.Windows.Visibility.Visible)
((MainViewModel)this.DataContext).ImageVisibility = System.Windows.Visibility.Collapsed;
else
((MainViewModel)this.DataContext).ImageVisibility = System.Windows.Visibility.Visible;
}
}
I'm very new to WPF and a beginner in C#.NET. I'm currently making an application where there will be many pages and the trigger to change the page is hand gesture using Kinect SDK (the trigger method is not relevant for this question). Normally when a WPF file is created, there will be a similarly named .cs file attached to it, which acts somewhat like a controller. However, I need multiple WPF files/pages to be controlled only by a single controller .cs file. How do I achieve that? Thanks for viewing my question and your answer will be very appreciated :)
You probably want to write a class that contains your 'controller' code and reference it from your WPF UserControls / Pages.
In a new file:
public class MyController
{
public void DoThings(object parameter)
{
// stuff you want to do
}
}
and then inside your UserControl code-behind class:
public partial class MyWpfControl : UserControl
{
private MyController controller;
public MyWpfControl
{
this.controller = new MyController();
}
}
and finally, tie your events back to the controller's method:
private void OnGesture(object sender, EventArgs e)
{
// call the method on the controller, and pass whatever parameters you need...
this.controller.DoThings(e);
}
The code behind is really part of the view and isn't really analogous to a controller and generally there shouldn't be much code in them. Typically you would want most of your logic between your "View Model" which serves as an abstraction of the view and "Model" which serves as an abstraction of the business logic that your UI is interacting with.
In this light what I think you really want is a View Model(VM) that controls multiple views. This is a fairly typical scenario and the preferred method (IMO anyway) is to have a hierarchical view model that has a top level the application model and a number of sub VMs that represent different components within your UI, though you can bind everything to your top level VM if you really want to.
To do this we would first define our view model like so
public interface IGestureSink
{
void DoGesture();
}
public class MyControlVM : INotifyPropertyChanged, IGestureSink
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private ApplicationVM parent;
public MyControlVM(ApplicationVM parent)
{
this.Name = "my user control";
this.parent = parent;
parent.PropertyChanged += (s, o) => PropertyChanged(this, new PropertyChangedEventArgs("Visible"));
}
public String Name { get; set; }
public bool Visible { get { return parent.ControlVisible; } }
public void DoGesture()
{
parent.DoGesture();
}
}
public class ApplicationVM : INotifyPropertyChanged, IGestureSink
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public ApplicationVM()
{
this.ControlVM = new MyControlVM(this);
this.ControlVisible = false;
}
public MyControlVM ControlVM { get; private set; }
public bool ControlVisible {get; set;}
public void DoGesture()
{
this.ControlVisible = !this.ControlVisible;
PropertyChanged(this, new PropertyChangedEventArgs("ControlVisible"));
}
}
and then all we need to do is to build a user control
<UserControl x:Class="WpfApplication2.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Background="LightBlue">
<Label Content="{Binding Name}"/>
</Grid>
</UserControl>
and page
<Window xmlns:my="clr-namespace:WpfApplication2" x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<Grid>
<my:MyControl Width="200" Height="200" x:Name="myUserControl" DataContext="{Binding ControlVM}" Visibility="{Binding Visible,Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="222,262,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
</Window>
That use it. The only thing that we need in our code behind is a constructor that sets up the page VM and wiring from our button to the view model.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ApplicationVM();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
((IGestureSink)(this.DataContext)).DoGesture();
}
}
If you wanted to use a monolithic view model instead you would use this Instead of binding the DataContext to ControlVM:
<my:MyControl Width="200" Height="200" x:Name="myUserControl" DataContext="{Binding DataContext}" Visibility="{Binding ControlVisible,Converter={StaticResource BooleanToVisibilityConverter}}"/>
I am a reasonably experienced programmer but new to WPF. I have bound a textblock on a form to an object property, but it is not updating the form as I would expect when I set the property. The binding appears to be done correctly--if I troubleshoot with a button that updates the property the form changes, but when I initially set the property in the form's constructor by parsing a local XML file it doesn't update.
I am using C# and VS2010. Could someone guide me for a few steps or refer me to a book or coding tool that gets me over this hump. Also, please note that I chose to structure things way by imitating the paradigm used in the "How Do I: Build My First WPF Application" at windowsclient.net. If you think I'm going about it the wrong way, I would appreciate a pointer to a better tutorial.
Form XAML:
<Window ...
xmlns:vm="clr-namespace:MyProjectWPF.ViewModels">
<Grid>
<Grid.DataContext>
<vm:MyConfigurationViewModel />
</Grid.DataContext>
<TextBlock Name="textBlock4" Text="{Binding Path=Database}" />
</Grid>
MyConfigurationViewModel class definition:
class MyConfigurationViewModel : INotifyPropertyChanged
{
private string _Database;
public string Database
{
get { return _Database; }
set { _Database = value; OnPropertyChanged("Database"); }
}
public void LoadConfiguration()
{
XmlDocument myConfiguration = new XmlDocument();
myConfiguration.Load("myfile.xml");
XmlNode root = myConfiguration.DocumentElement;
Database = root["Database"].InnerText;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string Property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(Property));
}
And the codebehind my XAML form:
public partial class MyForm : Window
{
private ViewModels.myConfigurationViewModel mcvm
= new ViewModels.myConfigurationViewModel();
public MyForm()
{
mcvm.LoadConfiguration();
}
You have two instances of myConfigurationViewModel. One is created inside the XAML and the second one is created inside the form's codebehind. You are calling LoadConfiguration on the one in the code behind, which is never set as the form's DataContext.
Remove this from the XAML:
<Grid.DataContext>
<vm:MyConfigurationViewModel />
</Grid.DataContext>
and change the constructor to this:
public MyForm()
{
mcvm.LoadConfiguration();
DataContext = mcvm;
}
Can you try this XAML:
<Window ...
xmlns:vm="clr-namespace:MyProjectWPF.ViewModels">
<Grid>
<TextBlock Name="textBlock4" Text="{Binding Path=Database}" />
</Grid>
with this code:
public partial class MyForm : Window
{
private ViewModels.myConfigurationViewModel mcvm = new ViewModels.myConfigurationViewModel();
public MyForm()
{
mcvm.LoadConfiguration();
this.DataContext = mcvm;
}
[Update] Was wrong on the explanation, removed it.