C# Pass data between two Pages WPF MVVM - c#

Im working on a small wpf project to understand c# and mvvm better.
Im currently having a problem, that I dont know how to pass data between two pages.
What I want to do:
I have two pages, they are the content of my MainWindow.
In PageViewCustomers I have a DatagridView(there are acutally two, but I just need one for this task), when I double click on a row, I open PageAddStaticIPAddress. Now I want to be able to access the row I double clicked in PageViewCustomers in the PageAddStaticIPAddress. I thought about passing the selected row as a CommandParameter in the xaml of PageViewCustomers.
But now I dont know how to access it from PageAddStaticIPAddress..
I have seen some solutions, but they all solve it by having Code in the View. I try to have as less code in the view as possible and solve it by only using Bindings,commands and my viewmodels.
Here is my current code:
PageViewCustomers.xaml
<Page x:Class="StaticIPConfiger.PageViewCustomers"
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:StaticIPConfiger"
xmlns:localvm="clr-namespace:StaticIPConfiger.Modelle"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="500"
Title="Kundenübersicht">
<Page.DataContext>
<localvm:VModel />
</Page.DataContext>
<Grid DataContext="{Binding}" >
<DataGrid IsReadOnly="True" Name="dgCustomers" ItemsSource="{Binding AlleKunden}" AutoGenerateColumns="False" Margin="0,0,0,197">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Path=c_name}" Width="*"></DataGridTextColumn>
<DataGridTextColumn Header="Standort" Binding="{Binding Path=l_name}" Width="*"></DataGridTextColumn>
</DataGrid.Columns>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding UpdateLocations}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
<DataGrid IsReadOnly="True" Name="dg2Customers" ItemsSource="{Binding AlleStandorte}" AutoGenerateColumns="False" Margin="0,168,0,10" >
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding OpenAddNewIPAddress}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=SelectedItem}"/>
</DataGrid.InputBindings>
<DataGrid.Columns>
<DataGridTextColumn Header="StandortId" Binding="{Binding Path=l_id}" Width="*"></DataGridTextColumn>
<DataGridTextColumn Header="Standort" Binding="{Binding Path=l_name}" Width="*"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
Class VModel.cs (VModel of PageViewCustomers)
namespace StaticIPConfiger.Modelle {
public class VModel : INotifyPropertyChanged {
DataView _alleKunden;
public VModel() {
DataTable dt = new DataTable();
using (SqlConnection connection = new SqlConnection("Data Source=.\\WERBASWEB;Initial Catalog=customer;Persist Security Info=True;User ID=sa;Password=Dotnet123!")) {
SqlDataAdapter adapter = new SqlDataAdapter();
adapter.SelectCommand = new SqlCommand("Select c_id,c_name, l_id,l_name, a_town, a_pcode, a_street from dbo.[AllCustomers]", connection);
adapter.Fill(dt);
}
AlleKunden = dt.DefaultView;
AlleStandorte = null;
}
public DataView AlleKunden { get; private set; }
public DataView AlleStandorte { get { return _alleKunden; } private set { _alleKunden = value;
RaisePropertyChanged("AlleStandorte");
} }
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Methods
private void RaisePropertyChanged(string propertyName) {
// take a copy to prevent thread issues
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region Commands
void UpdateLocationView(object parameter) {
DataRowView selectedRow = (DataRowView)parameter;
string customerid = selectedRow.Row[0].ToString();
string locationid = selectedRow.Row[2].ToString();
DataTable dt = SelectStatements.SelectLocationsForCustomer(locationid,customerid);
AlleStandorte = dt.DefaultView;
Console.WriteLine("Test");
}
bool CanUpdateLocationView(object parameter) {
return true;
}
public ICommand UpdateLocations { get { return new RelayCommand<object>(param => this.UpdateLocationView(param), param => this.CanUpdateLocationView(param)); } }
void OpenIPAddress() {
System.Windows.Application.Current.MainWindow.Content = new AddStaticIPAddress();
}
bool CanOpenIPAddress() {
return true;
}
public ICommand OpenAddNewIPAddress { get { return new RelayCommand(OpenIPAddress,CanOpenIPAddress); } }
#endregion
}
Page AddStaticIPAddress:
<Page x:Class="StaticIPConfiger.AddStaticIPAddress"
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:StaticIPConfiger"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="500"
Title="AddStaticIPAddress">
<Page.DataContext>
<local:AddCustomerVModel/>
</Page.DataContext>
<Grid>
<Label x:Name="lbl_net" Content="Netz:" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<Label x:Name="lbl_subnetmask" Content="Subetnmaske:" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10,36,0,0"/>
<Label x:Name="label_ipaddress" Content="IP Addresse:" HorizontalAlignment="Left" Margin="10,62,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="text_net" Text="" HorizontalAlignment="Left" Height="23" Margin="103,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<TextBox x:Name="text_subetnmask" Text="" HorizontalAlignment="Left" Height="23" Margin="103,36,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<TextBox x:Name="text_ipaddress" Text="" HorizontalAlignment="Left" Height="23" Margin="103,62,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<Label x:Name="lbl_description" Content="Bezeichnung:" HorizontalAlignment="Left" Margin="10,85,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="text_description" Text="" HorizontalAlignment="Left" Height="23" Margin="103,88,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<Button x:Name="btn_save_add_ip" Content="IP Addresse hinzufügen" Command ="" HorizontalAlignment="Left" Margin="10,129,0,0" VerticalAlignment="Top" Width="133"/>
<Button x:Name="btn_abort_add_ip" Content="Verwerfen" HorizontalAlignment="Left" Margin="148,129,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
AddIPAddressVModel:
namespace StaticIPConfiger.Modelle {
class AddIPAddressVModel : INotifyPropertyChanged {
IPAddress _ipaddress;
public AddIPAddressVModel() {
_ipaddress = new IPAddress { Net = "", Subetnmask = "", IpAddress = ""};
}
public IPAddress IPAddress {
get { return _ipaddress; }
set { _ipaddress = value; }
}
#region"Get/Set"
public int Id {
get { return IPAddress.Id; }
set {
IPAddress.Id = value;
RaisePropertyChanged("IPAddress");
}
}
public String Net {
get { return IPAddress.Net; }
set {
IPAddress.Net= value;
RaisePropertyChanged("Net");
}
}
public string Subnetmask {
get { return IPAddress.Subetnmask; }
set {
IPAddress.Subetnmask = value;
RaisePropertyChanged("Subetnmask");
}
}
public string IpAddress {
get { return IPAddress.IpAddress; }
set {
IPAddress.IpAddress = value;
RaisePropertyChanged("IPAddress");
}
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Methods
private void RaisePropertyChanged(string propertyName) {
// take a copy to prevent thread issues
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
I hope you understand my Problem ;-)
If you have any questions, let me know.
Thanks!
EDIT:
How I open AddIPAddress
I did bind a Command to the DataGrid, where I have a Command OpenAddNewIPAddress:
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding OpenAddNewIPAddress}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=SelectedItem}"/>
</DataGrid.InputBindings>
The command looks like this:
void OpenIPAddress() {
System.Windows.Application.Current.MainWindow.Content = new AddStaticIPAddress();
}
bool CanOpenIPAddress() {
return true;
}
public ICommand OpenAddNewIPAddress { get { return new RelayCommand(OpenIPAddress,CanOpenIPAddress); } }
That another question I had. When I set the content of my MainWindow (System.Windows.Application.Current.MainWindow.Content = new AddStaticIPAddress();) with a new Page, the menubar gets lost... Is there a better way to do this?

You could inject the AddIPAddressVModel with the command parameter. Something like this:
<DataGrid IsReadOnly="True" Name="dg2Customers" ItemsSource="{Binding AlleStandorte}" AutoGenerateColumns="False" Margin="0,168,0,10" >
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding OpenAddNewIPAddress}"CommandParameter="{Binding SelectedItem}"/>
</DataGrid.InputBindings>
<DataGrid.Columns>
<DataGridTextColumn Header="StandortId" Binding="{Binding Path=l_id}" Width="*"></DataGridTextColumn>
<DataGridTextColumn Header="Standort" Binding="{Binding Path=l_name}" Width="*"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
void OpenIPAddress(object parameter)
{
System.Windows.Application.Current.MainWindow.DataContext = new AddIPAddressVModel(parameter as DataRowView)
System.Windows.Application.Current.MainWindow.Content = new AddStaticIPAddress();
}
bool CanOpenIPAddress(object parameter)
{
return true;
}
public ICommand OpenAddNewIPAddress { get { return new RelayCommand<object>(OpenIPAddress, CanOpenIPAddress); } }
public AddIPAddressVModel(DataRowView drv) {
_ipaddress = new IPAddress { Net = "", Subetnmask = "", IpAddress = ""};
_drv = drv; //keep a reference to the clicked DataRowView
}
Remove this from the AddStaticIPAddress view:
<Page.DataContext>
<local:AddCustomerVModel/>
</Page.DataContext>
The Page should inherit its DataContext. Setting the DataContext in the XAML markup is only useful in very simple scenarios when the view model has no dependencies.

Try to put a field in AddStaticIPAddress:
public DataGridRow Row { get; set; }
So you can access it from VModel.
E.g. you can initialize AddIPAddressVModelon DataGridSelectionChanged action:
private void dgCustomers_OnSelectionChanged(...)
{
AddIPAddressVModel addip = new AddIPAddressVModel();
addip.Row = (DataGridRow) dgCustomers.SelectedItem;
addip.Show();
}

Related

How to Create a Filter in WPF using Datagrid and ComboBox

So here I have a MVVM form. the Form contains a Datagrid which is connected to the Databank. I also have a ComboBox which I want to use as a filter option. The Filter option shoud filter by the "AnlV nr" so when the user selects "01" from the ComboBox the datagrid should refresh and show only that "AnlV nr" that have "01" Below I will share you the code and you can see that i've gotten as far as showing the "AnlV" values in the ComboBox but I now do not know how to do the rest and make the filter work. Below is my Viewmodel and the Xaml code.
If anyone can help me with this I would really apreciate it.
Xaml Code:
<Window x:Class="QBondsFrontend.Views.Input.AnlVTexteView"
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:QBondsFrontend.Views.Input" xmlns:input="clr-namespace:QBondsFrontend.ViewModels.Input" d:DataContext="{d:DesignInstance Type=input:AnlVTexteViewModel}"
mc:Ignorable="d"
Title="AnlVTexteView"
Width="800"
MinHeight="400"
Height="490"
MinWidth="1010"
MaxWidth="1010"
UseLayoutRounding="True">
<Grid Background="#A8A8A8" >
<StackPanel VerticalAlignment="Top" Background="#A8A8A8" Orientation="Horizontal" Height="57">
<Label
Content="AnlV Nr.:" Height="35" FontSize="12"/>
<ComboBox Background="LightGray" Height="20" Width="70" ItemsSource="{Binding lstAnlVTexte}" SelectedItem="{Binding Search}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding AnlVPara}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Height="18" Width="68" Margin="5, 0"
Content="Filter löschen" FontSize="11" Style="{StaticResource STL_ButtonStandard}"
x:Name="filterlöschen"
Command="{Binding icdFilterDelete}"/>
</StackPanel>
<StackPanel Background="LightGray" VerticalAlignment="Top" Height="177" Margin="0,57,0,0">
<DataGrid x:Name="datagridXAML"
Height="177"
ItemsSource="{Binding lstAnlVTexte, Mode=TwoWay}"
Style="{StaticResource STL_DataGridReporting}"
CellStyle="{StaticResource STL_DataGridCellReporting}"
ColumnHeaderStyle="{StaticResource STL_DataGridColumnHeaderReporting}"
AlternatingRowBackground="#A8A8A8"
CanUserResizeColumns="False"
>
<DataGrid.Columns>
<DataGridTextColumn Header="AnlV-Nr"
Binding="{Binding AnlVPara}"
Width="60"/>
<DataGridTextColumn Header="gültig ab"
Binding="{Binding TextGueltigAb}"
Width="68"/>
<DataGridTextColumn Header="Text"
Binding="{Binding ParaText}"
Width="750"/>
<DataGridTextColumn Header="Info"
Binding="{Binding Info}"
Width="*"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
<StackPanel Background="#A8A8A8" HorizontalAlignment="Center" Margin="10,268,0,141" Width="1010" >
<Label Content="Bearbeitungsbereich" FontWeight="Bold" FontSize="12" Height="33" />
</StackPanel>
<StackPanel>
<StackPanel Orientation="Horizontal" Background="#A8A8A8" HorizontalAlignment="Center"
Width="1010" Margin="0,294,0,0" Height="31">
<Label Height="25" Width="60" Margin="20, 0, 0, 0" Content="AnlV-Nr.:" />
<ComboBox IsEditable="True" Background="gray" Height="22" Width="69" ItemsSource="{Binding AnlVPara}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding lstAnlVTexte}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Height="15" Margin="10, 0, 0, 0" />
<Label Height="26" Width="122" Content="Editierwarnungen" />
<StackPanel Height="48" Width="100"/>
</StackPanel>
<StackPanel Height="22" Orientation="Horizontal">
<Label Margin="20, 0, 0, 0" Content="gültig ab:" Height="27" />
<TextBox Background="LightGray" Height="20" Width="100" />
</StackPanel>
<StackPanel Height="50" Orientation="Horizontal">
<Label Content="Text:" Height="27" Width="38" Margin="42,0,0,10" />
<TextBox Background="LightGray" Width="500" Height="43" />
</StackPanel>
<StackPanel Orientation="Horizontal" >
<Label Content="Info:" Height="27" Width="38" Margin="42,0,0,0" />
<TextBox Background="LightGray" Width="500" Height="20" />
<Button x:Name="BTN_speichern" Width="80" Height="18" Margin="20,0,0,0" Content="Speichern"
Style="{StaticResource STL_ButtonStandard}" Command="{Binding icdSpeichern}"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
ViewModel:
using Newtonsoft.Json;
using QBondsData.DBModels;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Linq;
namespace QBondsFrontend.ViewModels.Input
{
public class AnlVTexteViewModel : BaseViewModel
{
#region data
private string _AnlVPara;
private DateTime _TextGueltigAb;
private string _ParaText;
private string _Info;
private List<AnlVhistText> _lstAnlVTexte;
private string _search;
#endregion
#region constructor
public AnlVTexteViewModel()
{
icdFilterDelete = new RelayCommand<object>(parameter => filterdelete(), parameter => true);
icdSpeichern = new RelayCommand<object>(parameter => speichern(), parameter => true);
GetData();
}
#endregion
#region members
public ICommand icdFilterDelete { get; set; }
public ICommand icdSpeichern { get; set; }
private string Search
{
get { return _search; }
set
{
_search = value;
OnPropertyChanged("Search");
}
}
public string AnlVPara
{
get
{
return _AnlVPara;
}
set
{
_AnlVPara = value;
OnPropertyChanged("AnlVPara");
}
}
public DateTime TextGueltigAb
{
get
{
return _TextGueltigAb;
}
set
{
_TextGueltigAb = value;
OnPropertyChanged("TextGueltigAb");
}
}
public string ParaText
{
get
{
return _ParaText;
}
set
{
_ParaText = value;
OnPropertyChanged("ParaText");
}
}
public string Info
{
get
{
return _Info;
}
set
{
_Info = value;
OnPropertyChanged("Info");
}
}
public List<AnlVhistText> lstAnlVTexte
{
get { return _lstAnlVTexte; }
set
{
_lstAnlVTexte = value;
OnPropertyChanged("lstAnlVTexte");
}
}
#endregion
#region methods
private void filterdelete()
{
}
private void speichern()
{
}
private async Task GetData()
{
HttpResponseMessage Response = await Globals.SendRequest("AnlVTexte/GetAnlVTexte"
, "GET");
if (Response.IsSuccessStatusCode)
{
lstAnlVTexte = JsonConvert.DeserializeObject<List<AnlVhistText>>
(await Response.Content.ReadAsStringAsync());
}
else
{
lstAnlVTexte = new List<AnlVhistText>();
Application.Current.Dispatcher.Invoke((Action)delegate
{
Globals.CloseReportByHash(this.GetHashCode()
, "Fehler! (HTTP Status " + Response.StatusCode + ")." +
"Kontaktieren Sie den Support.");
});
}
}
#endregion
}
}
When you change the type of lstAnlVTexte to ICollectionView you get two events CurrentChanged and CurrentChanging which you can handle in your viewmodel. From the ICollectionView you can get the CurrentItem.
Like this:
private List<AnlVhistText> _anlVTexte = new List<AnlVhistText>();
public AnlVTexteViewModel()
{
[...]
lstAnlVTexte = new CollectionView(_anlVTexte);
lstAnlVTexte.CurrentChanged += SelectionChanged; // raised after the current item has been changed.
lstAnlVTexte.CurrentChanging += SelectionChanging; // raise before changing the current item. Event handler can cancel this event.
}
private void SelectionChanged(object sender, EventArgs e)
{
var selectedItem = lstAnlVTexte.CurrentItem;
}
private void SelectionChanging(object sender, CurrentChangingEventArgs e)
{
}
private ICollectionView _lstAnlVTexte;
public ICollectionView lstAnlVTexte
{
get { return _lstAnlVTexte; }
set
{
_lstAnlVTexte = value;
OnPropertyChanged("lstAnlVTexte");
}
}
Here's a sample using the community toolkit mvvm and linq.
If you're not familiar, the toolkit does code generation.
This is a simple scenario to illustrate the approach.
Mainwindowviewmodel.
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private int selectedFilterInt = -1;
partial void OnSelectedFilterIntChanged(int newValue)
{
FilteredList = new ObservableCollection<MyItem>(FullList.Where(x=>x.Category == selectedFilterInt).ToList());
}
public List<int> FilterOptions { get; set; } = new List<int> {1,2,3};
private List<MyItem> FullList= new List<MyItem>
{
new MyItem{ Category = 1},
new MyItem{ Category = 1},
new MyItem { Category = 1 },
new MyItem { Category = 2 },
new MyItem { Category = 2 },
new MyItem { Category = 3 }
};
[ObservableProperty]
private ObservableCollection<MyItem> filteredList = new ObservableCollection<MyItem>();
public MainWindowViewModel()
{
FilteredList = new ObservableCollection<MyItem>(FullList);
}
}
There's a full list of all the items.
But a filtered list is going to be bound to the itemssource of my listbox ( equivalent to your datagrid ).
Due to the code generated, when selectedFilterInt changes, OnSelectedFilterIntChanged will be called. It's got a handler listening for property changed of SelectedFilterInt if you dug into the generated code.
That method uses a linq where to filter the full list into filtered list.
Setting that filtered list property raises property changed and the view re reads the new collection.
MainWindow. ( I did mention this is simplified )
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<ComboBox SelectedItem="{Binding SelectedFilterInt}"
ItemsSource="{Binding FilterOptions}"/>
<ListBox ItemsSource="{Binding FilteredList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Category}"/>
<TextBlock Text="{Binding Comment}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</Window>
and MyItem
public partial class MyItem : ObservableObject
{
[ObservableProperty]
private int category = 0;
[ObservableProperty]
private string comment = "Some test string";
}
Which is a bit underwhelming visually but works:
In your code you need to get all the data into a collection.
Call that FulList.
You then need another collection which will be the filtered data.
Call this FilteredList.
Bind itemssource to FilteredList
Initially, you presumably want FilteredList to be = FullList
Then when the user selects something in the combobox you need to react to that.
You could bind selecteditem to a property and act in the setter or handle propertychanged like my code does.
However you do it, you get a new integer.
You then use that to filter FullList into a new collection which will replace the bound FilteredList.
You also need to somehow have one entry per AnlV nr whatever that is in your combobox.
AnlV nr isn't going to work as a property name since it's got a space but it is the equivalent to Category in my sample.
You will use that selected value in the linq.
Substitute the name of that property for Category. Substitute the type of whatever your collection is. Maybe that's AnlVhistText. I'm not sure.

MVVM Property Change in Behavior<TreeView>

I cannot get a change in the UI (TextBox) when selecting a tree item. When I select a tree item, the property and the text should change. But this does not happen. Property changes, but text remains the same. At first I tried to cause a property change in MainViewModel, but ran into the same problem: the property changes but the UI remains unchanged.
public class BindableSelectedItemBehavior : Behavior<TreeView>, INotifyPropertyChanged
{
MainViewModel vm = new MainViewModel();
private string _message;
public string Message
{
get
{
return _message;
}
set
{
_message = value;
OnPropertyChanged("Message");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(nameof(SelectedItem), typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var item = e.NewValue as TreeViewItem;
if (item != null)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject != null)
{
AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
}
private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItem = e.NewValue;
string name = GetName((Item)SelectedItem);
Message = "edgddgdgdg";
}
public string GetName(Item item)
{
string circ = item.Name;
return circ;
}
}
}
<UserControl x:Class="WpfApp3.TreeViewTextChange"
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:WpfApp3"
xmlns:vm="clr-namespace:WpfApp3.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<vm:BindableSelectedItemBehavior/>
</UserControl.DataContext>
<Grid>
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding Path=Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Background="Aqua" Width="200" Height="50"/>
</Grid>
</UserControl>
Used a debugger - property changes but does not change textbox in UserControl.
My MainViewModel
class MainViewModel : BindableBase
{
MainModel mainModel = new MainModel();
private ICollectionView root;
public MainViewModel()
{
OpenSth = new DelegateCommand(DisplayItemTree);
ShowCheck = new DelegateCommand(ShowCheckedItemsCommand);
Sth = new DelegateCommand(Changetext);
TextChengedCommand = new DelegateCommand<object>(textChange);
}
public ICommand OpenSth { get; }
public ICommand ShowCheck { get; }
public ICommand Sth { get; }
public ICommand TextChengedCommand { get; }
private void textChange(object obj)
{
var str = obj as string;
}
public void DisplayItemTree()
{
var itemTree = mainModel.GetItemsTree();
Root = CollectionViewSource.GetDefaultView(itemTree);
Root.Filter = new Predicate<object>((item) =>
{
var realItem = (Item)item;
return true;
});
Root.Refresh();
}
public ICollectionView Root
{
get => root;
set => SetProperty(ref root, value);
}
public void ShowCheckedItemsCommand()
{
var str = new StringBuilder();
List<Item> items = mainModel.FindCheckedItem();
if (items.Count == 0)
{
str.Append("No selected items");
}
else
{
foreach (var item in items)
{
str.Append(item.Name);
}
}
MessageBox.Show(str.ToString());
}
private string _name;
public string TextText
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("TextText");
}
}
public void Changetext()
{
TextText = "dggg";
}
}
}
And xaml code
<Window x:Class="WpfApp3.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:WpfApp3"
xmlns:beh="clr-namespace:WpfApp3.ViewModel"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ii="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:prism="http://www.codeplex.com/prism"
mc:Ignorable="d"
xmlns:vm="clr-namespace:WpfApp3.ViewModel"
xmlns:bh="clr-namespace:WpfApp3.ViewModel"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="200"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel Grid.Column="1" Grid.Row="0" Grid.RowSpan="2">
<TextBox x:Name="searchBox" DockPanel.Dock="Top" Height="30" VerticalAlignment="Top" BorderBrush="Black" BorderThickness="1" Text="{Binding SearchText}">
<ii:Interaction.Triggers>
<ii:EventTrigger EventName="TextChenged">
<ii:InvokeCommandAction Command="{Binding TextChengedCommand}" CommandParameter="{Binding ElementName=searchBox, Path=Text}"/>
</ii:EventTrigger>
</ii:Interaction.Triggers>
</TextBox>
<DockPanel DockPanel.Dock="Bottom">
<Button Content="Get TreeView" Command="{Binding OpenSth}" Width="100"/>
<Button Content="Show Check Item" Command="{Binding ShowCheck}" Width="100"/>
</DockPanel>
<TreeView BorderBrush="Black" BorderThickness="1" ItemsSource="{Binding Root}">
<i:Interaction.Behaviors>
<beh:BindableSelectedItemBehavior SelectedItem="{Binding SelectedNode, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Sub}">
<DockPanel>
<CheckBox IsChecked="{Binding CheckedItem}" Margin="0 8 0 8" VerticalAlignment="Center" DockPanel.Dock="Left"/>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" DockPanel.Dock="Right"/>
</DockPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</DockPanel>
<DataGrid AutoGenerateColumns="False" Height="210" HorizontalContentAlignment="Left" Name="DataGrid" VerticalAlignment="Top" Grid.Column="2" Grid.Row="0" Margin="0,0,0,0" ItemsSource="{Binding ListOfCircuets}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Length}" MinWidth="50"/>
<DataGridTextColumn Binding="{Binding dU}" MinWidth="50"/>
<DataGridTextColumn Binding="{Binding Cable}" MinWidth="50"/>
<DataGridTextColumn Binding="{Binding I}" MinWidth="50"/>
</DataGrid.Columns>
</DataGrid>
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding TextText, Mode=TwoWay}" Background="Aqua" Width="200" Height="50"/>
<Button Content="Show Check Item" Command="{Binding Sth}" Width="100"/>
<local:TreeViewTextChange Grid.Row="1" Grid.Column="2"></local:TreeViewTextChange>
</Grid>
</Window>

WPF MVVM problem with ObservableCollection and INotifyPropertyChanged

I have a problem in adding and deleting items of ObservableCollection. After clicking on binded buttons, SQL Server database successfully updates, but after calling methods WorkerInfoCollection.Add and WorkerInfoCollection.Remove UI isn't updating. Moreover, WorkerInfoCollection.Remove(SelectedWorker) doesn't remove item at all. I can remove this item only if I use LINQ First or Default, but still - there are no changes in UI.
Through many hours of debugging and web searching, I've tried to change buttons Command Parameters: ElementName and Path, using RelativeSource:AncestorType, setting Command value in ViewModel constructor, setting Command value in setter, raise OnPropertyChanged in ObservableCollection, used different modes of Binding, adding and removing items from ObservableCollection, even tried to reinitialize ObservableCollection and fill it directly from database at runtime (kinda stupid). Nothing helped. Searching in web also didn't help the situation.
MainWindow.xaml
<Grid>
<DataGrid Name="WorkerInfoData"
HorizontalAlignment="Stretch"
Margin="10,10,50,10"
VerticalAlignment="Stretch"
ItemsSource="{Binding WorkerInfoCollection}"
SelectedItem="{Binding SelectedWorker,
Mode=TwoWay}">
<DataGrid.Columns>
<DataGridTextColumn Header="ID робітника"
Binding="{Binding WorkerId}" />
<DataGridTemplateColumn Header="Фото"
Width="SizeToCells"
IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding WorkerPhoto}"
Height="75"
Width="75"
Stretch="UniformToFill"
RenderOptions.BitmapScalingMode="Fant"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="ПІБ"
Binding="{Binding WorkerFullName}" />
<...>
<DataGridTextColumn Header="Хоббі"
Binding="{Binding WorkerHobby}" />
</DataGrid.Columns>
</DataGrid>
<StackPanel Panel.ZIndex="3"
Name="addRecordPanel"
Orientation="Horizontal"
HorizontalAlignment="Right"
Margin="0,0,-369,0"
Width="417"
DataContext="{Binding NewWorker,
Mode=TwoWay}">
<Grid>
<Button Name="buttonHideAddRecordPanel"
Width="48"
Height="48"
Click="ButtonHideAddRecordPanel_Click"
Visibility="Hidden"
Margin="0 0 0 100">
<Button.Background>
<SolidColorBrush Color="White"/>
</Button.Background>
<Image Source="Images/ClosePanel.png"
RenderOptions.BitmapScalingMode="Fant"/>
</Button>
<Button Name="buttonShowAddRecordPanel"
Height="48"
Width="48"
Click="ButtonShowAddRecordPanel_Click"
Margin="0 0 0 100">
<Button.Background>
<SolidColorBrush Color="White"/>
</Button.Background>
<Image Source="Images/AddRecord.ico"
RenderOptions.BitmapScalingMode="Fant"/>
</Button>
</Grid>
<Border BorderBrush="DarkGray"
BorderThickness="1.5"
Width="369"
Background="WhiteSmoke"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="0 0 0 10"
SnapsToDevicePixels="True">
<TextBlock Text="Дані нового робітника"
FontSize="16"
Background="Gainsboro"
TextAlignment="Center"
Margin="0"
Padding="0 0 0 3"/>
<TextBlock Text="ID робітника"
Margin="0 10 0 0"/>
<TextBox Text="{Binding WorkerId,
UpdateSourceTrigger=PropertyChanged}"
Margin="0 5"
Width="300"
Background="White"/>
<TextBlock Text="Фото робітника"
Margin="0 5 0 0"/>
<Border BorderBrush="DarkGray"
BorderThickness="1"
Width="300"
Margin="0 5 0 0">
<Image Source="{Binding WorkerPhoto,
NotifyOnTargetUpdated=True,
UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay,
Converter=
{conv:ByteToImage}}"
Height="300"
Stretch="UniformToFill"/>
</Border>
<Button Name="AddNewWorkerPhoto"
Width="150"
FontSize="14"
Content="Завантажити фото"
Command="{Binding AddRecordImage}"
CommandParameter="{Binding
ElementName=addRecordPanel,
Path=DataContext}">
<Button.DataContext>
<VM:WorkerInfoViewModel/>
</Button.DataContext>
</Button>
<...>
<Button Name="AddRecord"
Content="Додати запис"
FontSize="14"
Width="150"
Margin="0 10"
Command="{Binding AddRecord}"
CommandParameter="{Binding
ElementName=addRecordPanel,
Path=DataContext}">
<Button.DataContext>
<VM:WorkerInfoViewModel/>
</Button.DataContext>
</Button>
</StackPanel>
</ScrollViewer>
</Border>
</StackPanel>
<StackPanel Panel.ZIndex="3"
Name="deleteRecordPanel"
Orientation="Horizontal"
HorizontalAlignment="Right"
Margin="0,0,-369,0"
Width="417">
<Grid>
<Button Name="DeleteRecord"
Height="48"
Width="48"
Margin="0 100 0 0"
Command="{Binding DeleteRecord}"
CommandParameter="{Binding
ElementName=WorkerInfoData,
Path=SelectedItem}">
<Button.DataContext>
<VM:WorkerInfoViewModel/>
</Button.DataContext>
<Button.Background>
<SolidColorBrush Color="White"/>
</Button.Background>
<Image Source="Images/DeleteRecord.png"
RenderOptions.BitmapScalingMode="Fant"/>
</Button>
</Grid>
</StackPanel>
</Grid>
WorkerInfo.cs
public class WorkerInfo : INotifyPropertyChanged
{
private int workerId;
<...>
public int WorkerId
{
get { return workerId; }
set
{
workerId = value;
OnPropertyChanged(nameof(WorkerId));
}
<...>
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string prop = "")
{
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(prop));
}
RelayCommand.cs
public class RelayCommand : ICommand
{
private Action<object> execute;
private Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool>
canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute == null || canExecute(parameter);
}
public void Execute(object parameter)
{
execute(parameter);
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string prop = "")
{
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(prop));
}
}
WorkerInfoViewModel.cs
public class WorkerInfoViewModel : ViewModelBase
{
private SqlDataAdapter dataAdapter;
private DataTable dataTable;
private WorkerInfo newWorker;
private WorkerInfo selectedWorker;
public WorkerInfo NewWorker
{
get
{
if (newWorker == null)
{
return newWorker = new WorkerInfo();
}
else return newWorker;
}
set
{
newWorker = value;
}
}
public WorkerInfo SelectedWorker
{
get { return selectedWorker; }
set
{
selectedWorker = value;
OnPropertyChanged("SelectedWorker");
}
}
private ObservableCollection<WorkerInfo> workerInfoCollection;
public ObservableCollection<WorkerInfo> WorkerInfoCollection
{
get { return workerInfoCollection; }
set
{
workerInfoCollection = value;
OnPropertyChanged("WorkerInfoCollection");
}
}
public ICommand AddRecord { get; }
public ICommand AddRecordImage { get; }
public ICommand UpdateRecord { get; }
public ICommand UpdateRecordImage { get; }
private ICommand deleteCommand;
public ICommand DeleteRecord
{
get
{
if (deleteCommand == null)
{
deleteCommand = new RelayCommand(parameter =>
DeleteRecord_Click(parameter));
}
return deleteCommand;
}
}
public WorkerInfoViewModel()
{
string selectQuery = "SELECT * FROM [WorkerInfo]";
using (SqlConnection connection = new
SqlConnection(builder.ConnectionString))
{
using (SqlCommand command = new SqlCommand(selectQuery,
connection))
{
connection.Open();
using (dataAdapter = new SqlDataAdapter(command))
{
dataTable = new DataTable();
dataAdapter.Fill(dataTable);
WorkerInfoCollection = new
ObservableCollection<WorkerInfo>();
foreach (DataRow dataRow in dataTable.Rows)
{
WorkerInfoCollection.Add
(
new WorkerInfo()
{
WorkerId =
Convert.ToInt32(dataRow["WorkerId"]),
<...>
WorkerHobby =
dataRow["WorkerHobby"].ToString()
}
);
}
}
connection.Close();
}
}
AddRecord = new RelayCommand(parameter =>
AddRecord_Click(parameter));
AddRecordImage = new RelayCommand(parameter =>
AddWorkerPhoto_Click(parameter));
UpdateRecord = new RelayCommand(parameter =>
UpdateRecord_Click(parameter));
UpdateRecordImage = new RelayCommand(parameter =>
UpdateWorkerPhoto_Click(parameter));
//DeleteRecord = new RelayCommand(parameter =>
DeleteRecord_Click(parameter));
}
private void AddRecord_Click(object parameter)
{
NewWorker = (WorkerInfo)parameter;
WorkerInfoCollection.Add
(
new WorkerInfo()
{
WorkerId = NewWorker.WorkerId,
<...>
WorkerHobby = NewWorker.WorkerHobby
}
);
string insertQuery = "INSERT INTO [WorkerInfo] (" +
"WorkerId, " +
<...>
"#WorkerHobby)";
using (SqlConnection connection = new
SqlConnection(builder.ConnectionString))
{
connection.Open();
using (SqlCommand command = new SqlCommand(insertQuery,
connection))
{
try
{
command.Parameters.AddWithValue("#WorkerId",
NewWorker.WorkerId);
<...>
command.Parameters.AddWithValue("#WorkerHobby",
NewWorker.WorkerHobby);
command.ExecuteNonQuery();
connection.Close();
}
catch (Exception ex)
{
MessageBox.Show("error");
log.Error(ex);
}
}
}
}
private void DeleteRecord_Click(object parameter)
{
SelectedWorker = (WorkerInfo)parameter;
string deleteRecord = "DELETE FROM [WorkerInfo] " +
"WHERE WorkerId = #WorkerId";
if (SelectedWorker != null)
{
using (SqlConnection connection = new
SqlConnection(builder.ConnectionString))
{
connection.Open();
using (SqlCommand command = new SqlCommand(deleteRecord,
connection))
{
try
{
command.Parameters.AddWithValue("#WorkerId",
SelectedWorker.WorkerId);
command.ExecuteNonQuery();
connection.Close();
WorkerInfoCollection.Remove(SelectedWorker);
}
catch (Exception ex)
{
MessageBox.Show("error");
log.Error(ex);
}
}
}
}
}
When you do:
<Button.DataContext>
<VM:WorkerInfoViewModel/>
</Button.DataContext>
That instantiates another instance of workerinfo viewmodel.
Remove that.
You want to be adding and removing from the same observablecollection in the same viewmodel instance the itemssource of your datagrid is bound to.
As it is, you have at least 3 instances of that viewmodel.
Make sure you just have the one instance of your viewmodel and that is the datacontext of mainwindow and hence the buttons and datagrid.

WPF MVVM ComboBox data binding

I am trying to create a simple WPF application and bind data to combobox but I am not having any luck. My PeriodList is getting populated fine but is not getting bind to the combobox. Do I need to set the DataContext in XAML or in code behind? Please help, I am very confused.
Here is my XAML
<UserControl x:Class="FinancialControlApp.KeyClientReportView"
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:FinancialControlApp"
mc:Ignorable="d"
d:DesignHeight="300" Width="630">
<UserControl.Resources>
<!-- DataTemplate (View) -->
<DataTemplate DataType="{x:Type local:KeyClientReportModel}">
</DataTemplate>
</UserControl.Resources>
<DockPanel Margin="20">
<DockPanel DockPanel.Dock="Top" VerticalAlignment="Center">
<TextBlock Margin="10,2" DockPanel.Dock="Left" Text="Start Period" VerticalAlignment="Center" />
<ComboBox Name="cmbStartPeriod" Margin="10,2" Width="112" VerticalAlignment="Center" ItemsSource="{Binding PeriodList}">
</ComboBox>
<TextBlock Margin="10,2" DockPanel.Dock="Left" Text="End Period" VerticalAlignment="Center" />
<ComboBox Name="cmbEndPeriod" Margin="10,2" Width="112" VerticalAlignment="Center" ItemsSource="{Binding PeriodList}" />
<!--<Button Content="Save Product" DockPanel.Dock="Right" Margin="10,2" VerticalAlignment="Center"
Command="{Binding Path=SaveProductCommand}" Width="100" />-->
<Button Content="Run" DockPanel.Dock="Left" Margin="10,2"
Command="{Binding Path=GetProductCommand}" IsDefault="True" Width="100" />
</DockPanel>
<!--<ContentControl Margin="10" Content="{Binding Path=PeriodName}" />-->
<ContentControl Margin="10"></ContentControl>
</DockPanel>
</UserControl>
Here is my model
namespace FinancialControlApp
{
public class KeyClientReportModel : ObservableObject
{
private string _periodName;
public string PeriodName
{
get { return _periodName; }
set
{
if (value != _periodName)
{
_periodName = value;
OnPropertyChanged("PeriodName");
}
}
}
List<KeyClientReportModel> _periodList = new List<KeyClientReportModel>();
public List<KeyClientReportModel> PeriodList
{
get { return _periodList; }
set
{
_periodList = value;
OnPropertyChanged("PeriodList");
}
}
}
}
And here is my ViewModel
namespace FinancialControlApp
{
public class KeyClientReportViewModel : ObservableObject, IPageViewModel
{
private KeyClientReportModel _currentPeriod;
private ICommand _getReportCommand;
private ICommand _saveReportCommand;
public KeyClientReportViewModel()
{
GetPeriod();
}
public string Name
{
get { return "Key Client Report"; }
}
public ObservableCollection<KeyClientReportModel> _periodName;
public ObservableCollection<KeyClientReportModel> PeriodName
{
get { return _periodName; }
set
{
if (value != _periodName)
{
_periodName = value;
OnPropertyChanged("PeriodName");
}
}
}
private void GetPeriod()
{
DataSet ds = new DataSet();
DataTable dt = new DataTable();
Helper_Classes.SQLHelper helper = new Helper_Classes.SQLHelper();
ds = helper.getPeriod();
dt = ds.Tables[0];
PeriodName = new ObservableCollection<KeyClientReportModel>();
foreach (DataRow dr in dt.Rows)
{
var period = dr["Period"].ToString();
if (period != null)
{
PeriodName.Add(new KeyClientReportModel { PeriodName = period });
}
//p.PeriodName = dr["Period"].ToString();
}
}
}
}
UPDATE: So I attach a Value Converter to Break into the Debugger and here is what I see. I see
I see 5 items in the list
Change
ItemsSource="{Binding KeyClientReportModel.PeriodList}"
To:
ItemsSource="{Binding PeriodList}"
Make sure your ViewModel is set to the DataContext property of your view.
Set the combobox DisplayMemberPath to the property Name of your KeyClientReportViewModel class.
Or alternatively override the .ToString() method inside the KeyClientReportViewModel class in order to provide the Combobox item display text.
This can help you
------View
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<!-- To get the ViewModel -->
xmlns:viewmodels="clr-namespace:WpfApp1.ViewModels"
Title="MainWindow">
<Window.DataContext>
<!-- Assigning the ViewModel to the View -->
<viewmodels:MainWindowViewModel />
</Window.DataContext>
<DockPanel VerticalAlignment="Center"
DockPanel.Dock="Top">
<TextBlock Margin="10,2"
VerticalAlignment="Center"
DockPanel.Dock="Left"
Text="Start Period" />
<ComboBox Name="cmbStartPeriod"
Width="112"
Margin="10,2"
VerticalAlignment="Center"
ItemsSource="{Binding PeriodName}" // Items in the ViewModel
DisplayMemberPath="Name"/> // Property to display
</DockPanel>
</Window>
------- ViewModel
public class MainWindowViewModel
{
public MainWindowViewModel()
{
var items = new List<KeyClientReportModel>
{
new KeyClientReportModel
{
Name = "First",
Value = 1
},
new KeyClientReportModel
{
Name = "Second",
Value = 1
}
};
PeriodName = new ObservableCollection<KeyClientReportModel>(items);
}
// You don't need to notify changes here because ObservableCollection
// send a notification when a change happens.
public ObservableCollection<KeyClientReportModel> PeriodName { get; set; }
}
public class KeyClientReportModel
{
public int Value { get; set; }
public string Name { get; set; }
}

WPF MVVM GridView binding of cell values to ViewModel properties

MVVM newbie looking for a bit of guidance about datagrid binding. I have difficulty with setting up the properties where I can see that the contents of a datagrid cell has changed. I started out simple and am trying to set up an updateable country list with currency codes. Is there a way I can bind my grid column cells to properties in the viewmodel? So far I have just introduced a property for CurrencyCode, but I never execute the setter. What am I doing wrong?
Model:
using System.Linq;
using System.Collections.ObjectModel;
namespace AdminTool.DataModel.Repositories
{
public class AccountingRepository : BaseRepository
{
private AdminToolEntities entities = new AdminToolEntities();
// Country List
public ObservableCollection<CountryData> GetCountryList()
{
var result = (from c in entities.Countries
from u in entities.Currencies.Where(u => u.ID == c.CurrencyID).DefaultIfEmpty()
select new CountryData { IsChanged = false,
ID = c.ID,
CountryName = c.CountryName,
CountryCodeISO = c.CountryCodeISO,
CurrencyCode = u.CurrencyCode})
.OrderBy(o => o.CountryName)
.ToList();
return new ObservableCollection<CountryData>(result);
}
public void SaveCountryList(ObservableCollection<CountryData> countryList)
{
var result = (from l in countryList
from c in entities.Countries.Where(c => c.ID == l.ID)
from u in entities.Currencies.Where(u => u.CurrencyCode == l.CurrencyCode).DefaultIfEmpty()
where l.IsChanged
select l)
.ToList();
foreach (CountryData cd in result)
{
Country c = (Country)entities.Countries.Where(l => l.ID == cd.ID).FirstOrDefault();
if (c == null) // new entry
{
c = new Country();
entities.Countries.Add(c);
}
c.CountryName = cd.CountryName;
c.CountryCodeISO = cd.CountryCodeISO;
c.Currency = entities.Currencies.Where(u => u.CurrencyCode == cd.CurrencyCode).FirstOrDefault();
}
entities.SaveChanges();
}
}
/// <summary>
/// Data structures
/// </summary>
public class CountryData
{
public int ID { get; set; }
public string CountryName { get; set; }
public string CountryCodeISO { get; set; }
public string CurrencyCode { get; set; }
public bool IsChanged { get; set; }
}
}
View:
<Window x:Class="AdminTool.Desktop.View.CountryList"
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:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:AdminTool.Desktop.ViewModel"
Title="AdminTool" Height="600" Width="500">
<Window.DataContext>
<vm:AccountingViewModel/>
</Window.DataContext>
<Grid>
<DataGrid Name="dgCountryList"
ItemsSource="{Binding CountryList, Mode=TwoWay}"
SelectedItem="{Binding Path=SelectedCountry, Mode=TwoWay}"
HorizontalAlignment="Left" VerticalAlignment="Top" Height="450" Width="450" Margin="20,20,0,0"
AutoGenerateColumns="False"
SelectionMode="Single"
SelectionUnit="FullRow"
GridLinesVisibility="All"
CanUserDeleteRows="True"
CanUserAddRows="True">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChanged}" Value="true" >
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<!--<DataGridTextColumn Header="ID" Binding="{Binding ID}" Width="50" />-->
<DataGridTextColumn Header="Country" Binding="{Binding Path=CountryName, Mode=TwoWay}" Width="250" />
<DataGridTextColumn Header="ISO Code" Binding="{Binding Path=CountryCodeISO, Mode=TwoWay}" Width="80" />
<DataGridTextColumn Header="Currency" Binding="{Binding Path=CurrencyCode, Mode=TwoWay}" Width="80" />
</DataGrid.Columns>
</DataGrid>
<Button Content="Save" Command="{Binding Path=SaveCommand}" IsEnabled="{Binding Path=UpdateButtonEnabled}" HorizontalAlignment="Left" Margin="223,512,0,0" VerticalAlignment="Top" Width="75"/>
<Button Content="Cancel" Command="{Binding Path=CancelCommand}" HorizontalAlignment="Left" Margin="343,512,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
ViewModel:
using System.Collections.ObjectModel;
using AdminTool.DataModel.Repositories;
using AdminTool.Helpers;
namespace AdminTool.Desktop.ViewModel
{
public class AccountingViewModel : ViewModelBase
{
private ObservableCollection<CountryData> _countryList;
public ObservableCollection<CountryData> CountryList
{
get { return _countryList; }
set
{
_countryList = value;
RaisePropertyChanged("CountryList");
}
}
private CountryData _selectedCountry;
public CountryData SelectedCountry
{
get { return _selectedCountry; }
set
{
_selectedCountry = value;
RaisePropertyChanged("SelectedCountry");
}
}
private string currencyCode;
public string CurrencyCode
{
get { return currencyCode; }
set {
currencyCode = value;
SelectedCountry.IsChanged = true;
}
}
private bool updateButtonEnabled = false;
public bool UpdateButtonEnabled
{
get { return updateButtonEnabled; }
set { updateButtonEnabled = value;
RaisePropertyChanged("UpdateButtonEnabled");
}
}
#region Commands -----------------------------------------------------------------------------------
public RelayCommand SaveCommand { get; set; }
public RelayCommand CancelCommand { get; set; }
#endregion
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public AccountingViewModel()
{
SaveCommand = new RelayCommand(Save);
CancelCommand = new RelayCommand(Cancel);
AccountingRepository repo = new AccountingRepository();
CountryList = repo.GetCountryList();
}
#region Public methods -----------------------------------------------------------------------------
void Save(object parameter)
{
AccountingRepository repo = new AccountingRepository();
repo.SaveCountryList(CountryList);
}
void Cancel(object parameter)
{
}
#endregion
}
}
Thanks in advance for any help.
you have to handle the property changed event:
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
You need to make changes in model. Implement notify interface as given below.
Model - With name property being updated
public class CountryData : INotifyPropertyChanged
{
private string countryName;
public string CountryName
{
get { return countryName; }
set
{
countryName = value;
RaisePropertyChanged("CountryName");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
}
View - Use UpdateSourceTrigger=PropertyChanged with Bindings in grid.
<Grid>
<DataGrid Name="dgCountryList"
ItemsSource="{Binding CountryList, Mode=TwoWay}"
SelectedItem="{Binding Path=SelectedCountry, Mode=TwoWay}"
HorizontalAlignment="Left" VerticalAlignment="Top" Height="450" Width="450" Margin="20,20,0,0"
AutoGenerateColumns="False"
SelectionMode="Single"
SelectionUnit="FullRow"
GridLinesVisibility="All"
CanUserDeleteRows="True"
CanUserAddRows="True">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChanged}" Value="true" >
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<!--<DataGridTextColumn Header="ID" Binding="{Binding ID}" Width="50" />-->
<DataGridTextColumn Header="Country" Binding="{Binding Path=CountryName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="250" />
</DataGrid.Columns>
</DataGrid>
<Button Content="Save" Command="{Binding Path=SaveCommand}" IsEnabled="{Binding Path=UpdateButtonEnabled}" HorizontalAlignment="Left" Margin="223,512,0,0" VerticalAlignment="Top" Width="75"/>
<Button Content="Cancel" Command="{Binding Path=CancelCommand}" HorizontalAlignment="Left" Margin="343,512,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>

Categories