WPF Property not updated in UI when modified via derived class - c#

I am unable to display the CurrentStatus property from my ViewModelBase class in the status bar of my WPF application.
ViewModelBase is inherited by TasksViewModel and UserViewModel.
UserViewModel is inherited by ImportViewModel and TestViewModel.
MainWindow has a DataContext of TasksViewModel.
ViewModelBase:
public abstract class ViewModelBase : INotifyPropertyChanged
{
private string _currentStatus;
public string CurrentStatus
{
get { return _currentStatus; }
set
{
if (value == _currentStatus)
{
return;
}
_currentStatus = value;
OnPropertyChanged(nameof(CurrentStatus));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
TasksViewModel:
public class TasksViewModel : ViewModelBase
{
public IEnumerable<ViewModelBase> Collection => _collection;
public override string ViewModelName => "Tasks";
public TasksViewModel()
{
_collection = new ObservableCollection<ViewModelBase>
{
new ImportUsersViewModel(),
new TestFunctionsViewModel()
};
// Added as per John Gardner's answer below.
// watch for currentstatus property changes in the internal view models and use those for our status
foreach (ViewModelBase i in _collection)
{
i.PropertyChanged += InternalCollectionPropertyChanged;
}
}
// Added as per John Gardner's answer.
private void InternalCollectionPropertyChanged(object source, PropertyChangedEventArgs e)
{
var vm = source as ViewModelBase;
if (vm != null && e.PropertyName == nameof(CurrentStatus))
{
CurrentStatus = vm.CurrentStatus;
}
}
}
ImportUsersViewModel:
internal class ImportUsersViewModel : UserViewModel
{
private async void BrowseInputFileAsync()
{
App.Log.Debug("Browsing for input file.");
string path = string.IsNullOrWhiteSpace(InputFile)
? Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
: Path.GetDirectoryName(InputFile);
InputFile = FileFunctions.GetFileLocation("Browse for User Import File",
path, FileFunctions.FileFilter.CSVTextAll) ?? InputFile;
CurrentStatus = "Reading Import file.";
ImportUsers = new ObservableCollection<UserP>();
ImportUsers = new ObservableCollection<User>(await Task.Run(() => ReadImportFile()));
string importResult =
$"{ImportUsers.Count} users in file in {new TimeSpan(readImportStopwatch.ElapsedTicks).Humanize()}.";
CurrentStatus = importResult; // Property is updated in ViewModelBase, but not in UI.
}
}
MainWindow.xaml:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:ViewModel"
xmlns:view="clr-namespace:Views"
x:Class="MainWindow"
Title="Users"
Height="600"
Width="1000"
Icon="Resources/Icon.png">
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:ImportUsersViewModel}">
<view:ImportUsers />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:TestFunctionsViewModel}">
<view:TestFunctionsView />
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<viewModel:TasksViewModel />
</Window.DataContext>
<DockPanel>
<StatusBar DockPanel.Dock="Bottom" Height="auto">
<TextBlock Text="Status: " />
<!-- Not updated in UI by any View Model -->
<TextBlock Text="{Binding CurrentStatus}" />
</StatusBar>
</DockPanel>
</Window>
If I bind a text block to the CurrentStatus property inside the ImportUsers UserControl it updates without issue, but the "parent" status bar does not update.
My suspicion is that it can't be displayed in the MainWindow status bar because, although both ImportViewModel and TasksViewModel inherit ViewModelBase, they don't have any link to each other, and the TasksViewModel CurrentStatus property isn't being updated.

I think your suspicion is correct.
The DataContext on the Window is a different ViewModel instance to that of the ImportUsersViewModel.
While CurrentStatus is defined in the same object hierarchy, the CurrentStatus line in the ImportUsersViewModel is changing a different object instance than the CurrentStatus property attached to the Window DataContext.

Your window's DataContext is a TaskViewModel, but nothing on that view model is watching for property changes in it's collection, and updating itself. essentially, TasksViewModel is containing the other viewmodels, but not aggregating any of their behaviors.
you need something like:
public class TasksViewModel : ViewModelBase
{
public IEnumerable<ViewModelBase> Collection => _collection;
public override string ViewModelName => "Tasks";
public TasksViewModel()
{
_collection = new ObservableCollection<ViewModelBase>
{
new ImportUsersViewModel(),
new TestFunctionsViewModel()
};
// watch for currentstatus property changes in the internal view models and use those for our status
foreach (var i in _collection)
{
i.PropertyChanged += this.InternalCollectionPropertyChanged;
}
}
}
//
// if a currentstatus property change occurred inside one of the nested
// viewmodelbase objects, set our status to that status
//
private InternalCollectionPropertyChanged(object source, PropertyChangeEvent e)
{
var vm = source as ViewModelBase;
if (vm != null && e.PropertyName = nameof(CurrentStatus))
{
this.CurrentStatus = vm.CurrentStatus;
}
}

Related

Set up a binding between two properties in code behind

In my view, I have a ListBox with some templated items that contain buttons.
<ListBox x:Name="MyListBox" ItemTemplate="{DynamicResource DataTemplate1}"
ItemsSource="{Binding MyItems}">
</ListBox>
And the template for generated items:
<DataTemplate x:Key="DataTemplate1">
<StackPanel Orientation="Horizontal">
<Button Width="50" Click="Button_Click" />
</StackPanel>
</DataTemplate>
When user clicks a button on one of those ListBox items, I want to send the index of that ListBox item to my ViewModel.
So figured to use Binding as it seems to be the way in MVVM. But I'm struggling to set up a binding in code between two properties.
My View code is as follows:
public partial class ItemView : UserControl
{
ViewModel.ItemViewModel VM;
public ItemView()
{
InitializeComponent();
VM = new ViewModel.ItemViewModel();
this.DataContext = VM;
}
private int clickedItemIndex;
public int ClickedItemIndex { get => clickedItemIndex; set => clickedItemIndex = value; }
private void Button_Click(object sender, RoutedEventArgs e)
{
var ClickedItem = (sender as FrameworkElement).DataContext;
ClickedItemIndex = MyListBox.Items.IndexOf(ClickedItem);
}
}
I get the index and set it to ClickedItemIndex property,
I also have property in my ViewModel:
public int SomeInt { get; set; }
Now how do I set up a binding between these two properties?
I'm quite new to MVVM and still learning it. So, maybe this not the correct approach. But I need to have a way for each individual listbox item to be able to call upon an effect in more global viewmodel. For example, if I wanted to have a "Remove" button on each of the listbox items, I would somehow need to send the index to the viewmodel and call the removeItem method with index as the parameter. Or is there a better way to do similar things?
I have a sample app created just for this scenario. I know it seems a lot of code at first glance. Copy this code in your project, that will help debug and get a hang of it(MVVM, databinding, commands and so on).
usercontrol.xaml
<UserControl x:Class="WpfApplication1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:Model}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=Name}"/>
<Button Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.UpdateCommand}"
CommandParameter="{Binding}"
Content="Update"/>
<Button Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.RemoveCommand}"
CommandParameter="{Binding}"
Content="Remove"/>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Models}">
</ListBox>
</Grid>
usercontrol.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
View model
public class ViewModel : INotifyPropertyChanged
{
private Models _Models;
public Models Models
{
get { return _Models; }
set { _Models = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Models)));
}
}
public ViewModel()
{
Models = new Models();
UpdateCommand = new Command(o => true, UpdateItem);
RemoveCommand = new Command(o => true, RemoveItem);
}
void RemoveItem(object item)
{
Model m = (item as Model);
Models.Remove(m);
}
void UpdateItem(object item)
{
Model m = (item as Model);
m.Name = m.Name + " updated";
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public ICommand UpdateCommand { get; private set; }
public ICommand RemoveCommand { get; private set; }
}
Icommand implementation
public class Command : ICommand
{
private readonly Func<object, bool> _canExe;
private readonly Action<object> _exe;
public Command(Func<object,bool> canExecute,Action<object> execute)
{
_canExe = canExecute;
_exe = execute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExe(parameter);
}
public void Execute(object parameter)
{
_exe(parameter);
}
}
Model and a collection of models
public class Models : ObservableCollection<Model>
{
public Models()
{
Add(new Model ());
Add(new Model ());
Add(new Model ());
Add(new Model ());
}
}
public class Model : INotifyPropertyChanged
{
static int count = 0;
public Model()
{
Name = "Model "+ ++count;
}
private string _Name;
public string Name
{
get { return _Name; }
set { _Name = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
You don't need to use a Button in order to select the item. When you click/tap on the item it will get automatically selected.
Then simply bind ListBox.SelectedIndex to your view model property SomeInt and it will update on every selection.
Data binding overview in WPF
You can also get the item itself by binding ListBox.SelectedItem to your view model.
You can handle new values by invoking a handler from the property's set method:
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
private int currentItemIndex;
public int CurrentItemIndex
{
get => this.currentItemIndex;
set
{
this.currentItemIndex = value;
OnPropertyChanged();
// Handle property changes
OnCurrentItemIndexChanged();
}
}
private MyItem currentItem;
public MyItem CurrentItem
{
get => this.currentItem;
set
{
this.currentItem = value;
OnPropertyChanged();
}
}
protected virtual void OnCurrentItemIndexChanged()
{
// Handle the new this.CurrentItemIndex value
}
// Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ItemView .xaml
<UserControl>
<UserControl.DataContext>
<ViewModel />
</UserControl.DataContext>
<ListBox ItemsSource="{Binding MyItems}"
SelectedIndex="{Binding CurrentItemIndex}"
SelectedItem="{Binding CurrentItem}" />
</UserControl>

How can i bind to the sub-ViewModel using ViewModel inheritance?

In my application i have the following MasterViewModel1-class.
public class MasterViewModel1 : ViewModelBase
{
private ObservableCollection<ObservableObject> _MainGrid;
public ObservableCollection<ObservableObject> MainGrid
{
get => _MainGrid;
set
{
_MainGrid = value;
RaisePropertyChanged();
}
}
public ObservableCollection<FilterItem> FilterItems
{
get;
set;
}
public MasterViewModel1()
{
CreateDefaultMenu();
}
public void CreateDefaultMenu()
{
FilterItems = new ObservableCollection<FilterItem>
{
new FilterItem(OnFilterClicked)
{
Content = "Filter"
},
new FilterItem(OnFilterCancelClicked)
{
Content = "Filter aufheben"
}
};
}
public virtual void OnFilterClicked() { }
public virtual void OnFilterCancelClicked() { }
The MasterViewModel1-class is inherited by the TestViewModel-class.
public class TestViewModel : MasterViewModel1
{
private Kunde _NeuerKunde;
public Kunde NeuerKunde
{
get => _NeuerKunde;
set => _NeuerKunde = value;
}
private string _Kundenmatchcode;
public string Kundenmatchcode
{
get => _Kundenmatchcode;
set
{
_Kundenmatchcode = value;
RaisePropertyChanged();
}
}
public TestViewModel()
{
NeuerKunde = new Kunde();
}
}
I use the MasterViewModel1-class and its view for reusable reasons, because in the future there will be many more views which will inherit the MasterViewModel.
Inside the MasterView in need to bind to both, the MasterViewModel, so i have the "Base-Design".
And i need to bind to the "Sub"ViewModel, in this example the TestViewModel.
View of the MasterViewModel1
In the image u can see the MasterView. The red marked region is the place where the TestViewModel (TestView) should be placed. I can't use staticresource!!! It have to be dynamic, so if i instanciate another ViewModel, which also inherites from MasterViewModel1. The red marked region should change depending on the instantiated ViewModel.
I hope it's clear enought.
If u need further informations please ask.
Generally, all public properties of a superclass are visible and accessible via every subclass. You can bind to every public property.
If you want to change the layout or appearance of a view based on the actual implementation or type, you should use a DataTemplate which describes how the view is structured and bound to the model's data.
A simple ContentControl will serve as the dynamic view host.
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
private ViewModelBase currentView;
public ViewModelBase CurrentView
{
get => this.currentView;
set
{
this.currentView= value;
OnPropertyChanged();
}
}
public ICommand ToggleViewCommand => new RelayCommand(param => this.CurrentView = this.Views.FirstOrDefault(view => view != this.CurrentView));
private List<ViewModelBase> Views { get; }
public MainViewModel()
{
this.Views = new ObservableCollection<ViewModelBase>()
{
new TestViewModel() { Value = "TestViewModel View" },
new AnotherTestViewModel() { Name = "AnotherTestViewModel View" }
}
this.CurrentView = this.Views.First();
}
}
TestViewModel.cs
public class TestViewModel : ViewModelBase
{
private string value;
public string Value
{
get => this.value;
set
{
this.value = value;
OnPropertyChanged();
}
}
}
AnotherTestViewModel.cs
public class TestViewModel : ViewModelBase
{
private string name;
public string Name
{
get => this.name;
set
{
this.name = value;
OnPropertyChanged();
}
}
}
TestView.xaml
<Window>
<Window.DataContext>
<TestViewModel />
</Window.DataContext>
<TextBlock Text="{Binding Value}" />
</Window>
MainWindow.xaml
<Window>
<Window.DataContext>
<MainViewModel />
</Window.DataContext>
<Window.Resources>
<!-- Define the views as an implicit (keyless) DataTemplate -->
<DataTemplate DataType="{x:Type TestViewModel}">
<!-- Show a view as a UserControl -->
<TestView />
</DataTemplate>
<DataTemplate DataType="{x:Type AnotherTestViewModel}">
<!-- Or add a elements -->
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<Rectangle Height="80" Width="80" Fill="Red" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Command="{Binding ToggleViewCommand}" Content="Toggle View" />
<!--
Host of the different views based on the actual model type (dynamic view).
The implicit DataTemplates will apply automatically
and show the view that maps to the current CurrentView view model type
-->
<ContentControl Content="{Binding CurrentView}" />
</StackPanel>
</Window>

Create a ViewModel with sub ViewModel

Is there a proper way to create a C#/WPF ViewModel containing subViewModel ?
Objective is:
I have a MainWindow. That window is use to read/create images. There is a button on that windows who switch between 2 UserControl one with IHM used to read image, the other one used to create.
The MainWindow has a MainWindowViewModel with :
command switch
image length
application parameters
I want that both UserControls can acces to MainWindowViewModel field/properties and have they own commands.
Construction will be something like this:
public partial class ReadUserControl : UserControl
{
public ReadUserControl()
{
InitializeComponent();
DataContext = MainViewModel.ReadViewModel;
}
}
public partial class CreateUserControl : UserControl
{
public CreateUserControl()
{
InitializeComponent();
DataContext = MainViewModel.CreateViewModel;
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = MainViewModel;
}
}
For example, if a MainViewModel contain a field ImageWidth setting ImageWidth in CreateUserControl change the value for ReadUserControl.
I hope to have been clear, I don't know how design my MainViewModel to achieve this result
EDIT1:
I've created the MainWindowViewModel as a Singleton but i'm still unable to get MainViewModel.CreateViewModel and MainViewModel.ReadViewModel
public class MainWindowViewModel : ViewModelBase
{
private static MainWindowViewModel _instance = null;
public static MainWindowViewModel Instance
{
get
{
if (_instance == null)
_instance = new MainWindowViewModel();
return _instance;
}
}
private MainWindowViewModel()
: base()
{
}
#region CreateViewModel
/* How to create ? */
#endregion
#region ReadViewModel
/* How to create ? */
#endregion
}
Your example will work. At least if you have made your MainViewModel a Singleton.
A more professional approach might be an Constructor-Injection like this.
public partial class ReadUserControl : UserControl
{
public ReadUserControl(MainViewModel vm)
{
InitializeComponent();
DataContext = vm.ReadViewModel;
}
}
With such DependencyInjections you can achieve a higher level of abstraction, since your UserControls can be generalized. (They will all have the same Constructor)
On the other hand, you give every such UserControl the ability, to manipulate the MainViewModel, not aware of side-effects.
In your special case, it would be more safe, to pass only the needed parameters to the UserControl, instead of giving them a bunch of informations, they will never need.
public partial class ReadUserControl : UserControl
{
public ReadUserControl(Icommand command, int imageLength, AppParams appParams)
{
InitializeComponent();
...
// Do with your Constructorparameters what ever you have to
}
}
Edit:
Here a small, dumb implementation of how it could be done:
Code
public class MainViewModel : INotifyPropertyChanged {
private INotifyPropertyChanged _selectedViewModel;
public MainViewModel() {
var cmd = new RelayCommand(x => {
MessageBox.Show("HelloWorld");
}, x => true);
this.RVM = new ReadViewModel(cmd);
this.WVM = new WriteViewModel(cmd);
this.SelectedViewModel = WVM;
}
private ICommand _switchViewModelCommand;
public ICommand SwitchViewModelCommand => this._switchViewModelCommand ?? (this._switchViewModelCommand = new RelayCommand(x => {
if (this.SelectedViewModel == RVM) {
this.SelectedViewModel = WVM;
return;
}
this.SelectedViewModel = RVM;
}));
public INotifyPropertyChanged SelectedViewModel {
get {
return this._selectedViewModel;
}
set {
if (Equals(value, this._selectedViewModel))
return;
this._selectedViewModel = value;
this.OnPropertyChanged();
}
}
public ReadViewModel RVM {
get; set;
}
public WriteViewModel WVM {
get; set;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ReadViewModel : INotifyPropertyChanged {
public ReadViewModel(ICommand sayHelloCommand) {
this.HelloCommand = sayHelloCommand;
}
public ICommand HelloCommand {
get;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class WriteViewModel : INotifyPropertyChanged {
public WriteViewModel(ICommand sayHelloCommand) {
this.HelloCommand = sayHelloCommand;
}
public ICommand HelloCommand {
get;
}
public ICommand HelloMoonCommand => new RelayCommand(x => { MessageBox.Show("Hello Moon"); });
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid Height="200">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ContentControl Content="{Binding SelectedViewModel, UpdateSourceTrigger=PropertyChanged}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:ReadViewModel}">
<StackPanel>
<Button Content="Say Hello world" Command="{Binding HelloCommand}"></Button>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:WriteViewModel}">
<StackPanel>
<Button Content="Say Hello world" Command="{Binding HelloCommand}"></Button>
<Button Content="Say Hello Moon" Command="{Binding HelloMoonCommand}"></Button>
</StackPanel>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
<Button Content="Switch VM" Command="{Binding SwitchViewModelCommand}" Grid.Row="1"/>
</Grid>
You can pass in the MainViewModel as DataContext for your user control and set the data context of elements as Read/Create model
something like
<Grid> <!--using MainWindowViewModel as data context-->
<Grid DataContext="{Binding Path=CreateViewModel}"> <!--using CreateViewModel as data context-->
.....
</Grid>
<Grid>

How do you perform Binding with a DataGridView in WPF?

I want to bind a datagrid view in a user control that is docking to a main WPF form. However everytime I try to bind the data it must pre exist and won't update. Is there a way to perform this in the XAML directly to know when an event is triggered to update the datagridview rather than do it in the code behind?
Partial code of XAML:
xmlns:c="clr-namespace:TestWPFMain"
<UserControl.Resources>
<c:GridData x:Key="dataforGrid"/>
</UserControl.Resources>
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" ItemsSource="{Binding Source={StaticResource dataforGrid}, Path=Results, Mode=TwoWay}" />
</Grid>
Code Behind for UserControl above:
public GridControl()
{
InitializeComponent();
GridData gd = new GridData();
gd.UpdateResults();
//datagridMain.ItemsSource = gd.Results;
-- This code above will work if I uncomment but I want it to be bound
directly and was curious as I thought the mode of 'two way' would
do this. I am not certain and most examples assume property is already
set up and not being created and updated.
}
Code Class for GridData:
class PersonName
{
public string Name { get; set; }
}
class GridData
{
public ObservableCollection<PersonName> Results { get; set; }
public void UpdateResults()
{
using (EntityDataModel be = new EntityDataModel())
{
var list = be.tePersons.Select(x => new PersonName { Name = x.FirstName });
Results = new ObservableCollection<PersonName>(list);
}
}
}
To use binding like this, you need to:
Set the DataContext correctly on the DataGrid (or on one of its parent)
Implement INotifyPropertyChanged on your model class, and raise PropertyChanged in the property setter.
1)
Set your window's DataContext to the GridData object:
public GridControl()
{
InitializeComponent();
GridData gd = new GridData();
gd.UpdateResults();
this.DataContext = gd;
}
2)
Implement INotifyPropertyChanged. This ensures that your view gets notified when the Results property gets updated:
public class GridData : INotifyPropertyChanged
{
private ObservableCollection<PersonName> _results;
public ObservableCollection<PersonName> Results
{
get { return _results; }
set
{
_results = value;
RaisePropertyChanged("GridData");
}
}
// ...
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
#endregion
}
Then you can simply bind to the path relative to the data context.
<DataGrid ItemsSource="{Binding Results}" />
Note that you don't need two-way binding in this case -- that's for propagating changes from the View back to your model (ie, most useful for when there's a UI control like a text box or checkbox).
Here is an example (I used Window, but it will work the same for UserControl)
Xaml:
<Window x:Class="WpfApplication4.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" Name="UI">
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" ItemsSource="{Binding ElementName=UI, Path=GridData.Results, Mode=TwoWay}" />
</Grid>
</Window>
or id you want the whole DataContext:
<Window x:Class="WpfApplication4.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" Name="UI">
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" DataContext="{Binding ElementName=UI, Path=GridData}" ItemsSource="{Binding Results}" />
</Grid>
</Window>
Code:
You will have to implement INotifyPropertyChanged so the xaml knows GridData has changed
The ObservableCollection inside GridData as this function built-in so anytime you add remove items they will update the DataGrid control
public partial class MainWindow : Window , INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
GridData = new GridData { Results = new ObservableCollection<PersonName>() };
GridData.Results.Add(new PersonName { Name = "Test1" });
GridData.Results.Add(new PersonName { Name = "Test2" });
}
private GridData _gridData;
public GridData GridData
{
get { return _gridData; }
set { _gridData = value; NotifyPropertyChanged("GridData"); }
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies the property changed.
/// </summary>
/// <param name="info">The info.</param>
public void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Classes:
I made a small change to the update method, so it just clears and updates the existing ObservableCollection, otherwise you would have to Implement INotifypropertyChanged to this class if you assign a new ObservableCollection.
public class PersonName
{
public string Name { get; set; }
}
public class GridData
{
public GridData()
{
Results = new ObservableCollection<PersonName>()
}
public ObservableCollection<PersonName> Results { get; set; }
public void UpdateResults()
{
using (EntityDataModel be = new EntityDataModel())
{
// Just update existing list, instead of creating a new one.
Results.Clear();
be.tePersons.Select(x => new PersonName { Name = x.FirstName }).ToList().ForEach(item => Results.Add(item);
}
}
}

Data binding a nested property to a listbox

I cannot get any display from my observable collection in a custom object bound to a ListBox. This works fine when I have a string collection in my view model, but no names display when I try to access the property through a custom object. I am not receiving any errors in the output window.
Here is my code:
Custom Object
public class TestObject
{
public ObservableCollection<string> List { get; set; }
public static TestObject GetList()
{
string[] list = new string[] { "Bob", "Bill" };
return new TestObject
{
List = new ObservableCollection<string>(list)
};
}
}
Xaml
<Window x:Class="TestWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox Height="100" HorizontalAlignment="Left" Margin="120,61,0,0" Name="listBox1" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Path=TObj.List}" />
</Grid>
Xaml.cs
public partial class MainWindow : Window
{
private ModelMainWindow model;
public MainWindow()
{
InitializeComponent();
model = new ModelMainWindow();
this.DataContext = model;
this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
public void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
this.model.Refresh();
}
}
ViewModel
public class ModelMainWindow : INotifyPropertyChanged
{
private TestObject tObj;
public event PropertyChangedEventHandler PropertyChanged;
public TestObject TObj
{
get
{
return this.tObj;
}
set
{
this.tObj = value;
this.Notify("Names");
}
}
public void Notify(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public void Refresh()
{
this.TObj = TestObject.GetList();
}
}
Can't bind to private properties. Also the change notification targets the wrong property, change "Names" to "TObj". (Also i would recommend making the List property get-only (backed by a readonly field), or implementing INoptifyPropertyChanged so the changes cannot get lost)
Your List is private. Make it a public property otherwise WPF can't see it.

Categories