My previous post about detecting property changes in the VM wasn't in depth enough, so I'm posting this
I have a grid of Jobs. Each job can have one or more employees.
The DataGrid's RowDetailsTemplate contains another grid to show the employees. So to parent grid is bound to a list of Jobs. The inner grid is bound to a list of Employees that is on the Job model.
The Job Model:
public class Job : _Base
{
private string _JobName = string.Empty;
public string JobName
{
get { return _JobName; }
set
{
if (_JobName != value)
{
_JobName = value;
RaisePropertyChanged("JobName");
}
}
}
private string _JobNumber = string.Empty;
public string JobNumber
{
get { return _JobNumber; }
set
{
if (_JobNumber != value)
{
_JobNumber = value;
RaisePropertyChanged("JobNumber");
}
}
}
private ObservableCollection<Employee> _Employees;
public ObservableCollection<Employee> Employees
{
get { return _Employees; }
set
{
if (_Employees != value)
{
if (_Employees != value)
{
_Employees = value;
RaisePropertyChanged("Employees");
}
}
}
}
private Employee _SelectedEmployee;
public Employee SelectedEmployee
{
get { return _SelectedEmployee; }
set
{
if (_SelectedEmployee != value)
{
if (_SelectedEmployee != value)
{
_SelectedEmployee = value;
RaisePropertyChanged("SelectedEmployee");
}
}
}
}
public Job()
{
Employees = new ObservableCollection<Employee>();
}
}
The Employee model
public class Employee : _Base
{
private string _EmployeeName = string.Empty;
public string EmployeeName
{
get { return _EmployeeName; }
set
{
if (_EmployeeName != value)
{
_EmployeeName = value;
RaisePropertyChanged("EmployeeName");
}
}
}
private bool _IsChecked = false;
public bool IsChecked
{
get { return _IsChecked; }
set
{
if (_IsChecked != value)
{
_IsChecked = value;
RaisePropertyChanged("IsChecked");
}
}
}
}
The XAML
<DataGrid ItemsSource="{Binding Jobs}"
SelectedItem="{Binding SelectedJob}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Job Name" Binding="{Binding JobName}" />
<DataGridTextColumn Header="Job Number" Binding="{Binding JobNumber}" />
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<DataGrid ItemsSource="{Binding Employees}"
SelectedItem="{Binding SelectedEmployee}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsChecked}"/>
<DataGridTextColumn Binding="{Binding EmployeeName}"/>
</DataGrid.Columns>
</DataGrid>
<Button Margin="5"
Height="23"
Width="75"
HorizontalAlignment="Left"
Content="Remove"/>
</StackPanel>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
The MainWindowViewModel
public class MainWindowViewModel : _Base
{
private ObservableCollection<Job> _Jobs;
public ObservableCollection<Job> Jobs
{
get { return _Jobs; }
set
{
if (_Jobs != value)
{
if (_Jobs != value)
{
_Jobs = value;
RaisePropertyChanged("Jobs");
}
}
}
}
private Job _SelectedJob;
public Job SelectedJob
{
get { return _SelectedJob; }
set
{
if (_SelectedJob != value)
{
if (_SelectedJob != value)
{
_SelectedJob = value;
RaisePropertyChanged("SelectedJob");
}
}
}
}
public MainWindowViewModel()
{
this.PropertyChanged += new PropertyChangedEventHandler(MainWindowViewModel_PropertyChanged);
}
void MainWindowViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName.Trim().ToLower() == "ischecked")
{
int x = 1;
}
}
}
I have a couple of questions:
1) The SelectedEmployee property on the Job model does not fire when I click an employee in the inner grid.
2) The MainWindowViewModel_PropertyChanged does not fire when an employee is selected.
3) Notice the button below the inner grid. How do I bind its command to MainWindowVM?
As you have DataGridinside DataGrid's row, so the above DataGrid is somehow eating up the selectionchange for the inner DataGrid. To solve this, you will need to forcefully update the binding source for the child DataGrid. For doing so capture the SelectionChanged event for the inner DataGrid and in the handler do the following.
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DataGrid grid = e.OriginalSource as DataGrid;
var expression = grid.GetBindingExpression(DataGrid.SelectedItemProperty);
expression.UpdateSource();
}
MainwindowVM does not have any ischecked property thats why its propertychanged for that property is not firing.
To solve this you need to do couple of things. The problem is DataGrid cell and rows does not inherit the DataContext as they don't come under its visual tree. So to solve this you will have to use the BindingProxy to take the windows DataContext to your button in rowdetails of DataGrid
Define Binding Proxy class as below:
public class MyBindingProxy : Freezable
{
public static readonly DependencyProperty BindingDataProperty =
DependencyProperty.Register("BindingData", typeof(object),
typeof(MyBindingProxy), new UIPropertyMetadata(null));
protected override Freezable CreateInstanceCore()
{
return new MyBindingProxy();
}
public object BindingData
{
get { return (object)GetValue(BindingDataProperty); }
set { SetValue(BindingDataProperty, value); }
}
}
Now in the resources of your window (where DataGrid is) create the instance of proxy and set the BindingData to the DataContext of the Window i.e MainWindowViewModel as:
<Window.Resources>
<local:MyBindingProxy x:Key="myproxy" BindingData="{Binding}" />
</Window.Resources>
Now just set command as below on the button:
<Button Margin="5"
Height="23"
Width="75"
HorizontalAlignment="Left"
Content="Remove"
Command="{Binding BindingData.MyCommand, Source={StaticResource myproxy}}"/>
Related
new to WPF and programming here I am trying to update an item in a ListView when a button is pushed if the item already exists in that ListView, for example a user inputs CAR and CAR is already in the ListView the CAR count should go from 1 to 2.
This is my InventoryItem class:
public class InventoryItem
{
public string Name { get; set; }
public int Count { get; set; }
}
Here is my ViewModel
public class ViewModel : ViewModelBase
{
private InventoryItem _item;
private int _count;
private ObservableCollection<InventoryItem> _inventoryItems;
private ICommand _addItem;
public InventoryItem Item
{
get
{
return _item;
}
set
{
_item = value;
NotifyPropertyChanged("Item");
}
}
public int Count
{
get { return _count; }
set { _count = value; NotifyPropertyChanged("Count"); }
}
public ObservableCollection<InventoryItem> InventoryItems
{
get
{
return _inventoryItems;
}
set
{
_inventoryItems = value;
NotifyPropertyChanged("Items");
}
}
public ICommand AddItem
{
get
{
if (_addItem == null)
{
_addItem = new RelayCommand(ParamArrayAttribute => this.Submit(), null);
}
return _addItem;
}
}
public ViewModel()
{
Item = new InventoryItem();
InventoryItems = new ObservableCollection<InventoryItem>();
InventoryItems.CollectionChanged += new NotifyCollectionChangedEventHandler(InventoryItems_CollectionChanged);
}
void InventoryItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("Items");
NotifyPropertyChanged("Count");
}
private void Submit()
{
if (InventoryItems.Any(p => p.Name == Item.Name))
{
InventoryItems.First(x => x.Name == Item.Name).ItemCount++;
}
else
{
InventoryItems.Add(Item);
}
}
}
Here is my View
<ListView x:Name="listBox" ItemsSource="{Binding InventoryItems}" Grid.Row="0" HorizontalAlignment="Stretch" Height="Auto" Margin="10,10,10,60" VerticalAlignment="Stretch">
<ListView.Resources>
<Style TargetType="GridViewColumnHeader">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Name}" Width="Auto"/>
<GridViewColumn DisplayMemberBinding="{Binding ItemCount}" Width="Auto"/>
</GridView>
</ListView.View>
</ListView>
Whenever an item that exists in the list is typed in it appears that the InventoryItems ObservableCollection is being updated however InventoryItems_CollectionChanged event is not being fired so the ListView is not being updated. Isn't the collection changing so that event should be fired to update the ListView or am I not understanding the Binding and the event?
You need to notify about property changes in the InventoryItem class to update the changes of ItemCount in the ListView.
Quick solution:
public class InventoryItem : ViewModelBase
{
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
NotifyPropertyChanged(nameof(Name));
}
}
}
private string _name;
public int ItemCount
{
get { return _itemCount; }
set { _itemCount = value; NotifyPropertyChanged(nameof(ItemCount));
}
}
private int _itemCount;
}
}
The ObservableCollection class already contains handling of INotifyCollectionChanged.
You only need this line for the ObservableCollection. You can remove everything else that has to do with "InventoryItems" and the collection in your ViewModel.
public ObservableCollection<InventoryItem> InventoryItems { get; } = new ObservableCollection<InventoryItem>();
Also: When you add an item to the collection you need to create a new item. Otherwise you are adding the same object, that will not work.
This is my reduced ViewModel to how I think you want it to work:
public class ViewModel : ViewModelBase
{
private ICommand _addItem;
public string InputName
{
get { return _inputName; }
set
{
if (_inputName != value)
{
_inputName = value;
NotifyPropertyChanged(nameof(InputName));
}
}
}
private string _inputName;
public ObservableCollection<InventoryItem> InventoryItems { get; } = new ObservableCollection<InventoryItem>();
public ICommand AddItem
{
get
{
if (_addItem == null)
{
_addItem = new RelayCommand(ParamArrayAttribute => this.Submit(), null);
}
return _addItem;
}
}
private void Submit()
{
if (InventoryItems.Any(p => p.Name == InputName))
{
InventoryItems.First(x => x.Name == InputName).ItemCount++;
}
else
{
InventoryItems.Add(new InventoryItem() { Name = InputName, ItemCount = 1 });
}
}
}
To complete the picture, I have added following XAML for test:
<TextBox Text="{Binding InputName}" MinWidth="100" Margin="5"/>
<Button Content="Add" Command="{Binding AddItem}" Margin="5"/>
I have a simple ListBox and a TextBox as under.I want to display the selectedvalue property of Listbox in the textbox,but my ViewModel's selected object is always null.
What am i missing here?
My XAML
<StackPanel>
<Canvas>
<TextBox x:Name="TxtMail" Width="244" FontSize="14" Canvas.Left="36" Canvas.Top="34" Height="20" Text="{Binding CurrentRec.Name,Mode=OneWay}" />
<ListBox x:Name="AllMatching" Width="{Binding ElementName=TxtMail,Path=Width}" Height="100" Canvas.Top="54" Canvas.Left="36" DisplayMemberPath="Name" SelectedItem="{Binding CurrentRec,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectedValue="Name" SelectedValuePath="Name" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto" />
<Button Content="Test" x:Name="cmdtest" Click="cmdtest_Click"/>
</Canvas>
My ViewModel:
public class VM_Data : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int p_ID;
public double p_SP, p_CP;
public string p_Name;
public List<DM_Data> AllData;
public DM_Data CurrentRec;
public VM_Data()
{
LoadData();
}
public int ID
{
get { return p_ID; }
set
{
if (p_ID != value)
{
RaisePropertyChangedEvent("ID");
p_ID = value;
}
}
}
public double SP
{
get { return p_SP; }
set
{
if (p_SP != value)
{
RaisePropertyChangedEvent("SP");
p_SP = value;
}
}
}
public double CP
{
get { return p_CP; }
set
{
if (p_CP != value)
{
RaisePropertyChangedEvent("CP");
p_CP = value;
}
}
}
public string Name
{
get { return p_Name; }
set
{
if (p_Name != value)
{
RaisePropertyChangedEvent("Name");
p_Name = value;
}
}
}
private void LoadData()
{
AllData = new List<DM_Data>();
string[] strNames = "Jatinder;Shashvat;shashikala;shamsher;shahid;justin;jatin;jolly;ajay;ahan;vijay;suresh;namita;nisha;negar;zenith;zan;zen;zutshi;harish;hercules;harman;ramesh;shashank;mandeep;aman;amandeep;amarjit;asim;akshay;amol;ritesh;ritivik;riz;samana;samaira;bhagwandass;bhagwan;bhawna;bhavna".Split(';');
for(int i=0;i<=strNames.GetUpperBound(0);i++)
{
DM_Data NewRec = new DM_Data();
NewRec.CP = new Random().Next(200, 400);
NewRec.SP = new Random().Next(1, 10);
NewRec.ID = i + 1;
NewRec.Name = strNames[i];
AllData.Add(NewRec);
}
AllData = AllData.OrderBy(item => item.Name).ToList();
}
private void RaisePropertyChangedEvent(string Property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(Property));
}
}
}
My DataModel
public class DM_Data
{
public int p_ID;
public double p_SP, p_CP;
public string p_Name;
public int ID
{
get { return p_ID; }
set { p_ID = value; }
}
public double SP
{
get { return p_SP; }
set { p_SP = value; }
}
public double CP
{
get { return p_CP; }
set { p_CP = value; }
}
public string Name
{
get { return p_Name; }
set { p_Name = value; }
}
MainWindow.Xaml.cs
public partial class MainWindow : Window
{
VM_Data ViewModel;
public MainWindow()
{
InitializeComponent();
ViewModel = new VM_Data();
this.DataContext = ViewModel;
AllMatching.ItemsSource = ViewModel.AllData;
}
private void cmdtest_Click(object sender, RoutedEventArgs e)
{
DM_Data crec = ViewModel.CurrentRec;
}
}
CurrentRec must be a property that raises the PropertyChanged event:
private DM_Data _currentRec;
public DM_Data CurrentRec
{
get { return _currentRec; }
set { _currentRec = value; RaisePropertyChangedEvent("CurrentRec"); }
}
In the code you have posted, it is a field and you cannot bind to fields:
public DM_Data CurrentRec;
You can't bind to fields! CurrentRec must be a property. At now it is a field.
Why do you set ItemsSource in code-behind? Set it in XAML.
You should call RaisePropertyChangedEvent after you've changed backing field, not before.
It is not right pattern for events raising: if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(Property)); } You need to save event delegate to variable first or use ?.Invoke.
Don't create new instances of Random on every iteration of loop because you will get equal values. Create the only one outside of the loop and use it.
What you are doing is kind of MVVM, but not really.
Here is a quick fix anyway:
Please take a look at the Bindings.
<StackPanel>
<Canvas>
<TextBox x:Name="TxtMail" Width="244" FontSize="14" Canvas.Left="36" Canvas.Top="34" Height="20" Text="{Binding ElementName=AllMatching, Path=SelectedItem.Name}" />
<ListBox x:Name="AllMatching"
Width="{Binding ElementName=TxtMail,Path=Width}"
Height="100"
Canvas.Top="54"
Canvas.Left="36"
DisplayMemberPath="Name"
SelectedItem="{Binding CurrentRec,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto" />
<Button Content="Test" x:Name="cmdtest" Click="cmdtest_Click"/>
</Canvas>
</StackPanel>
I think you get the idea at: Text="{Binding ElementName=AllMatching, Path=SelectedItem.Name}".
Aditional Information
First:
You fire to early dude. Please first assign the value and then say its changed.
if (p_Name != value)
{
RaisePropertyChangedEvent("Name");
p_Name = value;
}
Second:
Use a ObservableCollection<DM_Data> to let your ListBox know about changes.
Third:
Use the posibility of Binding
Remove AllMatching.ItemsSource = ViewModel.AllData; and go like
<ListBox x:Name="AllMatching"
ItemsSource="{Binding Path=AllData}"
...
/>
And after all of this - please man check out some tutorials. And also refactor your code from VM_Data to DataViewModel thank you sir.
I have below class hierarchy.
public class RowViewModel : BaseViewModel
{
private CellViewModel cell;
public CellViewModel Cell
{
get { return cell; }
set
{
cell = value;
OnPropertyChanged("Cell");
}
}
}
public class CellViewModel : BaseViewModel
{
public string Text { get { return string.Join("\n", CellLinks.Select(c => c.Text)); } }
private ObservableCollection<CellLinkViewModel> cellLinks;
public ObservableCollection<CellLinkViewModel> CellLinks
{
get
{ return cellLinks; }
set
{
cellLinks = value;
OnPropertyChanged("CellLinks");
}
}
}
public class CellLinkViewModel : BaseViewModel
{
public string Text
{
get { return CellValue.Text; }
set
{
CellValue.Text = value;
OnPropertyChanged("Text");
}
}
public CellValueViewModel CellValue { get; set; }
}
public class CellValueViewModel : BaseViewModel
{
public string Text { get; set; }
}
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, e);
}
}
}
XAML code looks like,
<DataGrid ItemsSource="{Binding Rows}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="#" Width="200">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Cell.Text}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Cell.CellLinks}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Text, Mode=TwoWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
CellValueViewModel is shared across multiple CellLinkViewModel. When I change CellValueViewModel it should have propagated changes to all dependent parents and DataGrid should show all latest values.
This is result
But I think its not happening automatically, am I missing something here? How to notify all datagrid cells to update automatically when the nested objects gets updated.
You need to hook up event handlers to the PropertyChanged event for all CellLinkViewModel objects and raise the PropertyChanged event for the Text property of the CellViewModel whenever a link is modified, e.g.:
public class CellViewModel : BaseViewModel
{
public string Text { get { return string.Join("\n", CellLinks.Select(c => c.Text)); } }
private ObservableCollection<CellLinkViewModel> cellLinks;
public ObservableCollection<CellLinkViewModel> CellLinks
{
get
{
return cellLinks;
}
set
{
cellLinks = value;
if(cellLinks != null)
{
foreach(var link in cellLinks)
{
link.PropertyChanged += Link_PropertyChanged;
}
}
OnPropertyChanged("CellLinks");
}
}
private void Link_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged("Text");
}
}
You probably also want to handle the CollectionChanged event for the ObservableCollection<CellLinkViewModel> and hook up event handlers to dynamically added CellLinkViewModel objects as well.
Goal is to have multiple Combobox, once the item is selected in any of the Combobox, it should be removed or hided from other Comboboxes for selection.
I'm able to change the source whenever the selection got changed on any of the Comboboxes. But Problem is when I remove the selected item from the ItemsSource, item got removed from all the sources including the one on which I selected.
Here is the sample
xaml
<StackPanel>
<ComboBox Width="100" Height="50" SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding ComboboxItems}" />
<ComboBox Width="100" Height="50" SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding ComboboxItems}" />
<ComboBox Width="100" Height="50" SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding ComboboxItems}" />
<ComboBox Width="100" Height="50" SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding ComboboxItems}" />
</StackPanel>
Codebehind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ObservableCollection<string> s = new ObservableCollection<string>();
s.Add("item1");
s.Add("item2");
s.Add("item3");
s.Add("item4");
s.Add("item5");
s.Add("item6");
DataContext = new MainViewModel() { ComboboxItems = s , SelectedItem = "item2" };
}
}
public class MainViewModel : INotifyPropertyChanged
{
private ObservableCollection<string> comboboxitems;
public ObservableCollection<string> ComboboxItems
{
get { return comboboxitems; }
set { comboboxitems = value; OnPropertyChanged("ComboboxItem"); }
}
private string _selectedItem;
public string SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
comboboxitems.Remove(value); //here removing selected item from itemssource
OnPropertyChanged("SelectedItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propname)
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propname));
}
}
I know I can have multiple Collection for each Combobox but that may take lot of memory if number of Combobox increases.
Hope, there should be a easy way to achieve this in WPF.
You could define different SelectedItem for each ComboBox, and then create a wrapper of the SharedItemSource for each ComboBoxto filter out the SelectedItem of other ComboBox. eg:
C# :
public IEnumerable<string> ComboboxItems1
{
get
{
return ComboboxItems.Where(x => x != SelectedItem2 && x != SelectedItem3);
}
}
public string SelectedItem1
{
get { return _selectedItem1; }
set
{
if (_selectedItem1 != value)
{
_selectedItem1 = value;
RaisePropertyChanged("SelectedItem1");
RaisePropertyChanged("ComboboxItems2"); //raise propertychanged to refresh GUI
RaisePropertyChanged("ComboboxItems3");
}
}
}
public IEnumerable<string> ComboboxItems2
{
get
{
return ComboboxItems.Where(x => x!=SelectedItem1&&x!=SelectedItem3);
}
}
public string SelectedItem2
{
get { return _selectedItem2; }
set
{
if (_selectedItem2 != value)
{
_selectedItem2 = value;
RaisePropertyChanged("SelectedItem2");
RaisePropertyChanged("ComboboxItems1"); //raise propertychanged to refresh GUI
RaisePropertyChanged("ComboboxItems3");
}
}
}
public IEnumerable<string> ComboboxItems3
{
get
{
return ComboboxItems.Where(x => x != SelectedItem1 && x != SelectedItem2);
}
}
public string SelectedItem3
{
get { return _selectedItem3; }
set
{
if (_selectedItem3 != value)
{
_selectedItem3 = value;
RaisePropertyChanged("SelectedItem3");
RaisePropertyChanged("ComboboxItems1"); //raise propertychanged to refresh GUI
RaisePropertyChanged("ComboboxItems2");
}
}
}
XAML:
<ComboBox SelectedItem="{Binding SelectedItem1}" ItemsSource="{Binding ComboboxItems1}" />
<ComboBox SelectedItem="{Binding SelectedItem2}" ItemsSource="{Binding ComboboxItems2}" />
<ComboBox SelectedItem="{Binding SelectedItem3}" ItemsSource="{Binding ComboboxItems3}" />
I am developing a UI application which displays 3 datagrids, each of them are dependent on each other(selectedItem binding)
This is what I have done so far:
datagrid1 has one Model and one ViewModel,
datagrid2 has one Model and one ViewModel,
datagrid3 has one Model and one ViewModel.
and all three ViewModels are mapped to the View using DataContext in each <Datagrid.DataContext> property. The reason for setting ViewModel for each model is, The datagrid has to select the items from their respective tables from the database.
But now I am facing a difficulty when I try to set the SelectedItem in each datagrid.
I am pasting a sample code for one datagrid. Like this I have created viewmodel for other two datagrid's also.
XAML
<DataGrid DataContext="{Binding Path=HostData,NotifyOnTargetUpdated=True,Mode=OneWay}"
AutoGenerateColumns="False" Name="hostDatagrid" Margin="171,32,235,230">
<Datagrid.DataContext>
<host:HostViewModel>
</Datagrid.DataContext>
<DataGrid.Columns>
<DataGridTextColumn Header="Host" Width="auto" Binding="{Binding HostID}" />
<DataGridTextColumn Header="Status" Width="auto" Binding="{Binding HostStatus}"/>
</DataGrid.Columns>
</DataGrid>
<DataGrid DataContext="{Binding Path=LogData,NotifyOnTargetUpdated=True,Mode=OneWay}"
AutoGenerateColumns="False" Name="LogDatagrid" Margin="103,108,102,145">
<Datagrid.DataContext>
<host:LogViewModel>
</Datagrid.DataContext>
<DataGrid.Columns>
<DataGridTextColumn Header="Host ID" Width="auto" Binding="{Binding HostID}" />
<DataGridTextColumn Header="Logs" Width="auto" Binding="{Binding LogID}" />
<DataGridTextColumn Header="Log Path" Width="auto" Binding="{Binding LogPath}"/>
<DataGridTextColumn Header="Date" Width="auto" Binding="{Binding Date}"/>
<DataGridTextColumn Header="Last Activity" Width="auto" Binding="{Binding LastActivity}"/>
c#- Model
public LogFileModel()
{
}
private int _hostID;
public int HostID
{
get { return _hostID; }
set { _hostID= value; OnpropertyChanged("HostID"); }
}
private string _logid;
public string LogID
{
get { return _logid; }
set { _logid= value; OnpropertyChanged("LogID"); }
}
private string _logpath;
public string LogPath
{
get { return _logPath; }
set { _logPath = value; OnpropertyChanged("LogPath"); }
}
private DateTime _date;
public DateTime Date;
{
get { return _date; }
set { _date= value; OnpropertyChanged("Date"); }
}
private bool _activity;
public bool LastActivity
{
get { return _activity; }
set { _activity= value; OnpropertyChanged("LastActivity"); }
}
ViewModel
LogModel _myModel = new LogModel();
private ObservableCollection<LogFileModel> _logFileData = new
ObservableCollection<LogFileModel>();
public ObservableCollection<LogFileModel> LogFileData
{
get { return _logFileData; }
set { _logFileData = value; OnPropertyChanged("LogFileData"); }
} public LogFileViewModel()
{
initializeload();
timer.Tick += new EventHandler(timer_Tick);
timer.Interval = new TimeSpan(0, 0, 3);
timer.Start();
}
~LogFileViewModel()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
timer.Stop();
timer.Tick -= new EventHandler(timer_Tick);
}
disposed = true;
}
}
private void timer_Tick(object sender, EventArgs e)
{
try
{
LogFileData.Clear();
initializeload();
}
catch (Exception ex)
{
timer.Stop();
Console.WriteLine(ex.Message);
}
}
private void initializeload()
{
try
{
DataTable table = _myModel.getData();
for (int i = 0; i < table.Rows.Count; ++i)
LogFileData.Add(new LogFileModel
{
HostID= Convert.ToInt32(table.Rows[i][0]),
LogID = table.Rows[i][1].ToString(),
LogPath = table.Rows[i][2].ToString(),
Date = Convert.ToDateTime(table.Rows[i][3]),
LastAcivity= table.Rows[i][4].ToString(),
});
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyname)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyname));
}
public class LogModel
{
public DataTable getData()
{
DataTable ndt = new DataTable();
SqlConnection sqlcon = new SqlConnection(ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString);
sqlcon.Open();
//for this select statement only I have created separate ViewModel for the respective Model
SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM [LocalDB].[dbo].[LogFiles]", sqlcon);
da.Fill(ndt);
da.Dispose();
sqlcon.Close();
return ndt;
}
}
}
Is there any other way to overcome this issue and to have one ViewModel for one View, with their respective datagrid - select statements?
You are always free to split your view in different sub-views (like user controls).
The same thing you can do with the ViewModels too.
You can create one ViewModel which represents the whole view knowing the three sub viewmodels...
public class MainViewModel : ViewModel
{
private LogViewModel _SubLogViewModel = new LogViewModel();
public LogViewModel SubLogViewModel
{
get
{
return _SubLogViewModel;
}
set
{
if (_SubLogViewModel != value)
{
_SubLogViewModel = value;
OnpropertyChanged("SubLogViewModel");
}
}
}
private HostViewModel _SubHostViewModel = new HostViewModel();
public HostViewModel SubHostViewModel
{
get
{
return _SubHostViewModel;
}
set
{
if (_SubHostViewModel != value)
{
_SubHostViewModel = value;
OnpropertyChanged("SubHostViewModel");
}
}
}
private Host _SelectedHost;
public Host SelectedHost
{
get
{
return _SelectedHost;
}
set
{
if (_SelectedHost!= value)
{
_SelectedHost= value;
OnpropertyChanged("SelectedHost");
if(this.SelectedHost != null && this.SubLogViewModel != null)
{
this.SubLogViewModel.LoadLogFor(this.SelectedHost);
}
}
}
}
}
and the XAML something like:
<Grid>
<Grid.DataContext>
<host:MainViewModel />
</Grid.DataContext>
<DataGrid Name="hostDataGrid" DataContext="{Binding SubHostModel}" SelectedItem="{Binding SelectedHost, Mode=TwoWay}">
...
</DataGrid>
<DataGrid Name="LogDatagrid" DataContext="{Binding SubLogModel}">
</DataGrid>
</Grid>