How to add new view when instantiating a new view model - wpf

New to WPF and not sure how to programmatically instantiate a new viewmodel that wraps a new chart and its data collection. Right now, it consists of the following, but not sure the optimal way to set it up.
class ChartViewModel
{
public ChartViewModel()
{
CartesianChart chart = new CartesianChart();
chart.Series = new SeriesCollection
{
new GLineSeries
{
Title = "Pressure",
Values = new GearedValues<double>(),
},
new GLineSeries
{
Title = "Pulse",
Values = new GearedValues<int>(),
}
};
}
}
And then, I need to add the new chart to the view. The CartesianChart object is the UIElement and it works as follows when I just test it in main window without this class.
stackPanel.Children.Add(chart);
But the class can't access the xaml it seems and I can't add the actual view model class since thats not a UIElement, only the chart is. Basically need to create a new chart instance every time the previous chart fills up with something like this:
ChartViewModel tempChart = new ChartViewModel();
chartRepo.Add(tempChart); //chart repo is a list of ChartViewModels
So it needs its own SeriesCollection and UIElement. Thanks for any recommendations.

If you want to dynamically add new charts you have to make use of DataTemplate to template the chart data.
The DataTemplate which consists of the chart is bound to a ChartDataModel. We can use a ListView to display the charts (data templates). A view model ChartViewModel serves as the ListView.ItemsSource and holds a set of ChartData.
Each ChartData maps to a new chart.
Whenever you create a new ChartDataModel in the ChartViewModel and add it to ChartModels, the ListView will automatically create a new chart.
The view:
<ListView ItemsSource="{Binding ChartModels}">
<ListView.DataContext>
<ChartViewModel />
</ListView.DataContext>
<ListView.ItemTemplate>
<DataTemplate DataType="ChartDataModel">
<CartesianChart>
<CartesianChart.Series>
<LineSeries Title="Pressure" Values="{Binding PressureValues}" />
<LineSeries Title="Pulse" Values="{Binding PulseValues}" />
</CartesianChart.Series>
</CartesianChart>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The model:
class ChartDataModel
{
public ChartDataModel()
{
this.PressureValues = new ChartValues<double>();
this.PulseValues = new ChartValues<double>();
}
public ChartValues<double> PressureValues { get; set; }
public ChartValues<double> PulseValues { get; set; }
}
The view model:
class ChartViewModel : INotifyPropertyChanged
{
public ChartViewModel()
{
this.ChartModels = new ObservableCollection<ChartDataModel>();
CreateNewChart();
}
private void CreateNewChart()
{
var newChartDataModel = new ChartDataModel()
{
PressureDataValues = new ChartValues<double>()
{
10, 20, 30, 40, 50
},
PulseDataValues = new ChartValues<double>()
{
100, 200, 300, 400, 500
}
};
this.ChartModels.Add(newChartDataModel);
}
private ObservableCollection<ChartDataModel> chartModels;
public ObservableCollection<ChartDataModel> ChartModels
{
get => this.chartModels;
set
{
if (Equals(value, this.chartModels)) return;
this.chartModels = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

That ViewModel is instantiating a Chart, which is a UI element. ViewModels should only expose simple properties with public getters (and sometimes setters). Views should read those properties and change its UI elements accordingly. ViewModels usually don't contain UI elements.
That's to say, you should instantiate your chart inside the xaml (or xaml.cs), then bind its properties to the ViewModel. To link View and ViewModel, the view's DataContext property must be the ViewModel instance.
The ViewModel has direct access to your data source (e.g. a database) and translates that source into values ready to be consumed by the UI elements.
For example, your view may contain something like:
<livechart:CartesianChart>
<livechart:CartesianChart.Series>
<Series Title="{Binding FirstSeriesTitle}" Values="{Binding FirstSeriesValues}"/>
</livechart:CartesianChart.Series>
</livechart:CartesianChart>
While your ViewModel will have
public class ChartViewModel
{
public string FirstSeriesTitle { get; set; }
public IEnumerable<ChartPoint> FirstSeriesValues { get; set; }
}
I advise you to read some articles about the MVVM pattern to get a better grasp at it!
EDIT: :Since you need a variable number of charts, you should probably add an ItemsControl, and set its ItemsSource binded to an ObservableCollection of chartviewmodels. Set the itemscontrol's itemTemplate property to set the appearance of each item! (that's to say, chart and other ui elements)

Related

Binding a dynamic collection of varying objects to a WPF datagrid with ReactiveUI ViewModels

I have a strange use case for WPF DataGrid using MVVM through ReactiveUI that doesn't quite fit any other solution I've found so far.
The Problem Set
I have a DataSet that contains a list of Users. Each User has a string Id and a set of uniquely-identified data fields associated with it that can be represented as a set of string key-value pairs. All Users within a DataSet will have the same set of fields, but different DataSets may have different fields. For example, all Users in one DataSet may have fields "Name", "Age", and "Address"; while Users in another DataSet may have fields "Badge #" and "Job Title".
I would like to present the DataSets in a WPF DataGrid where the columns can be dynamically populated. I would also like to add some metadata to fields that identify what type of data is stored there and display different controls in the DataGrid cells based on that metadata: Pure text fields should use a TextBox, Image filepath fields should have a TextBox to type in a path and a Button to bring up a file-select dialog, etc.
What I Have That Works (but isn't what I want)
I break my data up into ReactiveUI ViewModels. (omitting RaisePropertyChanged() calls for brevity)
public class DataSetViewModel : ReactiveObject
{
public ReactiveList<UserViewModel> Users { get; }
public UserViewModel SelectedUser { get; set; }
};
public class UserViewModel : ReactiveObject
{
public string Id { get; set; }
public ReactiveList<FieldViewModel> Fields { get; }
public class FieldHeader
{
public string Key { get; set; }
public FieldType FType { get; set; } // either Text or Image
}
public ReactiveList<FieldHeader> FieldHeaders { get; }
};
public class FieldViewModel : ReactiveObject
{
public string Value { get; set; } // already knows how to update underlying data when changed
}
I display all of this in a DataSetView. Since Id is always present in Users, I add the first DataGridTextColumn here. Omitting unnecessary XAML for more brevity.
<UserControl x:Class="UserEditor.UI.DataSetView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:UserEditor.UI"
x:Name="DataSetControl">
<DataGrid Name="UserDataGrid"
SelectionMode="Single" AutoGenerateColumns="False"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
DataContext="{Binding Path=ViewModel.Users, ElementName=DataSetControl}">
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Id}" MinWidth="60" Width="SizeToCells"/>
</DataGrid.Columns>
</DataGrid>
</UserControl>
And I create additional columns in the code-behind, omitting boiler plate:
public partial class DataSetView : UserControl, IViewFor<DataSetViewModel>
{
// ViewModel DependencyProperty named "ViewModel" declared here
public DataSetView()
{
InitializeComponent();
this.WhenAnyValue(_ => _.ViewModel).BindTo(this, _ => _.DataContext);
this.OneWayBind(ViewModel, vm => vm.Users, v => v.UserDataGrid.ItemsSource);
this.Bind(ViewModel, vm => vm.SelectedUser, v => v.UserDataGrid.SelectedItem);
}
// this gets called when the ViewModel is set, and when I detect fields are added or removed
private void InitHeaders(bool firstInit)
{
// remove all columns except the first, which is reserved for Id
while (UserDataGrid.Columns.Count > 1)
{
UserDataGrid.Columns.RemoveAt(UserDataGrid.Columns.Count - 1);
}
if (ViewModel == null)
return;
// using all DataGridTextColumns for now
for (int i = 0; i < ViewModel.FieldHeaders.Count; i++)
{
DataGridColumn column;
switch (ViewModel.FieldHeaders[i].Type)
{
case DataSet.UserData.Field.FieldType.Text:
column = new DataGridTextColumn
{
Binding = new Binding($"Fields[{i}].Value")
};
break;
case DataSet.UserData.Field.FieldType.Image:
column = new DataGridTextColumn
{
Binding = new Binding($"Fields[{i}].Value")
};
break;
}
column.Header = ViewModel.FieldHeaders[i].Key;
column.Width = firstInit ? DataGridLength.SizeToCells : DataGridLength.SizeToHeader;
UserDataGrid.Columns.Add(column);
}
}
When Fields get added or remove, the UserViewModels are updated in DataSetViewModel and InitHeaders is called to recreate the columns. The resulting DataGridCells bind to their respective FieldViewModels and everything works.
What I'm Trying To Do (but doesn't work)
I would like to break FieldViewModel into two derived classes, TextFieldViewModel and ImageFieldViewModel. Each has their respective TextFieldView and ImageFieldView with their own ViewModel dependency property. UserViewModel still contains a ReactiveList. My new InitHeaders() looks like this:
private void InitHeaders(bool firstInit)
{
// remove all columns except the first, which is reserved for Id
while (UserDataGrid.Columns.Count > 1)
{
UserDataGrid.Columns.RemoveAt(UserDataGrid.Columns.Count - 1);
}
if (ViewModel == null)
return;
for (int i = 0; i < ViewModel.FieldHeaders.Count; i++)
{
DataGridTemplateColumn column = new DataGridTemplateColumn();
DataTemplate dataTemplate = new DataTemplate();
switch (ViewModel.FieldHeaders[i].Type)
{
case DataSet.UserData.Field.FieldType.Text:
{
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(TextFieldView));
factory.SetBinding(TextFieldView.ViewModelProperty,
new Binding($"Fields[{i}]"));
dataTemplate.VisualTree = factory;
dataTemplate.DataType = typeof(TextFieldViewModel);
}
break;
case DataSet.UserData.Field.FieldType.Image:
{
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(ImageFieldView));
factory.SetBinding(ImageFieldView.ViewModelProperty,
new Binding($"Fields[{i}]"));
dataTemplate.VisualTree = factory;
dataTemplate.DataType = typeof(ImageFieldViewModel);
}
break;
}
column.Header = ViewModel.FieldHeaders[i].Key;
column.Width = firstInit ? DataGridLength.SizeToCells : DataGridLength.SizeToHeader;
column.CellTemplate = dataTemplate;
UserDataGrid.Columns.Add(column);
}
}
The idea is that I create a DataGridTemplateColumn that generates the correct View and then binds the indexed FieldViewModel to the ViewModel dependency property. I have also tried adding a Converter to the Bindings that converts from the base VM to the correct derived type.
The end result is that the DataGrid populates with the correct view, but the DataContext is always a UserViewModel rather than the appropriate FieldViewModel-derived type. The ViewModel is never set, and the VMs don't bind properly. I'm not sure what else I'm missing, and would appreciate any suggestions or insight.
I've figured out an answer that works, though it may not be the best one. Rather than binding to the ViewModel property in my views, I instead bind directly to the DataContext:
factory.SetBinding(DataContextProperty, new Binding($"Fields[{i}]"));
In my views, I add some boilerplate code to listen to the DataContext, set the ViewModel property, and perform my ReactiveUI binding:
public TextFieldView()
{
InitializeComponent();
this.WhenAnyValue(_ => _.DataContext)
.Where(context => context != null)
.Subscribe(context =>
{
// other binding occurs as a result of setting the ViewModel
ViewModel = context as TextFieldViewModel;
});
}

Setting WPF datacontext for a specific control

I'm developing a WPF application and I'm struggling a little bit to understand some of the details of DataContext as it applies to binding. My application uses a business object which is defined like this:
public class MyBusinessObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
// enumerations for some properties
public enum MyEnumValues
{
[Description("New York")]
NewYork,
[Description("Chicago")]
Chicago,
[Description("Los Angeles")]
LosAngeles
}
// an example property
private string _myPropertyName;
public string MyPropertyName
{
get { return _myPropertyName; }
set
{
if (_myPropertyName == value)
{
return;
}
_myPropertyName = value;
OnPropertyChanged(new PropertyChangedEventArgs("MyPropertyName"));
}
}
// another example property
private MyEnumValues _myEnumPropertyName;
public MyEnumValues MyEnumPropertyName
{
get { return _myEnumPropertyName; }
set
{
if (_myEnumPropertyName== value)
{
return;
}
_myEnumPropertyName= value;
OnPropertyChanged(new PropertyChangedEventArgs("MyEnumPropertyName"));
}
}
// example list property of type Widget
public List<Widget> MyWidgets { get; set; }
// constructor
public MyBusinessObject()
{
// initialize list of widgets
MyWidgets = new List<Widget>();
// add 10 widgets to the list
for (int i = 1; i <= 10; i++)
{
MyWidgets.Add(new Widget());
}
// set default settings
this.MyPropertyName = string.empty;
}
}
As you can see, I have some properties that are declared in this class one of which is a list of Widgets. The Widget class itself also implements INotifyPropertyChanged and exposes about 30 properties.
My UI has a combobox which is bound to my list of Widgets like this:
MyBusinessObject myBusinessObject = new MyBusinessObject();
public MainWindow()
{
InitializeComponent();
this.DataContext = myBusinessObject;
selectedWidgetComboBox.ItemsSource = myBusinessObject.MyWidgets;
selectedWidgetComboBox.DisplayMemberPath = "WidgetName";
selectedWidgetComboBox.SelectedValuePath = "WidgetName";
}
The majority of the controls on my UI are used to display the properties of a Widget. When my user selects a Widget from the combobox, I want these controls to display the properties for the selected Widget. I'm currently achieving this behavior by updating my window's DataContext in the SelectionChanged event handler of my combobox like this:
private void selectedWidgetComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.DataContext = selectedWidgetComboBox.SelectedItem;
}
This allows me to bind my controls to the appropriate Widget property like this:
<TextBox Text="{Binding WidgetColor}"></TextBox>
However, not all of the controls in my UI are used to display Widget properties. Some of the controls need to display the properties from MyBusinessObject (for example: MyPropertyName defined above). In this scenario, I can't simply say:
<TextBox Text="{Binding MyPropertyName}"></TextBox>
...because the DataContext of the window is pointing to the selected Widget instead of MyBusinessObject. Can anyone tell me how I set the DataContext for a specific control (in XAML) to reference the fact that MyPropertyName is a property of MyBusinessObject? Thank you!
Instead of changing the DataContext of your window, you should add a property to your MyBusinessObject class like this one:
private Widget _selectedWidget;
public Widget SelectedWidget
{
get { return _selectedWidget; }
set
{
if (_selectedWidget == value)
{
return;
}
_selectedWidget = value;
OnPropertyChanged(new PropertyChangedEventArgs("SelectedWidget"));
}
}
Then bind SelectedWidget to the SelectedItem property of your combobox. Anywhere that you need to use the widget's properties you can do this:
<TextBox Text="{Binding Path=SelectedWidget.WidgetColor}"></TextBox>
try
<TextBox Text="{Binding MyBusinessObject.MyPropertyName}"></TextBox>
this works if MyBusinessObject is the datacontext of the textbox and MyPropertyName is a property of MyBusinessObject
Also, Here is a good article to clarify binding
hope this helps
EDIT 1:
use a relative binding like this:
text="{Binding DataContext.MyPropertyName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TypeOfControl}}}"
So the relatve binding allows you to look up the visual tree to another UI element and use its datacontext. I would consider wrapping your window's contents in a grid. and wet your windows datacontext to the businessobject and the grids datacontext to the widget. That way you can always use the parent window's datacontext through the realtive source binding.
so use the following if your window's datacontext is your business object
text="{Binding DataContext.MyPropertyName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"

Raise an event in datagridview to update database for delete or update MVVM method

I think I am missing something simple but I am basically doing a multiple step method to get my data. Let me run through a simpler version of what I am doing in an example.
I have an observable collection that implements INotifyPropertyChanged
The Observable Collection is of a class 'POCO' which is a simple POCO class that makes up these two properties:
PersonID int { get; set; }
Name string { get; set; }
I have an entity to sql data model that maps a simple database table that contains the same meta values in the POCO class and let's say for simple example it has three row values:
PersonID, Name
1, Brett
2, Emily
3, Test
The observable collection is wired in ModelView like so:
ObservableCollection<POCO> _Pocos;
POCOEntities ee = new POCOEntities();
public ObservableCollection<POCO> POCOs
{
get
{
if (_Pocos == null)
{
List<POCO> mes = this.GetPOCOs();
_Pocos= new ObservableCollection<POCO>(mes);
}
return _Pocos;
}
set
{
_Pocos = value;
OnPropertyChanged("POCOs");
}
}
List<POCO> GetPOCOs()
{
return ee.vPOCO.Select(p => new POCOView()
{
PersonId = p.PersonID,
Name = p.Name
}).ToList();
}
I also have a Current Item wired up as such.
POCO _CurrentPOCO;
public POCO CurrentPOCO
{
get { return _CurrentPOCO; }
set
{
_CurrentPOCO = value;
OnPropertyChanged("CurrentPOCO");
}
}
4 and 5 are the guts of the ModelView I wire them up to the view of the datagrid as such:
<DataGrid x:Name="datagrid"
ItemsSource="{Binding POCOs}"
CurrentItem="{Binding CurrentPOCO}" />
This is the part that I do not get, how do I update the database's entity model in near real time? The collection is wired up just fine and updating, how do I tell the database what happened? If I set up an event like 'CellEditEnding' or 'SelectionChanged' and try to implement an update proc from my entity model it BOMBS in the ModelView. If I stick to just the code behind it works, kind of, but does not seem to capture the 'after' changed value.
Even with using the ICommand property and implementing the relay command done in the MVVM. These methods won't work. So I was curious if I am over thinking it and their some type of interface you can just bake in that will do the refreshing to the database for you. I can handle inserting docs and then using a method to populate or refresh the datagrid but I would like to be able to change values in the datagridview and update the database directly.
SUMMARY:
In the simplest way possible I am just wanting to update the database as I change the datagridview and the observablecollection changes so the two sync with each other.
There are two categories of changes here:
The collection changes, i.e. items are added and/or removed. To track these changes, make your VM listen to the ObservableCollection's CollectionChanged event and use the NewItems and OldItems properties to figure out what data to add and/or remove from the DB.
Properties on one of your POCO instances changes, e.g. you change the name of a person. A change like this will not trigger a CollectionChanged event as the collection itself is still the same.
For #2 I would implement a simple Viewmodel for the POCO class that handles updates to its properties. After all, your POCO should be considered a business object and should not be exposed to the view directly. Each PocoVM holds a reference to a single POCO instance.
EDIT
I added more or less all of the code I used in my experiement, except for the stubbed database since I have no idea what you are using and how it works. It doesn't really matter as long as it returns lists of items and you can tell it to update a single item.
XAML
Same as yours except I added another grid (readonly) to show changes once they got accepted by my MysticalDBLayer. I also got rid of the CurrentItemas we will be using the PocoVM to keep track of which item we are editing.
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
<RowDefinition />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock>Input-Grid</TextBlock>
<DataGrid Grid.Row="1"
ItemsSource="{Binding POCOs}"/>
<TextBlock Grid.Row="2">Readonly-Grid</TextBlock>
<DataGrid Grid.Row="3"
ItemsSource="{Binding POCOs, Mode=OneWay}"
IsReadOnly="True"/>
</Grid>
View Model (DataContext of the XAML)
This is all that goes into this file. The database connection might vary based on what you use, but basically I have an observable collection that I populate the same way you do, except I create a new PocoVM (viewmodel) for every POCO and add the new PocoVM to the ObservableCollection instead of the POCO itself.
class VM
{
ObservableCollection<PocoVM> _pocoCollection = new ObservableCollection<PocoVM>();
public ObservableCollection<PocoVM> POCOs
{
get
{
if (_pocoCollection.Count == 0)
{
_pocoCollection = new ObservableCollection<PocoVM>();
IEnumerable<POCO> pocos = MysticalDBLayer.GetItems();
foreach (POCO poco in pocos)
{
_pocoCollection.Add(new PocoVM(poco));
}
}
return _pocoCollection;
}
}
}
And finally the PocoVM
Every time you try to update the value of one of your cells (only name will be update:able with this code as Number only has a getter), the corresponding setter will get called in this class. Here you can do your write to your DB and act based on whether that worked out or not.
class PocoVM : INotifyPropertyChanged
{
private POCO _dataInstance = null;
public PocoVM(POCO dataInstance)
{
_dataInstance = dataInstance;
}
public uint Number
{
get { return _dataInstance.Number; }
}
public string Name
{
get { return _dataInstance.Name; }
set
{
if (string.Compare(value, _dataInstance.Name, StringComparison.CurrentCultureIgnoreCase) == 0)
return;
if (!MysticalDBLayer.UpdatePoco(_dataInstance, new POCO(_dataInstance.Number, value)))
return;
_dataInstance.Name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string property)
{
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
If I understand you correctly, you only need to use the CollectionChanged event of the ObservableCollection.
Event arguments contains the Action (Add/Remove...) and you can get the NewItems and OldItems too.
So you can track the changes on the collection and do the synchronization with your database.
I hope I could help you.
Create your own ObservableCollection which overrides InsertItem() and RemoveItem() methods:
public class CustomerCollection : ObservableCollection<Customer>
{
private DataContext _db;
public DataContext Db
{
get { return _db; }
}
public CustomerCollection(IEnumerable<Customer> customers, DataContext context)
: base(customers)
{
_db = context;
}
protected override void InsertItem(int index, Customer item)
{
this.Db.AddToCustomers(item);
this.Db.SaveChanges();
base.InsertItem(index, item);
}
protected override void RemoveItem(int index)
{
this.Db.DeleteObject(this[index]);
this.Db.SaveChanges();
base.RemoveItem(index);
}
}
This is similar to a question I just answered here WPF datagrid with MVVM. (Because I'm a newbie to this site, I will copy the answer)
You should use a ListCollectionView.
Here's a sample showing how to do it:
1) Create a new WPF project called ListCollectionViewTest
2) In the MainWindow.xaml.cs cut&paste the following (should be in ViewModel but I'm too lazy)
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Data;
namespace ListCollectionViewTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private List<Employee> equivalentOfDatabase = new List<Employee>()
{
new Employee() { FirstName = "John", LastName = "Doe", IsWorthyOfAttention = true },
new Employee() { FirstName = "Jane", LastName = "Doe", IsWorthyOfAttention = true },
new Employee() { FirstName = "Mr.", LastName = "Unsignificant", IsWorthyOfAttention = false },
};
public ListCollectionView TestList { get; set; }
public MainWindow()
{
DataContext = this;
// This is all the magic you need -------
TestList = new ListCollectionView(equivalentOfDatabase);
TestList.Filter = x => (x as Employee).IsWorthyOfAttention;
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(equivalentOfDatabase.Aggregate("Employees are: \n\r", (acc, emp) => acc + string.Format(" - {0} {1}\n\r", emp.FirstName, emp.LastName), x => x));
}
}
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsWorthyOfAttention { get; set; }
}
}
3) In MainWindow.xaml cut&paste this:
<Window x:Class="ListCollectionViewTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<DataGrid ItemsSource="{Binding TestList}"
RowHeight="22"
AutoGenerateColumns="True">
</DataGrid>
<Button Content="Show All Employees in DB" Click="Button_Click" />
</StackPanel>
</Window>

WPF MVVM Bind list on custom control to ViewModel

Is it possible to bind data in the "wrong" direction? I want a value in a custom control to be bound to my ViewModel. I've tried binding with mode "OneWayToSource" but I can't get it to work.
Scenario (simplified):
I have a custom control (MyCustomControl) that has a dependency property that is a list of strings:
public class MyCustomControl : Control
{
static MyCustomControl()
{
//Make sure the template in Themes/Generic.xaml is used.
DefaultStyleKeyProperty.OverrideMetadata(typeof (MyCustomControl), new FrameworkPropertyMetadata(typeof (MyCustomControl)));
//Create/Register the dependency properties.
CheckedItemsProperty = DependencyProperty.Register("MyStringList", typeof (List<string>), typeof (MyCustomControl), new FrameworkPropertyMetadata(new List<string>()));
}
public List<string> MyStringList
{
get
{
return (List<string>)GetValue(MyCustomControl.MyStringListProperty);
}
set
{
var oldValue = (List<string>)GetValue(MyCustomControl.MyStringListProperty);
var newValue = value;
SetValue(MyCustomControl.MyStringListProperty, newValue);
OnPropertyChanged(new DependencyPropertyChangedEventArgs(MyCustomControl.MyStringListProperty, oldValue, newValue));
}
}
public static readonly DependencyProperty MyStringListProperty;
}
The control also contains code to manipulate this list.
I use this custom control in a UserControl that has a ViewModel. The ViewModel has a property that is also a list of strings:
public List<string> MyStringsInTheViewModel
{
get
{
return _myStringsInTheViewModel;
}
set
{
if (value != _myStringsInTheViewModel)
{
_myStringsInTheViewModel = value;
OnPropertyChanged("MyStringsInTheViewModel");
}
}
}
private List<string> _myStringsInTheViewModel;
Now I want to bind the list in my custom control (MyStringList) to the list in my ViewModel (MyStringsInTheViewModel) so that when the list is changed in the custom control it is also changed in the ViewModel. I've tried this but can't get it to work...
<myns:MyCustomControl MyStringList="{Binding Path=MyStringsInTheViewModel, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}">
How can I make such a binding?
Use ObservableCollection<T> instead of List<T>. It implements INotifyCollectionChanged Interface.

WPF DataContext does not refresh the DataGrid using MVVM model

Project Overview
I have a view which binds to a viewmodel containing 2 ObserverableCollection. The viewmodel constructor populates the first ObserverableCollection and the view datacontext is collected to bind to it through a public property called Sites.
Later the 2ed ObserverableCollection is populated in the LoadOrders method and the public property LoadFraudResults is updated for binding it with datacontext.
I am using WCF to pull the data from the database and its getting pulled very nicely.
VIEWMODEL SOURCE
class ManageFraudOrderViewModel:ViewModelBase
{
#region Fields
private readonly ICollectionView collectionViewSites;
private readonly ICollectionView collectionView;
private ObservableCollection<GeneralAdminService.Website> _sites;
private ObservableCollection<FraudService.OrderQueue> _LoadFraudResults;
#endregion
#region Properties
public ObservableCollection<GeneralAdminService.Website> Sites
{
get { return this._sites; }
}
public ObservableCollection<FraudService.OrderQueue> LoadFraudResults
{
get { return this._LoadFraudResults;}
}
#endregion
public ManageFraudOrderViewModel()
{
//Get values from wfc service model
GeneralAdminService.GeneralAdminServiceClient generalAdminServiceClient = new GeneralAdminServiceClient();
GeneralAdminService.Website[] websites = generalAdminServiceClient.GetWebsites();
//Get values from wfc service model
if (websites.Length > 0)
{
_sites = new ObservableCollection<Wqn.Administration.UI.GeneralAdminService.Website>();
foreach (GeneralAdminService.Website website in websites)
{
_sites.Add((Wqn.Administration.UI.GeneralAdminService.Website)website);
}
this.collectionViewSites= CollectionViewSource.GetDefaultView(this._sites);
}
generalAdminServiceClient.Close();
}
public void LoadOrders(Wqn.Administration.UI.FraudService.Website website)
{
//Get values from wfc service model
FraudServiceClient fraudServiceClient = new FraudServiceClient();
FraudService.OrderQueue[] OrderQueue = fraudServiceClient.GetFraudOrders(website);
//Get values from wfc service model
if (OrderQueue.Length > 0)
{
_LoadFraudResults = new ObservableCollection<Wqn.Administration.UI.FraudService.OrderQueue>();
foreach (FraudService.OrderQueue orderQueue in OrderQueue)
{
_LoadFraudResults.Add(orderQueue);
}
}
this.collectionViewSites= CollectionViewSource.GetDefaultView(this._LoadFraudResults);
fraudServiceClient.Close();
}
}
VIEW SOURCE
public partial class OrderQueueControl : UserControl
{
private ManageFraudOrderViewModel manageFraudOrderViewModel ;
private OrderQueue orderQueue;
private ButtonAction ButtonAction;
private DispatcherTimer dispatcherTimer;
public OrderQueueControl()
{
LoadOrderQueueForm();
}
#region LoadOrderQueueForm
private void LoadOrderQueueForm()
{
//for binding the first observablecollection
manageFraudOrderViewModel = new ManageFraudOrderViewModel();
this.DataContext = manageFraudOrderViewModel;
}
#endregion
private void cmbWebsite_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
BindItemsSource();
}
#region BindItemsSource
private void BindItemsSource()
{
using (OverrideCursor cursor = new OverrideCursor(Cursors.Wait))
{
if (!string.IsNullOrEmpty(Convert.ToString(cmbWebsite.SelectedItem)))
{
Wqn.Administration.UI.FraudService.Website website = (Wqn.Administration.UI.FraudService.Website)Enum.Parse(typeof(Wqn.Administration.UI.FraudService.Website),cmbWebsite.SelectedItem.ToString());
//for binding the second observablecollection*******
manageFraudOrderViewModel.LoadOrders(website);
this.DataContext = manageFraudOrderViewModel;
//for binding the second observablecollection*******
}
}
}
#endregion
}
XAML
ComboBox x:Name="cmbWebsite" ItemsSource="{Binding Sites}" Margin="5"
Width="100" Height="25" SelectionChanged="cmbWebsite_SelectionChanged"
DataGrid ItemsSource ={Binding Path = LoadFraudResults}
PROBLEM AREA:
When I call the LoadOrderQueueForm to bind the first observablecollection and later BindItemsSource to bind 2ed observable collection, everything works fine and no problem for the first time binding.
But, when I call BindItemsSource again to repopulate the obseravablecollection based on changed selected combo value via cmbWebsite_SelectionChanged, the observalblecollection gets populated with new value and LoadFraudResults property in viewmodule is populated with new values; but when i call the datacontext to rebind the datagrid,the datagrid does not reflect the changed values.
In other words the datagrid doesnot get changed when the datacontext is called the 2ed time in BindItemsSource method of the view.
manageFraudOrderViewModel.LoadOrders(website);
this.DataContext = manageFraudOrderViewModel;
manageFraudOrderViewModel values are correct but the datagrid is not relected with changed values.
Please help as I am stuck with this thing for past 2 days and the deadline is approaching near.
Thanks in advance
try to use datagrid.Items.Refresh() !
Yes, ilu2009 is correct.
Binding using the MVVM modal to a DataGrid and changing the objects in DataGrid.ItemsSource requires DataGrid.ItemsSource.Refresh() for it to reflect on the UI.

Resources