WPF MVVM DataGrid View SelectedCellsChanged - c#

I'm new to the WPF MVVM. I want to know about how to detect SelectedCellsChanged event inside my ViewModel. Is there any way to detect that event without putting any code into the code behind file. This is my code.
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
DataContext="{StaticResource CusVM}">
<Grid>
<Button x:Name="myButton" Command="{Binding MyButtonClickCommand}" Width="100" Height="50" Content="click" Margin="0,10,417,260" />
<Label Content="{Binding Name}" Margin="105,37,23,251" />
<TextBox x:Name="inputBox1" Width="200" Height="30" Margin="22,74,295,216" Text="{Binding Text1, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Width="200" Height="30" Margin="263,74,54,216" />
<ComboBox HorizontalAlignment="Left" Margin="122,10,0,0" VerticalAlignment="Top" Width="236" ItemsSource="{Binding Addresses}" SelectedItem="{Binding SelectedAddress}" DisplayMemberPath="AddressLine1" >
</ComboBox>
<DataGrid Margin="0,109,0,10" ItemsSource="{Binding Addresses}"/>
</Grid>
View Model : CustomerViewModel
namespace WpfApplication1.ViewModels
{
public class CustomerViewModel : EventBase
{
public ICommand MyButtonClickCommand
{
get { return new DelegateCommand(FuncToCall, FuncToEvaluate); }
}
private Address selected_address;
public Address SelectedAddress
{
get { return selected_address; }
set { selected_address = value; OnPropertyChanged("SelectedAddress"); Name = value.AddressLine1; }
}
IEnumerable<Address> addresses = new List<Address>();
public IEnumerable<Address> Addresses
{
get { return addresses; }
set
{
addresses = value;
OnPropertyChanged("Addresses");
}
}
public CustomerViewModel()
{
fillList();
}
private void fillList()
{
List<Address> addr = new List<Address>();
addr.Add(new Address() { AddressID=1, AddressLine1="test1"});
addr.Add(new Address() { AddressID=2, AddressLine1="test2"});
addr.Add(new Address() { AddressID = 3, AddressLine1 = "test3" });
addresses = addr;
}
private string text1;
public string Text1
{
get { return text1; }
set {
text1 = value;
OnPropertyChanged("Text1");
Name = text1;
}
}
private string name;
public string Name
{
get { return name; }
set {
name = value;
OnPropertyChanged("Name");
}
}
private void FuncToCall(object context)
{
Name = "test result";
}
private bool FuncToEvaluate(object context)
{
return true;
}
}
}

I think you might find the answer here. I would've add it as a comment, but I don't have enough rep just yet.

Related

Dynamically changing tabitem via button command not working wfp

Im trying to create a function in a wpf program, where I can select an item in a listview, and press a button and it changes the tabitem and allows me to then edit the item from the listview that was selected. Im having issues with getting the tabitem to change for me.
For the navigation of my app, I have a ViewModelBase, which my AppointmentsViewModel inherits from. Inside the AppointmentsViewVM there is a tabcontrol with 4 items, by clicking each one it loads the requested view/viewmodel for that function.
This is not the only way I've tried to get this to work, Im currently on day 4. I could get the TabIndex to change in the TabControl earlier, but the tab would still not change for me. So I abandoned that and tried the below route (still no luck).
ViewModelBase
namespace MBR2.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged
{
public ICommand MainMenuViewDogs_Command { get; set; }
public ICommand MainMenuViewAppointments_Command { get; set; }
private object _SelectedViewModel;
public object SelectedViewModel
{
get { return _SelectedViewModel; }
set
{
_SelectedViewModel = value;
OnPropertyChanged("SelectedViewModel");
}
}
public ViewModelBase()
{
MainMenuViewDogs_Command = new BaseCommand(OpenDogs);
MainMenuViewAppointments_Command = new BaseCommand(OpenAppointments);
}
private void OpenDogs(object obj)
{
SelectedViewModel = new DogsViewModel();
}
private void OpenAppointments(object obj)
{
SelectedViewModel = new AppointmentsViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool _SelectedIndexView;
public bool SelectedIndexView
{
get { return _SelectedIndexView; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexView");
}
}
private bool _SelectedIndexAdd;
public bool SelectedIndexAdd
{
get { return _SelectedIndexAdd; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexAdd");
}
}
private bool _SelectedIndexEdit;
public bool SelectedIndexEdit
{
get { return _SelectedIndexEdit; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexEdit");
}
}
private bool _SelectedIndexDelete;
public bool SelectedIndexDelete
{
get { return _SelectedIndexDelete; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexDelete");
}
}
}
}
AppointmentsViewModel
{
public class AppointmentsViewModel : ViewModelBase
{
private AppointmentsAddVM _AppointmentsAddVM;
public AppointmentsAddVM AppointmentsAddVM { get { return _AppointmentsAddVM; } }
private AppointmentsEditVM _AppointmentsEditVM;
public AppointmentsEditVM AppointmentsEditVM { get { return _AppointmentsEditVM; } }
private AppointmentsDeleteVM _AppointmentsDeleteVM;
public AppointmentsDeleteVM AppointmentsDeleteVM { get { return _AppointmentsDeleteVM; } }
private AppointmentsViewVM _AppointmentsViewVM;
public AppointmentsViewVM AppointmentsViewVM { get { return _AppointmentsViewVM; } }
public ObservableCollection<object> ViewModelList { get; set; }
public AppointmentsViewModel()
{
this.ViewModelList = new ObservableCollection<object>();
_AppointmentsAddVM = new AppointmentsAddVM();
_AppointmentsEditVM = new AppointmentsEditVM();
_AppointmentsDeleteVM = new AppointmentsDeleteVM();
_AppointmentsViewVM = new AppointmentsViewVM();
this.ViewModelList.Add(_AppointmentsAddVM);
this.ViewModelList.Add(_AppointmentsEditVM);
this.ViewModelList.Add(_AppointmentsDeleteVM);
this.ViewModelList.Add(_AppointmentsViewVM);
}
}
}
AppointmentsView.xaml
<UserControl
x:Class="MBR2.Views.AppointmentsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:vms="clr-namespace:MBR2.ViewModels.Appointments"
xmlns:views="clr-namespace:MBR2.Views.Appointments"
xmlns:viewmodels="clr-namespace:MBR2.ViewModels"
d:DataContext="{d:DesignInstance Type=viewmodels:AppointmentsViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate DataType="{x:Type vms:AppointmentsViewVM}">
<views:AppointmentsViewView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:AppointmentsAddVM}">
<views:AppointmentsAddView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:AppointmentsDeleteVM}">
<views:AppointmentsDeleteView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:AppointmentsEditVM}">
<views:AppointmentsEditView />
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="Appointments" Width="Auto" Height="Auto">
<DockPanel HorizontalAlignment="Center"
Height="Auto"
LastChildFill="False"
VerticalAlignment="Top"
Width="Auto">
<TabControl x:Name="VMTabControl">
<TabItem x:Name="ViewTab"
TabIndex="0"
Header="View"
IsSelected="{Binding SelectedIndexView}"
Content="{Binding AppointmentsViewVM}"></TabItem>
<TabItem x:Name="AddTab"
TabIndex="1"
Header="Add"
IsSelected="{Binding SelectedIndexAdd}"
Content="{Binding AppointmentsAddVM}"></TabItem>
<TabItem x:Name="EditTab"
TabIndex="2"
Header="Edit"
IsSelected="{Binding SelectedIndexEdit}"
Content="{Binding AppointmentsEditVM}"></TabItem>
<TabItem x:Name="DeleteTab"
TabIndex="3"
Header="Delete"
IsSelected="{Binding SelectedIndexDelete}"
Content="{Binding AppointmentsDeleteVM}"></TabItem>
</TabControl>
</DockPanel>
</Grid>
</UserControl>
And the associated AppointmentsViewVM
namespace MBR2.ViewModels.Appointments
{
public class AppointmentsViewVM : ViewModelBase, INotifyPropertyChanged
{
private List<AppointmentsView_Wrapper> _AppointmentsView;
public List<AppointmentsView_Wrapper> AppointmentsView
{
get { return _AppointmentsView; }
set
{
_AppointmentsView = value;
OnPropertyChanged("AppointmentsView");
}
}
private List<string> _NameColumn = new List<string>();
public List<string> NameColumn
{
get { return _NameColumn; }
set
{
_NameColumn = value;
OnPropertyChanged("NameColumn");
}
}
private List<string> _ApptDateColumn = new List<string>();
public List<string> ApptDateColumn
{
get { return _ApptDateColumn; }
set
{
_ApptDateColumn = value;
OnPropertyChanged("ApptDateColumn");
}
}
private List<string> _ApptTimeColumn = new List<string>();
public List<string> ApptTimeColumn
{
get { return _ApptTimeColumn; }
set
{
_ApptTimeColumn = value;
OnPropertyChanged("ApptTimeColumn");
}
}
private List<string> _ApptVetColumn = new List<string>();
public List<string> ApptVetColumn
{
get { return _ApptVetColumn; }
set
{
_ApptVetColumn = value;
OnPropertyChanged("ApptVetColumn");
}
}
private List<string> _ApptCreatedColumn = new List<string>();
public List<string> ApptCreatedColumn
{
get { return _ApptCreatedColumn; }
set
{
_ApptCreatedColumn = value;
OnPropertyChanged("ApptCreatedColumn");
}
}
private List<int> _ApptIDColumn = new List<int>();
public List<int> ApptIDColumn
{
get { return _ApptIDColumn; }
set
{
_ApptIDColumn = value;
OnPropertyChanged("ApptIDColumn");
}
}
private string _AppointmentEdit_Enabled = "False";
public string AppointmentEdit_Enabled
{
get { return _AppointmentEdit_Enabled; }
set
{
_AppointmentEdit_Enabled = value;
OnPropertyChanged("AppointmentEdit_Enabled");
}
}
private AppointmentsView_Wrapper _ApptIDSelected;
public AppointmentsView_Wrapper ApptIDSelected
{
get { return _ApptIDSelected; }
set
{
AppointmentEdit_Enabled = "True";
_ApptIDSelected = value;
OnPropertyChanged("ApptIDSelected");
}
}
public AppointmentData AppointmentData = new AppointmentData();
public Messaging Messaging = new Messaging();
public ICommand AppointmentsListView_Command => new DelegateCommand<object>(AppointmentsListView_Clicked);
public ICommand EditSelection_Command => new DelegateCommand<object>(EditSelection_Clicked);
public AppointmentsViewVM()
{
BuildPage();
}
public async void BuildPage()
{
AppointmentsView = await AppointmentData.Appointments_GetAll();
foreach(var item in AppointmentsView)
{
ApptIDColumn.Add(item.ApptID);
NameColumn.Add(item.DogName);
ApptDateColumn.Add(item.ApptDate);
ApptTimeColumn.Add(item.ApptTime);
ApptVetColumn.Add(item.ApptVet);
ApptCreatedColumn.Add(item.ApptCreated.ToString("dd/mm/yyyy"));
}
}
public void AppointmentsListView_Clicked(object obj)
{
Messaging.ShowAlert(ApptIDSelected.ApptID.ToString());
}
public void EditSelection_Clicked(object obj)
{
bool result = Messaging.AskQuestion(ApptIDSelected.ApptID.ToString());
if(result)
{
SelectedIndexView = false;
SelectedIndexAdd = false;
SelectedIndexEdit = true;
SelectedIndexDelete = false;
OnPropertyChanged("SelectedIndexView");
OnPropertyChanged("SelectedIndexAdd");
OnPropertyChanged("SelectedIndexEdit");
OnPropertyChanged("SelectedIndexDelete");
}
else
{
Messaging.ShowAlert("no");
}
}
}
}
Here's a minimal reproduction of something where you select in a listbox and that then selects a corresponding tab in a tabcontrol.
This is very minimal but we can perhaps imagine a more sophisticated viewmodel per item in the listbox with name and viewmodel or something.
This is mainwindow.
<Window.Resources>
<DataTemplate DataType="{x:Type local:Avm}">
<local:Aview/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Bvm}">
<local:Bview/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Cvm}">
<local:Cview/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewmodel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding ViewModels}"
x:Name="lb"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ViewModelName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TabControl Grid.Column="1"
ItemsSource="{Binding ViewModels}"
SelectedItem="{Binding ElementName=lb, Path=SelectedItem, Mode=TwoWay}"
>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding ViewModelName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
I have only done 3 views and viewmodels.
Note that the selecteditem of the listbox is bound twoway to the tabcontrol.
I have matching views and viewmodels A, B and C
MainWindowViewModel
public class MainWindowViewmodel : ViewModelBase
{
public ObservableCollection<Object> ViewModels { get; set; } = new ObservableCollection<Object>{
new Avm{ViewModelName="A viewmodel" },
new Bvm{ViewModelName="B viewmodel" },
new Cvm{ViewModelName="C viewmodel" }
};
}
Both the itemssource of listbox and tabcontrol are bound to that collection of viewmodels. Which are, as I mentioned, as simple as you get really.
Viewmodelbase
public class ViewModelBase : INotifyPropertyChanged
{
public string ViewModelName { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Avm, Bvm and Cvm just inherit from that.
An example usercontrol view.
<Grid>
<TextBlock Text="{Binding ViewModelName}"/>
</Grid>
</UserControl>
When I spin that up, select and select an item in the listbox the matching tab is selected. And vice versa. Select a tab and it selects the same one in the listbox.

Button Content doesn't Update [MVVM]

In my Code does my Function OnPropertyChanged not update the UI, despite the Fact that it does work everywhere else in my Programm. I try to upgrade the Content of the Button, so that if the User presses it, it will change it's Content. It does Update the value but then won't show it in the UI for some weird Reason. Any help is appreciated...
The View:
public partial class CreateMP : Window
{
public CreateMP()
{
InitializeComponent();
}
public CreateMP(string UserName)
{
CreateMPViewModel.User = UserName;
InitializeComponent();
}
}
The ViewModel:
public class CreateMPViewModel : Window
{
private string _ButtonContent = "Create Server";
private bool _ButtonEnable = true;
public event PropertyChangedEventHandler PropertyChanged;
private readonly TcpListener listener;
private TcpClient client;
public ICommand ButtonCommand_Back { get; set; }
public ICommand ButtonCommand_CreateGame { get; set; }
public string ButtonContent
{
get
{
return _ButtonContent;
}
set
{
if (value != _ButtonContent)
{
_ButtonContent = value;
OnPropertyChanged("ButtonContent");
}
}
}
public bool ButtonEnable
{
get
{
return _ButtonEnable;
}
set
{
if (value != _ButtonEnable)
{
_ButtonEnable = value;
OnPropertyChanged("ButtonEnable");
}
}
}
public static string User { get; set; }
public string IPAdresse { get; set; }
public int Passwort { get; set; }
public CreateMPViewModel()
{
ButtonCommand_Back = new DelegateCommand(BackButtonClick);
ButtonCommand_CreateGame = new DelegateCommand(CreateGameClick);
int Port = 51246;
Random rnd = new Random();
Password = rnd.Next(0, 99999);
IPAddress localAdd = IPAddress.Parse(IPAdresse);
listener = new TcpListener(localAdd, Port);
listener.Start();
}
private void BackButtonClick()
{
client = GameHandler.CurrentClient;
Application.Current.MainWindow.Close();
if (client != null)
{
client.Close();
}
if (listener != null)
{
listener.Stop();
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void CreateGameClick()
{
ButtonEnable = false;
ButtonContent = "Waiting for Player...";
//More Code here after the Update of the Button
}
}
The XAML:
<Window x:Class="CreateMP"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Game" Height="450" Width="400"
WindowStartupLocation="CenterScreen"
ResizeMode="NoResize" WindowStyle="None">
<Window.DataContext>
<local1:CreateMPViewModel/>
</Window.DataContext>
<Grid>
<Label Name ="CreateGame" Content="Create Game" Margin="109,46,118,322" FontSize="26" Foreground="#0074BC"/>
<Button Name ="btnCreateGame" Content="{Binding ButtonContent, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="35" Margin="118,267,118,0" VerticalAlignment="Top" Background="#0074BC" Foreground="White" Command="{Binding ButtonCommand_CreateGame}" IsEnabled="{Binding ButtonEnable, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Name ="btnBack" Content="Back" Height="35" Margin="118,387,118,0" VerticalAlignment="Top" Background="#0074BC" Foreground="White" Command="{Binding ButtonCommand_Back}"/>
<Label Content="IP4-Adress:" HorizontalAlignment="Left" Height="30" Margin="92,138,0,0" VerticalAlignment="Top" Width="90" Foreground="#0074BC"/>
<Label Content="Password:" HorizontalAlignment="Left" Height="30" Margin="92,168,0,0" VerticalAlignment="Top" Width="90" Foreground="#0074BC"/>
<Label Content="{Binding IPAdresse}" HorizontalAlignment="Left" Height="30" Margin="187,138,0,0" VerticalAlignment="Top" Width="127" Foreground="#0074BC"/>
<Label Content="{Binding Password}" HorizontalAlignment="Left" Height="30" Margin="187,168,0,0" VerticalAlignment="Top" Width="127" Foreground="#0074BC"/>
</Grid>
</Window>
The CreateMPViewModel class must inherit from INotifyPropertyChanged.
public class CreateMPViewModel : Window, INotifyPropertyChanged
{
//........................................
//........................................
}

C# WPF - ComboBox of databound ObservableCollection of classes

Recently I started converting a proof of concept UWP app to a working WPF app.
What I want is to have two dropdowns (combobox) of "characters" I can choose, I want them databound to an ObservableCollection property, where the characters I selected is stored in a different Character property for player 1 then player 2.
I had databinding on dropdowns working in the UWP app, but I can't get it to work in the WPF app.
In the WPF app, the comboboxes stay empty and I can't select an option.
I tried following the answer to this question, but I think I'm missing something: Binding a WPF ComboBox to a custom list
Here is the code, kept minimal:
Character.cs
public class Character : INotifyPropertyChanged
{
private int _id;
public int Id
{
get
{
return _id;
}
set
{
_id = value;
}
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public Character(int id, string name)
{
Id = id;
Name = name;
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow.xaml
<Window x:Class="SmashWiiUOverlayManager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SmashWiiUOverlayManager"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Grid.Row="0">
<ComboBox
Name="Player1CharacterDropdown"
ItemsSource="{Binding CharacterList, Mode=TwoWay}"
SelectedItem="{Binding Player1SelectedCharacter, Mode=TwoWay}"
SelectedValuePath="Name"
DisplayMemberPath="Name"
Width="144">
</ComboBox>
</StackPanel>
<StackPanel Grid.Column="0" Grid.Row="1" HorizontalAlignment="Right">
<ComboBox
Name="Player2CharacterDropdown"
ItemsSource="{Binding CharacterList, Mode=TwoWay}"
SelectedItem="{Binding Player2SelectedCharacter, Mode=TwoWay}"
SelectedValuePath="Character"
DisplayMemberPath="Name"
Width="144">
</ComboBox>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Character> _characterList;
public ObservableCollection<Character> CharacterList
{
get
{
return _characterList;
}
set
{
_characterList = value;
}
}
private Character _player1SelectedCharacter;
public Character Player1SelectedCharacter
{
get
{
return _player1SelectedCharacter;
}
set
{
_player1SelectedCharacter = value;
}
}
private Character _player2SelectedCharacter;
public Character Player2SelectedCharacter
{
get
{
return _player2SelectedCharacter;
}
set
{
_player2SelectedCharacter = value;
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
CharacterList = new ObservableCollection<Character>
{
new Character(0, "Mario"),
new Character(1, "Luigi"),
new Character(2, "Wario"),
};
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
This code currently leaves the comboboxes empty.
When I use:
Player1CharacterDropdown.ItemsSource = new ObservableCollection<Character>
{
new Character(0, "Mario", ".\\images\\mario.png"),
new Character(1, "Luigi", ".\\images\\luigi.png"),
new Character(2, "Wario", ".\\images\\wario.png"),
};
... the combox gets filled, but it's databound to the property, which is what I would like.
What am I missing here?

Q: C# WPF DataGrid bound to ObservableCollection<T> gets not updated

I'm currently facing an issue in my C# WPF application, that is seemingly simple to solve. But even after reading several Stackoverflow posts, tutorials on the web i still cannot solve the problem.
I have a DataGrid, that i'm trying to bind to a ObservableCollection. Furthermore, i am using the MVVM pattern (at least i'm trying) together with PRISM.
My Problem is, that the DataGrid does not get populated after i add a new Rule to the ObservableCollection. When i'm debugging the application i can see, that the collection has items contained. As you can see from the code i am using RaisePropertyChanged from the Bindablebase to fire the PropertyChangedEvent. I have also bound the DataGrid's ItemSource to RuleList.
Can anybody see what i'm doing wrong?
Thanks in Advance,
Michael
Here is my RuleModel.cs:
namespace MyApp.GenericViews.Model
{
public class RuleModel
{
public bool IsEnabled { get; set; }
public string RuleName { get; set; }
public string Source { get; set; }
public string Target { get; set; }
public string ModuleName { get; set; }
public string ElementName { get; set; }
public RuleModel()
{
}
}
}
Here is my View.xaml:
<UserControl
x:Class="MyApp.GenericViews.View.RulesView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyApp.GenericViews.View"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:MyApp.GenericViews.ViewModel"
d:DataContext="{d:DesignInstance d:Type=vm:RulesViewModel}"
d:DesignHeight="600"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Content="{Binding Title}" />
<!--<StackPanel Grid.Row="0" Orientation="Horizontal">
<Button Content="Add" Style="{StaticResource DefaultDialogButton}" />
<Button Content="Remove" Style="{StaticResource DefaultDialogButton}" />
</StackPanel>-->
<DataGrid
Name="RulesDataGrid"
Grid.Row="1"
Margin="5"
AutoGenerateColumns="False"
ItemsSource="{Binding Path=RuleList, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<DataGrid.Columns>
<!--<DataGridCheckBoxColumn Width="40" Binding="{Binding Path=IsEnabled}" />-->
<DataGridTextColumn
Width="100"
Binding="{Binding Path=RuleName, UpdateSourceTrigger=PropertyChanged}"
Header="Name" />
<DataGridTextColumn
Width="100"
Binding="{Binding ModuleName}"
Header="Module" />
<!--<DataGridComboBoxColumn
Width="100"
Header="Element"
ItemsSource="{Binding ModuleList}"
SelectedItemBinding="{Binding Path=ElementName}" />-->
<DataGridTextColumn
Width="50*"
Binding="{Binding Source}"
Header="Source" />
<DataGridTextColumn
Width="50*"
Binding="{Binding Target}"
Header="Target" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
Here is my ViewModel.cs.
namespace MyApp.GenericViews.ViewModel
{
public class RulesViewModel : BindableBase, INavigationAware, IViewModel
{
private XmlDatabase _xmlDatabase;
private readonly IUnityContainer _container;
private readonly IEventAggregator _eventAggregator;
public IMyAppView View { get; set; }
public List<string> ModuleList { get; set; }
private string _title;
public string Title
{
get { return _title; }
set
{
if (_title != value)
{
_title = value;
SetProperty(ref _title, value);
RaisePropertyChanged(nameof(Title));
}
}
}
private Rule _selectedRule;
public Rule SelectedRule
{
get { return _selectedRule; }
set
{
if (_selectedRule != value)
{
SetProperty(ref _selectedRule, value);
RaisePropertyChanged(nameof(SelectedRule));
}
}
}
private ObservableCollection<RuleModel> _RuleList;
public ObservableCollection<RuleModel> RuleList
{
get { return _RuleList; }
set
{
if (_RuleList != value)
{
SetProperty(ref _RuleList, value, nameof(RuleList));
//RaisePropertyChanged(nameof(RuleList));
//OnPropertyChanged(nameof(RuleList));
}
}
}
public RulesViewModel(IRuleView view, IUnityContainer c, IEventAggregator evt)
{
View = view;
View.ViewModel = this;
_container = c;
_eventAggregator = evt;
_eventAggregator.GetEvent<RuleCommand>().Subscribe(DoProcessRuleCommand);
}
private void DoProcessRuleCommand(RuleCommandType commandType)
{
switch (commandType)
{
case RuleCommandType.AddRule:
if (RuleList.IsNullOrEmpty())
{
RuleList = new ObservableCollection<RuleModel>();
}
var newRule = new RuleModel()
{
RuleName = "new rule",
};
RuleList.Add(newRule);
RaisePropertyChanged(nameof(RuleList));
//OnPropertyChanged(nameof(RuleList));
break;
case RuleCommandType.DeleteRule:
if (RuleList.IsNeitherNullNorEmpty())
{
//RuleList.Remove(Selected)
}
break;
default:
break;
}
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
_xmlDatabase = _container?.Resolve<XmlDatabase>();
if (_xmlDatabase != null)
{
RuleList = new ObservableCollection<RuleModel>(_xmlDatabase.Rules.Select(r => RuleModel.FromSerializable(r)));
}
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
}
}
Ok, after Ivoros gave me the hint in the comments to review the UserControl's DataContext i looked into the code behind of my View.xaml.cs.
It turnes out that i forgot to return the DataContext on the public View property
before:
public IMiCasaViewModel ViewModel { get; set; }
after:
public IMiCasaViewModel ViewModel
{
get
{
return (INormalizationViewModel)DataContext;
}
set
{
DataContext = value;
}
}
Thank You!!

WPF Record didn't update even with the use of INotifyPropertyChanged

I have a collection of Patients which I set in my ComboBox, whenever I ran and test the form, it seems fine, but whenever I update a record or add another one (from another form), the ComboBox doesn't get updated. I can do a remedy to this by using the code behind and IContent interface but I like to reduce the use of code behind as much as possible. Can you pinpoint to me what markup or code that is lacking?
Here is my Grid Markup (I omitted the RowDefinitions and ColumnDefinitions)
<UserControl.Resources>
<common:ImageSourceConverter x:Key="ToImageSourceConverter" />
<businessLogic:PatientMgr x:Key="PatientsViewModel" />
<ObjectDataProvider x:Key="ItemsSource" ObjectInstance="{StaticResource PatientsViewModel}" />
</UserControl.Resources>
<Grid x:Name="DetailsGrid" DataContext="{StaticResource ItemsSource}">
<Border Grid.Row="1" Grid.RowSpan="8" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Turquoise" BorderThickness="2">
<Image x:Name="InsideButtonImage"
VerticalAlignment="Center"
HorizontalAlignment="Center"
StretchDirection="Both" Stretch="Uniform"
Source="{Binding ElementName=FullNameComboBox, Path=SelectedItem.PictureId, Converter={StaticResource ToImageSourceConverter}, UpdateSourceTrigger=PropertyChanged}"
/>
</Border>
<TextBox x:Name="IdTextBox" Text="{Binding ElementName=FullNameComboBox, Path=SelectedItem.Id, UpdateSourceTrigger=PropertyChanged}" Grid.Row="0" Grid.Column="1" Visibility="Collapsed"/>
<ComboBox x:Name="FullNameComboBox" Grid.Row="10" Grid.Column="1" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath = "FullName"
SelectedIndex="0"
SelectionChanged="FullNameComboBox_OnSelectionChanged"
/>
<TextBox x:Name="GenderTextBox" Text="{Binding ElementName=FullNameComboBox, Path=SelectedItem.GenderName, UpdateSourceTrigger=PropertyChanged}" Grid.Row="11" Grid.Column="1" IsReadOnly="True" IsReadOnlyCaretVisible="True"/>
</Grid>
Here is my BusinessLogicLayer
public class PatientMgr :INotifyPropertyChanged
{
#region Fields
private readonly PatientDb _db;
private Patient _entity;
private List<Patient> _entityList;
private ObservableCollection<Patient> _comboBoxItemsCollection;
private Patient _selectedItem;
#endregion
#region Properties
public Patient Entity
{
get { return _entity; }
set
{
if (Equals(value, _entity)) return;
_entity = value;
OnPropertyChanged();
}
}
public List<Patient> EntityList
{
get { return _entityList; }
set
{
if (Equals(value, _entityList)) return;
_entityList = value;
OnPropertyChanged();
}
}
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get { return _comboBoxItemsCollection; }
set
{
if (Equals(value, _comboBoxItemsCollection)) return;
_comboBoxItemsCollection = value;
OnPropertyChanged();
}
}
public Patient SelectedItem
{
get { return _selectedItem; }
set
{
if (Equals(value, _selectedItem)) return;
_selectedItem = value;
OnPropertyChanged();
}
}
#endregion
#region Constructor
public PatientMgr()
{
_db = new PatientDb();
Entity = new Patient();
EntityList = new List<Patient>();
Parameters = new Patient();
ComboBoxItemsCollection = new ObservableCollection<Patient>(_db.RetrieveMany(Entity));
SelectedItem = ComboBoxItemsCollection[0];
}
#endregion
public List<Patient> RetrieveMany(Patient parameters)
{
return _db.RetrieveMany(parameters);
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Because ItemsSource used statically, your ComboBox doesn't get notified when it's source changed. Try using an instance of PatientMgr as your UserControl.DataContext like this:
public partial class YourUserControl: UserControl
{
private PatientMgr PatientsViewModel;
public YourUserControl()
{
InitializeComponent();
PatientsViewModel = new PatientMgr();
DataContext = PatientsViewModel;
}
public void AddComboItem(Patient item)
{
PatientsViewModel.ComboBoxItemsCollection.Add(item);
}
public void UpdateComboItem(Patient item)
{
//your update code
}
After removing static bindings, your XAML should looks like this:
<UserControl.Resources>
<common:ImageSourceConverter x:Key="ToImageSourceConverter" />
</UserControl.Resources>
<Grid x:Name="DetailsGrid"
<Border Grid.Row="1" Grid.RowSpan="8" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Turquoise" BorderThickness="2">
<Image x:Name="InsideButtonImage" ...
Since ComboBoxItemsCollection is an ObservableCollection, it notifies UI on add/remove automatically, however you have to write proper update functionality in order to force UI to refresh.
EDIT:
To implement an update method by taking advantage of Editable feature of ComboBox you could follow these steps:
Defined an integer property to track the last valid index (index != -1) of combo-box selected item and bind it to FullNameComboBox.SelectedIndex.
Defined a string property that represents the text value of combo-box selected item and bind it to FullNameComboBox.Text (the binding should trigger on LostFocus so the changes only accepted when user finished typing).
Find and remove ComboBoxItemsCollection.OldItem (find it by last valid index) and insert ComboBoxItemsCollection.ModifiedItem when FullNameComboBox.Text changes. Using remove and insert instead of assigning (OldItem = ModifiedItem;) will force UI to update.
In PatientMgr Code:
public int LastValidIndex
{
get { return _lastIndex; }
set
{
if (value == -1) return;
_lastIndex = value;
OnPropertyChanged();
}
}
public string CurrentFullName
{
get
{
return SelectedItem.FullName;
}
set
{
var currentItem = SelectedItem;
ComboBoxItemsCollection.RemoveAt(LastValidIndex);
currentItem.FullName = value;
ComboBoxItemsCollection.Insert(LastValidIndex, currentItem);
SelectedItem = currentItem;
}
}
In UserControl.Xaml :
<ComboBox x:Name="FullNameComboBox" Grid.Row="1" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection,
UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedItem}"
SelectedIndex="{Binding LastValidIndex}"
IsTextSearchEnabled="False"
Text="{Binding CurrentFullName, UpdateSourceTrigger=LostFocus}"
DisplayMemberPath = "FullName"
/>
I don’t like this:
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get { return _comboBoxItemsCollection; }
set
{
if (Equals(value, _comboBoxItemsCollection)) return;
_comboBoxItemsCollection = value;
OnPropertyChanged();
}
}
Try this:
OnPropertyChanged(“ComboBoxItemsCollection”);
And, are you sure this equals is resolved right?
if (Equals(value, _comboBoxItemsCollection)) return;
Try to debug it …
Your problem has to do with that IsEditable="True" set on the ComboBox. Your bindings work fine for me.
Here is what i've just tried:
Simple Patient model with its FullName property:
public class Patient : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _FullName;
public string FullName
{
get { return _FullName; }
set
{
_FullName = value;
PropertyChanged(this, new PropertyChangedEventArgs("FullName"));
}
}
}
This is the XAML:
<Window x:Class="PatientsStack.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PatientsStack"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:PatientsMgr x:Key="PatientsViewModel"/>
<ObjectDataProvider x:Key="ItemsSource" ObjectInstance="{StaticResource PatientsViewModel}" />
</Window.Resources>
<Grid DataContext="{StaticResource ItemsSource}">
<StackPanel>
<ComboBox x:Name="FullNameComboBox" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection, UpdateSourceTrigger=PropertyChanged}"
TextSearch.TextPath="FullName"
DisplayMemberPath="FullName"
SelectedItem="{Binding SelectedPatient}"
Text="{Binding SelectedPatient.FullName, Mode=TwoWay}"
VerticalAlignment="Top"
IsTextSearchEnabled="False"
SelectedIndex="0">
</ComboBox>
<Button Content="Change name" Command="{Binding ChangeNameCommand}" CommandParameter="{Binding ElementName=FullNameComboBox, Path=SelectedItem}"/>
<Button Content="Add patient" Command="{Binding AddPatientCommand}" CommandParameter="{Binding ElementName=FullNameComboBox, Path=SelectedItem}"/>
</StackPanel>
</Grid>
This two pieces right here did the job:
SelectedItem="{Binding SelectedFilter}"
Text="{Binding SelectedPatient.FullName, Mode=TwoWay}"
Without the Text binding, the updated worked, but you didn't see it unless you click the drop down.
As you see, i have 2 command to change one existing Patient's FullName or to add a new one. Both work as expected.
Here is the ViewModel for this:
public class PatientsMgr : INotifyPropertyChanged
{
private ObservableCollection<Patient> _ComboBoxItemsCollection;
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get
{
return _ComboBoxItemsCollection;
}
set
{
_ComboBoxItemsCollection = value;
PropertyChanged(this, new PropertyChangedEventArgs("ComboBoxItemsCollection"));
}
}
private Patient _SelectedPatient;
public Patient SelectedPatient
{
get { return _SelectedPatient; }
set
{
_SelectedPatient = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedPatient"));
}
}
public ICommand ChangeNameCommand { get; set; }
public ICommand AddPatientCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public PatientsMgr()
{
ComboBoxItemsCollection = new ObservableCollection<Patient>();
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient1" });
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient2" });
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient3" });
ChangeNameCommand = new RelayCommand<Patient>(ChangePatientName);
AddPatientCommand = new RelayCommand<Patient>(AddPatient);
}
public void ChangePatientName(Patient patient)
{
patient.FullName = "changed at request";
}
public void AddPatient(Patient p)
{
ComboBoxItemsCollection.Add(new Patient() { FullName = "patient added" });
}
}
I am posting my RelayCommand too, but i am sure you have it defined for your actions:
public class RelayCommand<T> : ICommand
{
public Action<T> _TargetExecuteMethod;
public Func<T, bool> _TargetCanExecuteMethod;
public RelayCommand(Action<T> executeMethod)
{
_TargetExecuteMethod = executeMethod;
}
public bool CanExecute(object parameter)
{
if (_TargetExecuteMethod != null)
return true;
return false;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
T tParam = (T)parameter;
if (_TargetExecuteMethod != null)
_TargetExecuteMethod(tParam);
}
}

Categories

Resources