I'm rather new to WPF and C# in general. I'm playing around with it and encountered a problem which I feel like would be a piece of cake for an expert but I have no idea what I'm doing wrong.
I'm trying to create a simple DataGrid control (within a TabControl) and bind it to an ObservableCollection object.
I use microsoft's Data Binding Demo provided in their data binding overview as a basis for my code.
MainWindow XAML:
<Window x:Class="PetProject.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:PetProject"
mc:Ignorable="d"
Title="PetProject" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=Dogs}"
x:Key="DogsDataView" />
</Window.Resources>
<Grid Margin="8,8,8,8">
<TabControl>
<TabItem Header="Dogs">
<DataGrid ItemsSource="{Binding Source={StaticResource DogsDataView}}">
</DataGrid>
</TabItem>
</TabControl>
</Grid>
</Window>
Code-Behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace PetProject
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
///
public partial class MainWindow : Window
{
CollectionViewSource DogsDataView;
public MainWindow()
{
InitializeComponent();
DogsDataView = (CollectionViewSource)(this.Resources["DogsDataView"]);
}
}
}
The App XAML is
<Application x:Class="PetProject.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PetProject"
Startup="AppStartup">
<!--StartupUri="MainWindow.xaml"-->
</Application>
code-behind:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace PetProject
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private ObservableCollection<Dog> dogs = new ObservableCollection<Dog>();
void AppStartup(object sender, StartupEventArgs args)
{
LoadData();
MainWindow mainWindow = new MainWindow();
mainWindow.Show();
}
public ObservableCollection<Dog> Dogs
{
get { return this.dogs; }
set { this.dogs = value; }
}
private void LoadData() {
Dog Johnny = new Dog("Johnny",1325);
Dog Diamond = new Dog("Diamond",1327);
this.Dogs.Add(Johnny);
this.Dogs.Add(Diamond);
}
}
}
Dog is just a class implementing the INotifyPropertyChanged interface (which for now does not do anything):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace PetProject
{
public class Dog : INotifyPropertyChanged
{
private string name;
private string number;
public event PropertyChangedEventHandler PropertyChanged;
public Dog(string name, int number)
{
this.name = name;
this.number = number.ToString("D4");
}
protected void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
I'd appreciate any help in understanding why the DataGrid is not populated.
Also, any suggestion on bad coding habits or improvement to the code would be very welcome, as I'm in a very initial learning-by-experience phase.
Thanks!
You can't bind to private fields. You can only bind to public properties. As far as the DataGrid is concerned, Dog has no information to display.
public class Dog : INotifyPropertyChanged
{
private string _name;
private string _number;
public Dog(string name, int number)
{
Name = name;
Number = number.ToString("D4");
}
public String Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
NotifyPropertyChanged(nameof(Name));
}
}
}
public String Number
{
get { return _number; }
set
{
if (value != _number)
{
_number = value;
NotifyPropertyChanged(nameof(Number));
}
}
}
I'm prefixing your private fields with underscores because that's standard practice. It's standard practice because having two identifiers which differ only by case is a recipe for confusion and bugs.
First of all, I suggest you to read about MVVM principles then maybe choose a MVVM framework to use with WPF. For instance MVVM light toolkit is a good choice to start and understand MVVM.
For your example, here are just a few remarks about your code:
I suggest you to group all your 'business' data into a viewModel class (see MVVM practices all around the web) - nothing in the App class...
This ViewModel will implement 'INotifyPropertyChanged' interface
So the Dogs property will be located into this ViewModel and will raise the 'PropertyChanged' event in its setter (what is not currently the case in your sample)
There are several MVVM frameworks that would 'bind' automatically your views to your view model, but to understand, the main goal is to set your Window.DataContext with the appropriate ViewModel.
That's why you can restore in App.xaml: StartupUri="MainWindow.xaml"
Then to load your ViewModel, you can do something like that to load your Dogs collection:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// For test: LOAD & SET your DataContext here
//
var myDogViewmodel = new DogViewModel();
myDogViewModel.LoadData();
this.DataContext = myDogViewmodel;
}
}
Your ViewModel should look like something like that:
public class DogViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Dog> _dogs;
public ObservableCollection<Dog> Dogs
{
get { return _dogs; }
set
{
_dogs = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Dogs"));
}
}
public void LoadData()
{
// ....
}
}
Then your Dog class must also implement INotifuPropertyChanged interface:
public class Dog : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
private int _number;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
}
public int Number
{
get => _number;
set
{
if (_number != value)
{
_number = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Number"));
}
}
}
}
Finally,in your MainWindow.xaml:
>
<Window x:Class="PetProject.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:PetProject"
mc:Ignorable="d"
Title="PetProject" Height="350" Width="525">
<Grid Margin="8,8,8,8">
<TabControl>
<TabItem Header="Dogs">
<DataGrid ItemsSource="{Binding Dogs}" />
</TabItem>
</TabControl>
</Grid>
It should work now ;) Tell me if it's clear. Get familiar with MVVM...
Related
I am a C# newbie trying to build a simple MVVM app, and I am having trouble tying events in my XAML View to methods in my Model or ViewModel. I understand why MVVM is used and feel like I get the broad strokes of how to put an MVVM app together, but I am lost in the details. I apologize in advance if it looks like I have no idea what I'm doing, but I don't, despite lots of reading up on the subject.
I want btnUpdate_Click in MainScreenViewModel to execute when the button is clicked, but I get the error
MC6005 Click="vm:btnUpdate_Click" is not valid. 'vm:btnUpdate_Click' is not a valid event handler method name. Only instance methods on the generated or code-behind class are valid.
If my classes are public and in the same namespace, what do I need to do to make them visible from my View? I don't want to move methods back to the MainWindow class.
<Window x:Class="SFM_Calculator.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:SFM_Calculator"
xmlns:vm="MainScreenViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
>
<Window.DataContext>
<local:SFMModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
...
<ColumnDefinition/>
</Grid.ColumnDefinitions>
...
</Grid.RowDefinitions>
...
<Button
Grid.Column="1"
Grid.Row="2"
x:Name="btnUpdate"
Content="Update"
Click="vm:btnUpdate_Click"
/>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Linq;
using System.ComponentModel;
namespace SFM_Calculator
{
public class MainScreenViewModel : INotifyPropertyChanged
{
private void btnUpdate_Click(object sender, System.Windows.RoutedEventArgs e)
{
TestInt = 999;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public SFMModel sfmModel = new SFMModel();
private int _testInt;
public int TestInt
{
get { return _testInt; }
set { _testInt = value; }
}
public MainScreenViewModel()
{
Debug.WriteLine("Got here.");
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace SFM_Calculator
{
public class SFMModel : INotifyPropertyChanged
{
private int _tprop;
public int TProp
{
get { return _tprop; }
set { _tprop = value; }
}
public SFMModel ()
{
TProp = 69;
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Configuration;
namespace SFM_Calculator
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
Your viewModel should expose a command, not a method. Plus you should access the exposed command via binding mechanism.
Sample Command that implements required ICommand interface
internal class Command : ICommand
{
private readonly Action execute;
public Command(Action execute)
{
this.execute = execute;
}
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter)
{
return true;
}
public void Execute(object? parameter)
{
execute();
}
}
Sample ViewModel that exposes ICommand (not a regular method as in your example). It will change the value of Text property after clicking the button - just to show that it works.
internal class ViewModel : INotifyPropertyChanged
{
public string Text { get; private set; }
public ICommand AwesomeCommand { get; }
public ViewModel()
{
AwesomeCommand = new Command(() => {
Text = "Button clicked";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
});
}
public event PropertyChangedEventHandler? PropertyChanged;
}
MainWindow:
<StackPanel>
<Button Command="{Binding AwesomeCommand}"></Button>
<Label Content="{Binding Text}" Height="100"></Label>
</StackPanel>
code-behind MainWindow, to hook-up ViewModel and the view (MainWindow):
public MainWindow()
{
DataContext = new ViewModel();
InitializeComponent();
}
Currently i am trying to learn WPF, but i have hit a brickwall with my current Problem, after many hours googling and trying to fix it on my own. I am trying to display the Model Province. I have found multiple similar Problems but i couldn't figure it out on my own. After having checked the Output there was no mention of any error. Currently the Window shows just the empty Model but no data even though the Observable Collection gets updated. So before i completely destroy my interest in WPF i am asking for help.
MyView
<Window x:Class="isnuaatest.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:isnuaatest"
xmlns:local1="clr-namespace:isnuaatest.Models"
xmlns:local2="clr-namespace:isnuaatest.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local2:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid>
<DataGrid ItemsSource="{Binding Provinces, UpdateSourceTrigger=PropertyChanged}">
</DataGrid>
</Grid>
<StackPanel Width="200" Margin="50">
<Button x:Name="OpenSaveFile" Click="OpenSaveFile_Click">OpenSaveFile</Button>
</StackPanel>
</Grid>
My View Model
using isnuaatest.Helper;
using isnuaatest.Models;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Input;
namespace isnuaatest.ViewModel
{
public class MainWindowViewModel : INotifyPropertyChanged
{
public ObservableCollection<Province> _province;
public ObservableCollection<Province> Provinces
{
get { return this._province; }
set
{
_province = value;
}
}
public MainWindowViewModel() : base()
{
this.Provinces = new ObservableCollection<Province>();
}
private string _savegamePath;
public string SavegamePath
{
get { return _savegamePath; }
set { _savegamePath = value; OnPropertyChanged("SavegamePath"); GetProvinces(_savegamePath);}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var savegamefile = this.PropertyChanged;
if (savegamefile != null)
savegamefile(this, new PropertyChangedEventArgs(propertyName));
}
public event EventHandler OnItemChanged;
public void GetProvinces(string path)
{
Reader reader = new Reader();
if (_savegamePath != null)
{
FileStream fs = File.OpenRead(path);
List<Province> listofProvinces = reader.ReadTextString(fs);
foreach (Province province in listofProvinces)
{
Provinces.Add(new Province()
{
Aristocrats = province.Aristocrats,
Artisans = province.Artisans
});
}
}
}
}
}
Code Behind
using isnuaatest.Helper;
using isnuaatest.Models;
using isnuaatest.ViewModel;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace isnuaatest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindowViewModel _vm = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
private void OpenSaveFile_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog fileDialog = new OpenFileDialog();
fileDialog.Multiselect = false;
dynamic result = fileDialog.ShowDialog();
if (result == true)
{
_vm.SavegamePath = fileDialog.FileName;
}
}
}
}
My thinking is that maybe the Data Context wont update, because the data is in the Observable Collection. If this is true how can i update the Data Context, i already tried adding it in xaml to no avail.
Thanks
You actually create 3 different MainWindowViewModel objects - one in xaml and two in code behind. You can get rid of one in xaml, once in MainWindow constructor you set DataContext xaml-one is overridden.
But two objects in code-behind cause your problem - you load file into _vm object, but it's not the one that is held in DataContext.
To fix your problem use _vm for DataContext and not the new object:
public MainWindowViewModel _vm = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _vm;
}
Change your Provinces:
public ObservableCollection<Province> Provinces
{
get { return this._province; }
set
{
_province = value;
OnPropertyChanged("Provinces");
}
}
I'm trying to create UserControls to be able to reuse them later in the WPF application. I choose to build a small project to train myself, but I can't make it work.
The objective is to have a TextBox whose content will be sent as a Label Text with the click on a Button.
I've read and tried the solutions on those links :
XAML Binding on Dependency Property
Custom Dependency Properties
Simple Dependency Property and UserControl issues in C#
Add dependency property to control
But even the starting text I set in the constructor doesn't show up, and the button click does nothing at all.
Here are my files :
MyControl.xaml
<UserControl x:Class="WpfApplication1.MyControl"
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:WpfApplication1"
mc:Ignorable="d"
Name="control"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Text="{Binding TextBoxContent,ElementName=control}"/>
<Button Content="Print Entry" Grid.Row="1" Command="{Binding ButtonCommmand,ElementName=control}"/>
<Label Grid.Row="2" Content="{Binding LabelContent,ElementName=control}"/>
</Grid>
</UserControl>
MyControl.xaml.cs
using GalaSoft.MvvmLight.CommandWpf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1
{
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
ButtonCommmand = new RelayCommand(Action);
}
public string TextBoxContent
{
get
{
return (string)GetValue(TextBoxContentProperty);
}
set
{
SetValue(TextBoxContentProperty, value);
}
}
public RelayCommand ButtonCommmand
{
get
{
return (RelayCommand)GetValue(ButtonCommandProperty);
}
set
{
SetValue(ButtonCommandProperty, value);
}
}
public string LabelContent {
get
{
return (string)GetValue(LabelContentProperty);
}
set
{
SetValue(LabelContentProperty, value);
}
}
public void Action()
{
LabelContent = TextBoxContent;
}
public static readonly DependencyProperty TextBoxContentProperty = DependencyProperty.Register("TextBoxContent", typeof(string), typeof(MyControl), new PropertyMetadata(""));
public static readonly DependencyProperty ButtonCommandProperty = DependencyProperty.Register("ButtonCommmand", typeof(RelayCommand), typeof(MyControl), new PropertyMetadata(null));
public static readonly DependencyProperty LabelContentProperty = DependencyProperty.Register("LabelContent", typeof(string), typeof(MyControl), new PropertyMetadata(""));
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.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:WpfApplication1"
xmlns:control="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<control:MyControl DataContext="{Binding customControl}" TextBoxContent="{Binding Text,Mode=TwoWay}" LabelContent="{Binding EndText,Mode=TwoWay}" ButtonCommmand="{Binding Command,Mode=TwoWay}"/>
</Grid>
</Window>
MainWindow.xaml.cs
using GalaSoft.MvvmLight.CommandWpf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
MyControl customControl = new MyControl();
public MainWindow()
{
InitializeComponent();
Command = new RelayCommand(Action);
Text = "Testing... Testing... 1, 2, 3,...";
}
private string text;
public string Text
{
get
{
return text;
}
set
{
text = value;
NotifyPropertyChanged();
}
}
private string endText;
public string EndText
{
get
{
return endText;
}
set
{
endText = value;
NotifyPropertyChanged();
}
}
private RelayCommand command;
public RelayCommand Command
{
get
{
return command;
}
set
{
command = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void Action()
{
EndText = Text;
}
private void NotifyPropertyChanged([CallerMemberName]string PropertyName = "")
{
if (!String.IsNullOrEmpty(PropertyName))
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
}
Thank you a lot for your help !
You are creating another instance of your UserControl in the code-behind of the window:
MyControl customControl = new MyControl();
What you want to do is to bind the properties of the UserControl that you have defined in your XAML to the properties of the window. For this to work you should set the DataContext of the window to itself:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
Command = new RelayCommand(Action);
Text = "Testing... Testing... 1, 2, 3,...";
}
}
...and remove this from your XAML:
DataContext="{Binding customControl}"
This question already has answers here:
WPF Binding Image Source
(2 answers)
Closed 5 years ago.
I have been working on a small sample Wpf Mvvm project for experimenting with INotifyPropertyChanged interface. The project actually works correctly, but the problem that I am having is that the project only works correctly if I set the DataContext in the code behind of MainWindow.xaml. If I try to set the DataContext in the xaml markup then some of the features of the project don't work. The UI contains a textblock, textbox (for entering text to display in the textblock OnPropertyChanged) and submit button (which really does nothing except provide a place to lose focus from textbox) and 3 other buttons (color buttons) for changing the background color of the UI. The default color of the UI is orange -- until the color is changed by clicking any of the color buttons
There are 3 viewModels, PersonViewModel (which the textbox binds to), BackgroundViewModel (for the color buttons) and a MainViewModel which combines the two other viewModels. The viewModels reside in the viewModels folder of the project. There is also an ObservableObject class (ViewModelBase class basically) which implement INotifyPropertyChanged interface and gets inherited by PersonViewModel and BackgroundViewModel. ObservableObject.cs resides in the root folder of the project.
The Project isn't pure Mvvm. The color buttons use a click event in the code behind of MainWindow.xaml. If I set the DataContext in the Code behind of MainWindow.xaml everything works correctly. If I set the DataContext in the xaml markup -- the textbox/textblock features works but the color buttons won't change the background color of the UI. When I step through the code it runs through all the code correctly but the UI background colors don't change. I am guessing it is a binding thing.
The sample project can be downloaded here
The code is below. How can I make this project function correctly if I set the DataContext in the xaml markup? I tried the following binding on the Grid which WILL set the default orange color for the UI, but the color buttons don't work:
<Grid Background="{Binding Background.Color}" DataContext="{StaticResource bc}">
--MainWindow.xaml
<Window x:Class="NotifyChangeExample.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:NotifyChangeExample"
xmlns:VM="clr-namespace:NotifyChangeExample.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="550" Width="525">
<!--<Window.DataContext>
<VM:MainViewModel />
</Window.DataContext>-->
<Window.Resources>
<VM:MainViewModel x:Key="bc" />
</Window.Resources>
<Grid Background="{Binding Background.Color}" DataContext="{StaticResource bc}">
<!--<Grid Background="{Binding Background.Color}">-->
<DockPanel LastChildFill="False" Margin="0,82,0,0">
<StackPanel Width="150" DockPanel.Dock="Top">
<TextBlock Text="{Binding Person.Name, StringFormat=Welcome (0)}" />
<TextBox Text="{Binding Person.Name, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" />
<Button>Submit</Button>
</StackPanel>
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal" DockPanel.Dock="Bottom" >
<Button Click="Red_Clicked">Red Background</Button>
<Button Click="Blue_Clicked">Blue Background</Button>
<Button Click="Yellow_Clicked">Yellow Background</Button>
</StackPanel>
</DockPanel>
</Grid>
</Window>
--MainWindow.xaml.cs
using NotifyChangeExample.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace NotifyChangeExample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
MainViewModel _main = new MainViewModel();
public MainWindow()
{
InitializeComponent();
//DataContext = _main;
}
private void Red_Clicked(object sender, RoutedEventArgs e)
{
_main.SetBackground(Brushes.Red);
}
private void Blue_Clicked(object sender, RoutedEventArgs e)
{
_main.SetBackground(Brushes.Blue);
}
private void Yellow_Clicked(object sender, RoutedEventArgs e)
{
_main.SetBackground(Brushes.Yellow);
}
}
}
--ObservableObject.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NotifyChangeExample
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
}
--PersonViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NotifyChangeExample.ViewModels
{
public class PersonViewModel : ObservableObject
{
private string _name;
public string Name
{
get
{
if (string.IsNullOrEmpty(_name))
return "Unknown";
return _name;
}
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
}
--BackgroundViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
namespace NotifyChangeExample.ViewModels
{
public class BackgroundViewModel : ObservableObject
{
private Brush _color;
public Brush Color
{
get
{
if (_color == null)
return Brushes.Orange;
return _color;
}
set
{
_color = value;
OnPropertyChanged("Color");
}
}
}
}
--MainViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
namespace NotifyChangeExample.ViewModels
{
public class MainViewModel
{
public PersonViewModel Person { get; private set; }
public BackgroundViewModel Background { get; private set; }
public MainViewModel()
{
Person = new PersonViewModel();
Background = new BackgroundViewModel();
}
public void SetBackground(Brush brushColor)
{
Background.Color = brushColor;
}
}
}
Your code behind is using the _main object so if you want to set the DataContext in the XAML, you just need to set _main using the DataContext.
So in the XAML you would have
<Window.DataContext>
<VM:MainViewModel />
</Window.DataContext>
and in your code behind you would set _main by casting the DataContext to a MainViewModel
MainViewModel _main;
public MainWindow()
{
InitializeComponent();
_main = (MainViewModel) DataContext;
}
Alternatively, remove the DataContext from XAML, and use this MainWindow constructor:
private readonly MainViewModel _main = new MainViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _main;
}
When you are binding your ViewModel from XAML it can't work, because in your code-behind you are setting the colors to your local ViewModel "_main". But _main is not bound to the View, bc is.
I am playing with a sample WPF application that is tiered in a Model-View-Presenter manner. The Model is a collection of Animals which is displayed in a view via binding to properties in a presenter class. The XAML has an items control that displays all the animals in a model.
The model class has a boolean attribute called 'IsMammal'. I want to introduce a filter in the XAML in the form of a radio button group that filters the collection of animals based on the 'IsMammal' attribute. Selection of the radiobutton 'Mammals' updates the items control with all the Animals that have the 'IsMammal' value set to true and when the value is toggled to 'Non-Mammals', the display is updated with all animals that have that particular boolean set to false. The logic to do the filtering is extremely simple. What is troubling me is the placement of the logic. I don't want any logic embedded in the *.xaml.cs. I want the toggling of the radiobutton to trigger a logic in the presenter that tapers my animal collection to be rebound to the display.
Some guidance here will be extremely appreciated.
Thanks
I suggest you do the following in your Presenter:
=> Create a ListCollectionView field. Set it to be equal to the "Default Collection View" of your collection, and use it as the ItemsSource for the list control. Something like:
public class Presenter()
{
private ListCollectionView lcv;
public Presenter()
{
this.lcv = (ListCollectionView)CollectionViewSource.GetDefaultView(animalsCollection);
listControl.ItemsSource = this.lcv;
}
}
=> In the handler/logic for the RadioButton's corresponding event, filter the ListCollectionView. Something like:
void OnCheckedChanged()
{
bool showMammals = radioButton.IsChecked;
this.lcv.Filter = new Predicate((p) => (p as Animal).IsMammal == showMammals);
this.lcv.Refresh();
}
Hope this helps.
EDIT:
While doing this is possible using MVP, using MVVM should be a better choice, IMHO (and as mentioned by the other answer). To help you out, I wrote a sample that implements your requirements via MVVM. See below:
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<StackPanel Orientation="Vertical">
<RadioButton x:Name="rb1" GroupName="MyGroup" Content="IsMammal = true" Checked="rb1_Checked"/>
<RadioButton x:Name="rb2" GroupName="MyGroup" Content="IsMammal = false" Checked="rb2_Checked"/>
<ListBox ItemsSource="{Binding Path=AnimalsCollectionView}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</Window>
Code-behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.Threading;
using System.ComponentModel;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void rb1_Checked(object sender, RoutedEventArgs e)
{
(this.DataContext as ViewModel).UpdateFilter(true);
}
private void rb2_Checked(object sender, RoutedEventArgs e)
{
(this.DataContext as ViewModel).UpdateFilter(false);
}
}
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Animal> animals = new ObservableCollection<Animal>();
private ListCollectionView animalsCollectionView;
public ListCollectionView AnimalsCollectionView
{
get { return this.animalsCollectionView; }
}
public void UpdateFilter(bool showMammals)
{
this.animalsCollectionView.Filter = new Predicate<object>((p) => (p as Animal).IsMammal == showMammals);
this.animalsCollectionView.Refresh();
}
public ViewModel()
{
this.animals.Add(new Animal() { Name = "Dog", IsMammal = true });
this.animals.Add(new Animal() { Name = "Cat", IsMammal = true });
this.animals.Add(new Animal() { Name = "Bird", IsMammal = false });
this.animalsCollectionView = (ListCollectionView)CollectionViewSource.GetDefaultView(this.animals);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
}
public class Animal : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return this.name; }
set
{
this.name = value;
this.OnPropertyChanged("Name");
}
}
private bool isMammal;
public bool IsMammal
{
get { return this.isMammal; }
set
{
this.isMammal = value;
this.OnPropertyChanged("IsMammal");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
}
}
I came from an MVP background before learning WPF and I have come to find that adapting the MVP pattern to WPF is a difficult exercise at best. The "proper" (read, least frustrating) approach is to utilize the Model-View-ViewModel (MVVM) pattern, which makes heavy use of the databinding features of WPF to minimize the amount of code that ends up the view-behind file.
In the simplest case, you would end up with two properties on your ViewModel:
bool FilterMammals
ObservableCollection MammalsToDisplay
In the XAML, You would bind the first to your radio button group and the second to the ItemsSource of the ListBox. The WPF databinding framework will call your property setter whenever the radiobutton group value changes, and in here you can filter and then update the list of items.
I'm not sure what your familiarity with MVVM is, so I'll stop here. Let me know if more detail would help :).