I have a viewmodel named AllUserViewModel that contains a list of UserViewModels.
The UserViewModel is binded with a UserControl.
This is my MainWindow, foreach user I add a UserControl to a stackpanel container.
public partial class MainWindow : Window
{
public AllUserViewModel allUserViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
DwmDropShadow.DropShadowToWindow(this);
allUserViewModel = new AllUserViewModel();
this.DataContext = allUserViewModel;
allUserViewModel.Users.Add(new UserViewModel(new User(1, "Robby", "Bezet")));
allUserViewModel.Users.Add(new UserViewModel(new User(2, "Erwin", "Bezet")));
allUserViewModel.Users.Add(new UserViewModel(new User(3, "Kevin", "Bezet")));
foreach (UserViewModel u in allUserViewModel.Users)
{
Container.Children.Add(new UserControlButton(u));
}
Container.MouseEnter += new MouseEventHandler(Container_MouseEnter);
}
void Container_MouseEnter(object sender, MouseEventArgs e)
{
UserViewModel u = allUserViewModel.GetUser(2);
u.Name = "Laurens"; // Doesn't work
}
}
To each UserControlButton I pass a UserViewModel
public partial class UserControlButton : UserControl
{
public UserViewModel userViewModel { get; set; }
public UserControlButton(UserViewModel u)
{
this.InitializeComponent();
this.DataContext = u;
}
}
And this is my UserControlButton
<UserControl
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:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:ee="http://schemas.microsoft.com/expression/2010/effects"
mc:Ignorable="d"
x:Class="UserControlSolution.UserControlButton"
x:Name="UserControl"
Height="50" Background="#FF2F2F2F"
VerticalAlignment = "Top"
Margin="2,0,0,5"
>
<StackPanel x:Name="UserContainer" Orientation="Vertical" VerticalAlignment="Center" Grid.Column="1" Background="{DynamicResource DarkGrey}">
<TextBlock x:Name="NameLabel" FontSize="16" Foreground="#FFE5E5E5" Text="{Binding Name}" VerticalAlignment="Top" FontFamily="Segoe UI Semibold" RenderTransformOrigin="0.5,0.5" Margin="0,0,0,2"/>
<TextBlock x:Name="UserStatusLabel" Text="{Binding UserStatus}" TextWrapping="NoWrap" VerticalAlignment="Top" Foreground="#FFE5E5E5" />
</StackPanel>
</UserControl>
AllUserViewmodel
public class AllUserViewModel : BaseViewModel
{
ObservableCollection<UserViewModel> _users;
public AllUserViewModel()
{
Users = new ObservableCollection<UserViewModel>();
}
/// <summary>
/// Observable Collection of Users
/// Uses INotifyPropertyChange when list changes
/// </summary>
public ObservableCollection<UserViewModel> Users
{
get { return _users; }
set
{
if (_users != value)
{
_users = value;
NotifyPropertyChanged("Users");
}
}
}
public void AddUser(User user)
{
UserViewModel userViewModel = new UserViewModel(user);
Users.Add(userViewModel);
}
public UserViewModel GetUser(int ID)
{
foreach (UserViewModel u in Users)
{
if(u.ID == ID)
return u;
}
return null;
}
}
And UserViewModel
public class UserViewModel : BaseViewModel
{
readonly User _user;
public UserViewModel(User user)
{
if (user == null)
throw new ArgumentNullException("User");
_user = user;
}
public string Name
{
get { return _user.Name; }
set
{
if (value == _user.Name)
return;
_user.Name = value;
NotifyPropertyChanged("UserName");
}
}
public string UserStatus
{
get { return _user.UserStatus; }
set
{
if (value == _user.UserStatus)
return;
_user.UserStatus = value;
NotifyPropertyChanged("UserStatus");
}
}
public int ID
{
get { return _user.ID; }
}
}
The problem is that the 3 users are shown initially, but when I try to change the name on the mouse enter event, the name is not changed although the NotifyPropertyChanged was triggered.
public string Name
{
get { return _user.Name; }
set
{
if (value == _user.Name)
return;
_user.Name = value;
NotifyPropertyChanged("UserName");
}
}
Your property is called Name but you raise a PropertyChanged event for a property called UserName !
Related
I am creating an application with an MVVM model, in one of my views I have an ObservableCollection where by means of a button I create a new element and it appears on the screen, the problem is that I have a button to update that changes the name of the ListViewItem , and this name doesn't change until I switch between views
Problem
The DNP3-Master are my Items and the button I activate changes the name to "Test" but it is not updated until I change my view (this is a UserControl)
MasterViwModel
class MasterViewModel : ObservableObject
{
public ushort count { get; set; }
public ObservableCollection<MasterTraceModel> MasterReference { get; set; }
public RelayCommand CreateMaster { get; set; }
public RelayCommand Update { get; set; }
private ObservableCollection<MasterModel> _masterList;
public ObservableCollection<MasterModel> MasterList
{
get { return _masterList; }
set { _masterList = value; OnPropertyChanged(); }
}
private MasterModel _selectedMaster;//SelectedItem from ListView
public MasterModel SelectedMaster
{
get { return _selectedMaster; }
set { _selectedMaster = value; OnPropertyChanged(); }
}
public MasterViewModel()
{
MasterList = new ObservableCollection<MasterModel>();//my Observable Collections
//Stuff
this.count = 1;
//Stuff
CreateMaster = new RelayCommand(o =>
{
MasterList.Add(new MasterModel(this.count, "127.0.0.1", "20000", runtime));
this.count = (ushort)(count + 1);
});//Here I add the elements to my ObservableCollections
//Stuff
Update = new RelayCommand(o =>
{
SelectedMaster.SetName("Test");
});
}
}
MasterView
<UserControl x:Class="Prototype.MVVM.View.MasterView"
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:viewmodel="clr-namespace:Prototype.MVVM.ViewModel"
d:DataContext="{d:DesignInstance Type=viewmodel:MasterViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Border Margin="20,20,0,20" Background="#151515" CornerRadius="8">
<ListView Name="MasterListView" Margin="5"
ItemsSource="{Binding MasterList}"
SelectedItem="{Binding SelectedMaster}"
ItemContainerStyle="{StaticResource MasterTheme}"
Background="Transparent"
BorderThickness="0"
/>
</Border>
<StackPanel Grid.Column="1" Margin="0,20,0,0">
<Button Margin="0,0,0,10" Grid.Column="1" Style="{StaticResource SmallBtn}" Command="{Binding Update}">
<Image Height="24" Width="24" Source="/Icons/cil-reload.png" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
</Button>
</StackPanel>
</Grid>
</UserControl>
MasterModel
class MasterModel : ObservableObject
{
public string Name { get; set; }
public ushort Adress { get; set; }
public string Host { get; set; }
public string Port { get; set; }
public Runtime _runtime { get; set; }
public MasterChannel channel { get; set; }
public ConnectStrategy CStrategy { get; set; }
public string[] Delay { get; set; }
public MasterModel(ushort Adress, string Host, string Port, Runtime runtime)
{
this.Name = "DNP3-Master-" + Adress.ToString();
this.Adress = Adress;
this.Host = Host;
this.Port = Port;
this._runtime = runtime;
CStrategy = new ConnectStrategy();
//CStrategy.MinConnectDelay = new TimeSp
Delay = new string[3];
Delay[0] = CStrategy.MinConnectDelay.ToString();
Delay[1] = CStrategy.MaxConnectDelay.ToString();
Delay[2] = CStrategy.ReconnectDelay.ToString();
this.channel = MasterChannel.CreateTcpChannel(//Stuff);
}
public void SetName(string name)
{
this.Name = name;
}
public void Star(Runtime runtime)
{
Task.Run(async () =>
{
try
{
await MasterFunctions.RunChannel(channel);
}
finally
{
runtime.Shutdown();
}
});
}
The MasterModel class should implement the INotifyPropertyChanged event and raise the PropertyChanged event for the data-bound property when you call SetName:
private string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
Using an ObservableCollection<T> doesn't replace the need to implement INotifyPropertyChanged and raise change notifications for the individual items in the collection. It notifies the view when items are added to and removed from the collection only.
Goal
My actual goal is to navigate the ContentPresenter not by the main navigation, but via a button within the navigated page.
My current results
This is my Main navigation on the left hand side:
When clicked on either of the main navigation items, the ContentPresenter will load it's ViewModel.
Here is the Home tab
and the Some Other tab
Expected results
My expectation is to click on the button (See image below) from the loaded View Model, and navigate to the other view model...
But I am not sure how to implement such idea.
Code
Page View Model
public class PageViewModel
{
public string Title { get; set; }
public object Content { get; set; }
public List<PageViewModel> Children { get; set; }
}
Main View Model
public class MainViewModel
{
public List<PageViewModel> Navigation { get; set; }
public MainViewModel()
{
Navigation = new List<PageViewModel>
{
new PageViewModel
{
Title = "Home",
Content = new HomeViewModel()
},
new PageViewModel
{
Title = "Some Other Tab",
Content = new SomeOtherViewModel()
}
};
}
}
MainWindow.xaml
...
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<ListView ItemsSource="{Binding Navigation}"
x:Name="Nav">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type local:PageViewModel}">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<ContentPresenter Content="{Binding ElementName=Nav, Path=SelectedItem.Content}"
Grid.Column="1"/>
</Grid>
</Window>
Home View Model
public class HomeViewModel
{
public string SomeTitle { get; set; }
public HomeViewModel()
{
SomeTitle = "Hello Home ViewModel";
}
}
Some Other View Model
public class SomeOtherViewModel
{
public string SomeTitle { get; set; }
public SomeOtherViewModel()
{
SomeTitle = "Hello SomeOther View Model";
}
}
Question
What would be the correct implementation to navigate via the internal (child) view model?
You must implement INotifyPropertyChanged in the MainViewModel and add a property called SelectedItem to bind to the listview.
I put the code to do this below. The code works properly.
PageViewModel.cs
public class PageViewModel
{
public string Title { get; set; }
public object Content { get; set; }
}
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
public List<PageViewModel> Navigation { get; set; }
private PageViewModel selectedItem { get; set; }
public PageViewModel SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
public MainViewModel()
{
Navigation = new List<PageViewModel>
{
new PageViewModel
{
Title = "Home",
Content = new HomeViewModel(this),
},
new PageViewModel
{
Title = "Some Other Tab",
Content = new SomeOtherViewModel(),
}
};
SelectedItem = Navigation.FirstOrDefault();
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
HomeViewModel.cs
public class HomeViewModel
{
public string SomeTitle { get; set; }
public object View { get; set; }
MainViewModel mainViewModel;
public RelayCommand SomeOtherCommand { get; private set; }
public HomeViewModel(MainViewModel _mainViewModel)
{
SomeTitle = "Hello Home ViewModel";
View = new View1(this);
mainViewModel = _mainViewModel;
SomeOtherCommand = new RelayCommand(SomeOtherMethod);
}
private void SomeOtherMethod(object parameter)
{
mainViewModel.SelectedItem = mainViewModel.Navigation.Where(a => a.Title == "Some Other Tab").FirstOrDefault();
}
}
SomeOtherViewModel.cs
public class SomeOtherViewModel
{
public string SomeTitle { get; set; }
public object View { get; set; }
public SomeOtherViewModel()
{
SomeTitle = "Hello SomeOther View Model";
View = new View2();
}
}
RelayCommand.cs
public class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new NullReferenceException("execute");
}
else
{
_execute = execute;
_canExecute = canExecute;
}
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
public RelayCommand(Action<object> execute) : this(execute, null)
{
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute.Invoke(parameter);
}
}
MainWindow.xaml
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<ListView ItemsSource="{Binding Navigation}" x:Name="Nav" SelectedItem="{Binding Path=SelectedItem,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type local:PageViewModel}">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<ContentPresenter Content="{Binding ElementName=Nav, Path=SelectedItem.Content.View}" Grid.Column="1" />
</Grid>
View1.xaml
<Grid>
<Button x:Name="btnGet" Content="get" Height="40" Command="{Binding SomeOtherCommand}"></Button>
</Grid>
View2.xaml
<Grid>
<Button Content="test"></Button>
</Grid>
View1.cs
public partial class View1 : UserControl
{
public View1(HomeViewModel homeViewModel)
{
InitializeComponent();
DataContext = homeViewModel;
}
}
Im learning MVVM at the moment and made a Login Window which opens first.
After Logging in, my MainWindow opens. The MainWindows Title is set via Property in MainWindowViewModel but doesn't shown (its empty) now when I open this window via LoginWindow instead of making it the StartUp Window.
This is the Code how I open MainWindow from Login.
LoginViewModel.cs
if (r)
{
CurrentUser.Username = Username;
Messenger.Default.Send(new NotificationMessage("CloseWindow"));
}
LoginView.xaml.cs
public Login()
{
InitializeComponent();
Messenger.Default.Register<NotificationMessage>(this, (message) =>
{
switch (message.Notification)
{
case "CloseWindow":
Messenger.Default.Send(new NotificationMessage("NewCourse"));
var MainWindow = new MainWindow();
MainWindow.Show();
this.Close();
break;
}
});
}
MainViewModel.cs
public MainViewModel()
{
if (IsInDesignMode)
{
WindowTitle = "Controlcenter (Designmode)";
CurrentUserLoggedIn = "Logged in as: " + CurrentUser.Username;
CurrentVersion = "Version: " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
}
else
{
WindowTitle = "Controlcenter";
CurrentUserLoggedIn = "Logged in as: " + CurrentUser.Username;
CurrentVersion = "Version: " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
}
}
public string WindowTitle { get; private set; }
public string CurrentUserLoggedIn { get; private set; }
public string CurrentVersion { get; private set; }
I dont know why but I think MainViewModel() isn't called.
Im using MVVMLight and PropertyChanged.Fody.
So my ViewModelLocator looks like this
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<DataErrorInfoViewModel>();
SimpleIoc.Default.Register<LoginViewModel>();
}
public MainViewModel Main => ServiceLocator.Current.GetInstance<MainViewModel>();
public LoginViewModel Login => ServiceLocator.Current.GetInstance<LoginViewModel>();
public DataErrorInfoViewModel DataErrorInfo => ServiceLocator.Current.GetInstance<DataErrorInfoViewModel>();
Is there something wrong in ViewModelLocator?
Edit:
MainWindow.xaml
<Window x:Class="Ui.Desktop.MainWindow"
[...]
xmlns:logic="clr-namespace:Logic.Ui;assembly=ControlcenterMVVM.Logic.Ui"
Title="{Binding WindowTitle, Mode=OneWay}"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Window.Resources>
<DataTemplate x:Name="firmcustomerViewTemplate" DataType="{x:Type logic:FirmcustomerViewModel}">
<local:Firmcustomer DataContext="{Binding}" />
</DataTemplate>
<DataTemplate x:Name="privatecustomerViewTemplate" DataType="{x:Type logic:PrivatecustomerViewModel}">
<local:Privatecustomer DataContext="{Binding}" />
</DataTemplate>
</Window.Resources>
<Grid>
[...]
<Label Content="{Binding CurrentUser}" FontWeight="Normal" FontSize="13" />
<Label Content="{Binding CurrentVersion}" />
</StackPanel>
<ContentControl Grid.Row="0" Grid.Column="1" Grid.RowSpan="3" Content="{Binding}" />
</Grid>
And MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new FirmcustomerViewModel();
}
private void firmcustomer_Click(object sender, RoutedEventArgs e)
{
DataContext = new FirmcustomerViewModel();
}
private void privatecustomer_Click(object sender, RoutedEventArgs e)
{
DataContext = new PrivatecustomerViewModel();
}
}
So what does PropertyChanged.Fody for me now?
In MainWindowViewModel i just add the Properties as follows:
public string WindowTitle { get; private set; }
public string CurrentUserLoggedIn { get; private set; }
public string CurrentVersion { get; private set; }
No I compile the Project and use dotpeek to decompile the project and see, how it looks like now
public string WindowTitle
{
get
{
return this.\u003CWindowTitle\u003Ek__BackingField;
}
private set
{
if (string.Equals(this.\u003CWindowTitle\u003Ek__BackingField, value, StringComparison.Ordinal))
return;
this.\u003CWindowTitle\u003Ek__BackingField = value;
this.RaisePropertyChanged(nameof (WindowTitle));
}
}
public string CurrentUserLoggedIn
{
get
{
return this.\u003CCurrentUserLoggedIn\u003Ek__BackingField;
}
private set
{
if (string.Equals(this.\u003CCurrentUserLoggedIn\u003Ek__BackingField, value, StringComparison.Ordinal))
return;
this.\u003CCurrentUserLoggedIn\u003Ek__BackingField = value;
this.RaisePropertyChanged(nameof (CurrentUserLoggedIn));
}
}
public string CurrentVersion
{
get
{
return this.\u003CCurrentVersion\u003Ek__BackingField;
}
private set
{
if (string.Equals(this.\u003CCurrentVersion\u003Ek__BackingField, value, StringComparison.Ordinal))
return;
this.\u003CCurrentVersion\u003Ek__BackingField = value;
this.RaisePropertyChanged(nameof (CurrentVersion));
}
}
So RaisePropertyChaned is there.
The button click does not fire if I use the DelegateCommand from Prism. If I write a command class for button click and not use Prism, then it works. This is my code with Prism:
This is my View XAML:
<Window x:Class="MVVMPractice2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Label Content="Customer Name" HorizontalAlignment="Left" Margin="0,0,0,292.8"></Label>
<Label Name="lblName" HorizontalAlignment="Left" Margin="108,0,0,292.8" Width="37" Content="{Binding TxtCustomerName}"></Label>
<Label Content="Sales Amount" HorizontalAlignment="Left" Margin="0,28,0,264.8"></Label>
<TextBox Name="lblAmount" HorizontalAlignment="Left" Margin="101,28,0,264.8" Width="44" Text="{Binding TxtAmount}"></TextBox>
<Label Content="Buying Habits" HorizontalAlignment="Left" Margin="0,56,0,236.8"></Label>
<Label Name="lblBuyingHabits" HorizontalAlignment="Left" Margin="108,56,0,236.8" Width="52" Background="{Binding LblAmountColor}"></Label>
<Label Content="Married" HorizontalAlignment="Left" Margin="0,84,0,208.8" Width="62"></Label>
<CheckBox Name="chkMarried" HorizontalAlignment="Left" Margin="102,84,0,208.8" IsChecked="{Binding IsMarried}"></CheckBox>
<Label Content="Tax" HorizontalAlignment="Left" Margin="0,112,0,180.8"></Label>
<TextBlock Name="lblTax" HorizontalAlignment="Left" Margin="108,117,0,175.8" Width="37" Text="{Binding TaxAmount}"></TextBlock>
<Button Name="btnTax" Content="Calculate Tax" Margin="118,158,287.4,123.8" Command="{Binding UpdateCommand}"></Button>
</Grid>
</Window>
And the ViewModel:
public class MainWindowViewModel : BindableBase
{
//instantiate the model
private Customer customer = new Customer();
//property for button click command
private DelegateCommand UpdateCommand;
//constructor to instantiate the buttons click command
public MainWindowViewModel()
{
UpdateCommand = new DelegateCommand(customer.CalculateTax, customer.IsValid);
}
//this property maps customer name from model to the view
public string TxtCustomerName
{
get { return customer.CustomerName; }
set { customer.CustomerName = value; }
}
//this property maps amount from model to the view
public string TxtAmount
{
get { return Convert.ToString(customer.Amount); }
set { customer.Amount = Convert.ToDouble(value); }
}
//this property maps and transforms color from model to the view
public string LblAmountColor
{
get
{
if (customer.Amount > 2000)
{
return "Blue";
}
else if (customer.Amount > 1500)
{
return "Red";
}
return "Yellow";
}
}
//this property maps and transforms married from model to the view
public bool IsMarried
{
get
{
if (customer.Married == "Married")
{
return true;
}
else if (customer.Married == "UnMarried")
{
return false;
}
return false;
}
set
{
if (value)
{
customer.Married = "Married";
}
else
{
customer.Married = "UnMarried";
}
}
}
//this property maps tax from model to the view
public string TaxAmount
{
get { return Convert.ToString(customer.Tax); }
set { customer.Tax = Convert.ToDouble(value); }
}
}
and the Model:
public class Customer
{
//model property
private string customerName = "Ivan";
public string CustomerName
{
get { return customerName; }
set { customerName = value; }
}
//model property
private double amount = 2000;
public double Amount
{
get { return amount; }
set { amount = value; }
}
//model property
private string married = "Married";
public string Married
{
get { return married; }
set { married = value; }
}
//model property
private double tax;
public double Tax
{
get { return tax; }
set { tax = value; }
}
//this modifier calculates the tax
public void CalculateTax()
{
if (amount > 2000)
{
tax = 20;
}
else if (amount > 1000)
{
tax = 10;
}
else
{
tax = 5;
}
}
//this modifier validates the amount
public bool IsValid()
{
if (amount < 0)
{
return false;
}
else
{
return true;
}
}
}
As pointed to in the comment above, changing the field to a property helps, and firing NotifyPropertyChanged (removed irrelevant parts):
public class MainWindowViewModel : BindableBase
{
//property for button click command
public DelegateCommand UpdateCommand { get; }
//constructor to instantiate the buttons click command
public MainWindowViewModel()
{
UpdateCommand = new DelegateCommand(() => {
customer.CalculateTax();
OnPropertyChanged( () => TaxAmount );
}, customer.IsValid);
}
//this property maps tax from model to the view
public string TaxAmount
{
get { return Convert.ToString(customer.Tax); }
set { customer.Tax = Convert.ToDouble(value); }
}
}
Alternatively, make Customer implement INotifyPropertyChanged and use something like PropertyObserver to pass the events to the view.
I am working on the ability to add and delete rows in an observable collection.
Since the original post I have created a test app that only has the ability to delete rows from the observable collection. I populate the database externally then open it in this just to test the delete function which doesn't work. It executes the RemoveAt line, deletes from the Observable Collection but the view does not update. Here is all my code:
Model:
public class TestModel : ObservableObject
{
#region Properties
private Double id;
public Double ID
{
get { return id; }
set
{
id = value;
RaisePropertyChangedEvent("ID");
}
}
private string type;
public string Type
{
get { return type; }
set
{
type = value;
RaisePropertyChangedEvent("Type");
}
}
private decimal amount;
public decimal Amount
{
get { return amount; }
set
{
amount = value;
RaisePropertyChangedEvent("Amount");
}
}
private string notes;
public string Notes
{
get { return notes; }
set
{
notes = value;
RaisePropertyChangedEvent("Notes");
}
}
#endregion
}
Viewmodel:
public class MainWindowViewModel : ObservableObject
{
#region GetData
public MainWindowViewModel()
{
Transactions = DatabaseFunctions.getTransactionData();
}
#endregion
#region ObservableCollections
private ObservableCollection<TestModel> transactions;
public ObservableCollection<TestModel> Transactions
{
get { return transactions; }
set
{
transactions = value;
RaisePropertyChangedEvent("Transactions");
}
}
#endregion
#region Properties
public static string SharedWith;
private Double id;
public Double ID
{
get { return id; }
set
{
id = value;
RaisePropertyChangedEvent("ID");
}
}
private string type;
public string Type
{
get { return type; }
set
{
type = value;
RaisePropertyChangedEvent("Type");
}
}
private decimal amount;
public decimal Amount
{
get { return amount; }
set
{
amount = value;
RaisePropertyChangedEvent("Amount");
}
}
private string notes;
public string Notes
{
get { return notes; }
set
{
notes = value;
RaisePropertyChangedEvent("Notes");
}
}
#endregion
public void DeleteTransactionRow(List<TestModel> SelectedTransaction, int SelectedIndex)
{
Transactions.RemoveAt(SelectedIndex);
}
View:
<Window x:Class="OCTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:Properties="clr-namespace:OCTest.Properties"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xcdg="http://schemas.xceed.com/wpf/xaml/datagrid"
Title="Test" SizeToContent="WidthAndHeight"
xmlns:ViewModel="clr-namespace:OCTest.ViewModel">
<Window.DataContext>
<ViewModel:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<xcdg:DataGridControl x:Name="TransactionsDataGrid" Grid.Row="0" ItemsSource="{Binding Transactions, Mode=TwoWay}" AutoCreateColumns="False" SelectionMode="Single">
<xcdg:DataGridControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete Row" Click="DeleteTransactionRow_Click"/>
</ContextMenu>
</xcdg:DataGridControl.ContextMenu>
<xcdg:DataGridControl.Columns>
<xcdg:Column Title="Type" FieldName="Type" ReadOnly="True"/>
<xcdg:Column Title="Amount" FieldName="Amount">
<xcdg:Column.CellContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding StringFormat={}{0:C}}"/>
</DataTemplate>
</xcdg:Column.CellContentTemplate>
</xcdg:Column>
<xcdg:Column Title="Notes" FieldName="Notes"/>
</xcdg:DataGridControl.Columns>
</xcdg:DataGridControl>
</Grid>
Code behind that deals with the delete command and passes needed information to the view model:
public partial class MainWindow : Window
{
MainWindowViewModel mainwindowviewmodel = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
}
private void DeleteTransactionRow_Click(object sender, RoutedEventArgs e)
{
List<TestModel> selectedtransaction = TransactionsDataGrid.SelectedItems.Cast<TestModel>().ToList();
mainwindowviewmodel.DeleteTransactionRow(selectedtransaction, TransactionsDataGrid.SelectedIndex);
}
private void MouseRightButtonUpHandler(object sender, RoutedEventArgs e)
{
this.TransactionsDataGrid.SelectedItem = ((DataCell)sender).ParentRow.DataContext;
}
}
So hopefully someone can see why the RemoveAt doesn't update the view.
You need to set the DataContext of the MainWindow to your view model:
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
I'd also recommend looking into binding click events to commands in your view model rather than relying on the code behind.