c# - ObservableCollection doesn't seem save added items - c#

Description
I'm trying to build this simple UWP MVVM notetaking app.
The purpose of the app is adding text from a Textbox to a ListView, when a Add Button is clicked, or delete an item of the ListView by pressing a Delete Button, which is assigned to each item in the ListView.
Adding item to the ObservableCollection
Adding items to the ObservableCollection<Note> seems to work fine. The items are shown in the ListView without any problems.
Deleting items in the ObservableCollection
Deleting item doesn't work as it should.
My debug attempt
I tried to invoke the method responsible for deleting the items from both the constructor and the Delete Button.
When I invoke DoDeleteNote(Note itemToDelete) from the Delete Button, nothing happens, but if i invoke the same method from the constructor then the item is deleted.
I created a breakpoint in the DoDeleteNote(Note itemToDelete) method, and I can see in the debugger that it runs through the code, but nothing is deleted from the ObservableCollection<Note>.
However when i invoke the DoDeleteNote(Note itemToDelete) method from the constructor the item is deleted.
It is also strange, that the Note items I create and add to the ObservableCollection<Note> from the NoteViewModel constructor are the only items that are in the ObservableCollection<Note>. The items I add using the Add Button, are gone, but still shown in the ListView.
I’m thinking maybe something is wrong with either INotifyPropertyChanged or the bindings, but i’m not sure where to start looking, and what to look for, so I could use some help.
I know there seems to be a lot of code here, but I felt it was necessary not to omit anything to understand the data flow.
XAML
<Page
x:Class="ListView2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ListView2"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModel="using:ListView2.ViewModel"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.DataContext>
<viewModel:NoteViewModel/>
</Grid.DataContext>
<ListView Header="Notes"
HorizontalAlignment="Left"
Height="341"
Width="228"
VerticalAlignment="Top"
Margin="163,208,0,0"
ItemsSource="{Binding Notes, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate x:Name="MyDataTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="TbxblListItem" Text="{Binding NoteText}"/>
<Button Command="{Binding DeleteNoteCommand}"
CommandParameter="{Binding ElementName=TbxblListItem}">
<Button.DataContext>
<viewModel:NoteViewModel/>
</Button.DataContext>
<Button.Content>
<SymbolIcon Symbol="Delete"
ToolTipService.ToolTip="Delete Note"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Button.Content>
</Button>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBox x:Name="TbxNoteContent" HorizontalAlignment="Left"
Margin="571,147,0,0"
TextWrapping="Wrap"
VerticalAlignment="Top"
Width="376"/>
<Button Content="Add"
HorizontalAlignment="Left"
Margin="597,249,0,0"
VerticalAlignment="Top"
Command="{Binding AddNoteCommand}"
CommandParameter="{Binding Text, ElementName=TbxNoteContent}"/>
</Grid>
</page>
Note
namespace ListView2.Model
{
class Note
{
private string _noteText;
public Note(string noteText)
{
NoteText = noteText;
}
public string NoteText { get { return _noteText; } set { _noteText = value; } }
}
}
Notification
using System.ComponentModel;
namespace ListView2.Model
{
class Notification : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
NoteViewModel
using System.Collections.ObjectModel;
using Windows.UI.Xaml.Controls;
using ListView2.Model;
namespace ListView2.ViewModel
{
class NoteViewModel : Notification
{
#region Instance Fields
private ObservableCollection<Note> _notes;
private RelayCommand _addNoteCommand;
private RelayCommand _deleteNoteCommand;
//private string _noteText;
#endregion
#region Constructors
public NoteViewModel()
{
//adds sample data to Notes property (ObservableCollection<Note>)
Notes = new ObservableCollection<Note>() { new Note("Sample text 1"), new Note("Sample text 2") };
//Used for testing the deletion of items from ObservableCollection-------------------------------------
Notes.RemoveAt(1);
Notes.Add(new Note("Sample text 3"));
//foreach (var item in Notes)
//{
// if (item.NoteText == "Sample text 3")
// {
// DoDeleteNote(item);
// break;
// }
//}
//------------------------------------------------------
//Button command methods are added to delegates
AddNoteCommand = new RelayCommand(DoAddNote);
DeleteNoteCommand = new RelayCommand(DoDeleteNote);
}
#endregion
#region Properties
public ObservableCollection<Note> Notes { get { return _notes; } set { _notes = value; OnPropertyChanged("Notes"); } }
//public string NoteText { get { return _noteText; } set { _noteText = value; OnPropertyChanged("NoteText"); } }
public RelayCommand AddNoteCommand { get { return _addNoteCommand; } set { _addNoteCommand = value; } }
public RelayCommand DeleteNoteCommand { get { return _deleteNoteCommand; } set { _deleteNoteCommand = value; } }
#endregion
#region methods
private void DoAddNote(object obj)
{
var newItem = obj as string;
if (!string.IsNullOrEmpty(newItem))
{
AddNote(newItem);
}
}
//Work in progress
private void DoDeleteNote(object obj)
{
//Used when the XAML Delete Button invokes this method
TextBlock textBlockSender = obj as TextBlock;
//string myString = textBlockSender.Text;
Note itemToDelete = textBlockSender.DataContext as Note;
//Used when the constuctor invokes this method, for testing purposes------------
//Note itemToDelete = obj as Note;
//--------------------------------------------------------
foreach (Note note in this.Notes)
{
if (note.NoteText == itemToDelete.NoteText)
{
//int noteIndex = Notes.IndexOf(note);
//Notes.RemoveAt(noteIndex);
DeleteNote(note);
break;
}
}
//if (Notes.Contains(itemToDelete))
//{
// Notes.Remove(itemToDelete);
//}
}
public void AddNote(string noteText)
{
this.Notes.Add(new Note(noteText));
}
public void DeleteNote(Note itemToDelete)
{
this.Notes.Remove(itemToDelete);
}
#endregion
}
}
RelayCommand class which is the implementation for ICommand does not seem relevant for the question, so I haven't included it here, but if you are curious it can be seen on GitHub

As #Eugene Podskal points out one of the problems is with this code
<Button.DataContext>
<viewModel:NoteViewModel/>
</Button.DataContext>
Your Layout Grid instantiates a new NoteViewModel and the above code will do the same leaving you with 2 NoteViewModels active on the page.
Firstly give the ListView a name
<ListView x:Name="MyList" Header="Notes"
Next let's fix the Bindings on your DataTemplate of ListView MyList
<ListView.ItemTemplate>
<DataTemplate x:Name="MyDataTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="TbxblListItem" Text="{Binding NoteText}"/>
<Button Command="{Binding DataContext.DeleteNoteCommand, ElementName=MyList}"
CommandParameter="{Binding}">
<SymbolIcon Symbol="Delete"
ToolTipService.ToolTip="Delete Note"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Button>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
this line
Command="{Binding DataContext.DeleteNoteCommand, ElementName=MyList}"
means we are now Binding to the DataContext of MyList which is your NoteViewModel as defined under the main Grid
The CommandParamter is simplified to
CommandParameter="{Binding}"
as I explain below it is better practice to Bind to the object in your MyList which in this case is an object of Note
To make this work we need to adjust your NoteViewModel slightly
Change your private delete field and public property to
private RelayCommand<Note> _deleteNoteCommand;
public RelayCommand<Note> DeleteNoteCommand { get { return _deleteNoteCommand; } set { _deleteNoteCommand = value; } }
and in the constructor
DeleteNoteCommand = new RelayCommand<Note>(DoDeleteNote);
DoDeleteNote method is simplified to
private void DoDeleteNote(Note note)
{
this.Notes.Remove(note);
}
so we handily get rid of casting from a TextBlock. You can now get rid of the DeleteNote method as it's no longer needed.
Finally we need to add a new RelayCommand that takes a Generic Type to make our Command DeleteNoteCommand work correctly.
RelayCommand
public class RelayCommand<T> : ICommand
{
#region Fields
private readonly Action<T> _execute = null;
private readonly Predicate<T> _canExecute = null;
#endregion
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command with conditional execution.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion
#region ICommand Members
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
var handler = CanExecuteChanged;
if (handler != null)
CanExecuteChanged(this, new EventArgs());
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
#endregion
}
Apologies this answer is long but I wanted to point out every step. I also suggest using a mvvm framework when working with xaml as it will make your life alot easier. I recommend mvvmlight but there are plenty of others. Hope that helps

Related

How to get Text from TextBox to ViewModel while it is bound to other control?(MVVM)

I have a simple app, that should add SelectedItem from ComboBox to ListBox.
I have Model:Player
public class Player
{
public int ID { get; set; }
public string Name { get; set; }
private bool _isSelected = false;
public bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; }
}
}
And ObservableCollection property in my ViewModel (Players)
public class ViewModel
{
public ObservableCollection<Player> Players { get; set; }
public ObservableCollection<Player> PlayersInTournament { get; set; } = new ObservableCollection<Player>();
public ICommand AddPlayerCommand { get; set; }
public ViewModel()
{
DataAccess access = new DataAccess();
Players = new ObservableCollection<Player>(access.GetPlayers());//GetPlayers from DataBase
AddPlayerCommand = new RelayCommand(AddPlayer, CanAddPlayer);
}
private void AddPlayer()
{
//Something like PlayersInTournamen.Add(SelectedPlayer);
}
private bool CanAddPlayer()
{
bool canAdd = false;
foreach(Player player in Players)
{
if (player.IsSelected == true)
canAdd = true;
}
return canAdd;
}
}
Property(ItemSource) of my ComboBox is bound to the Players collection. When the application is Loaded my ComboBox is filled with objects from DataBase and when I select one of them it is displayed in my ReadOnly TextBox. I achieved this by binding the Text property to the ItemSelected.Name property of ComboBox. There is an Add button in the app that add selected player to the tournament(ListBox)(the app is about tournament). ListBox's ItemSource is PlayersInTournament collection(see in ViewModel).
XAML(DataContext of Window is set to ViewModel instance after InitializeComponents()):
<Window x:Class="ComboBoxDemoSQL.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:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:local="clr-namespace:ComboBoxDemoSQL"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel>
<StackPanel HorizontalAlignment="Center"
Orientation="Horizontal" Margin="0 40 0 10">
<TextBox x:Name="HoldPlayerTextBox"
Width="100"
Text="{Binding ElementName=PlayersComboBox, Path=SelectedItem.Name}"
IsReadOnly="True">
</TextBox>
<ComboBox Name="PlayersComboBox"
VerticalAlignment="Top"
Margin="10 0 0 0"
HorizontalAlignment="Center" Width="100"
ItemsSource="{Binding Players}"
DisplayMemberPath="Name"
Text="Select player"
IsEditable="True"
IsReadOnly="True"/>
</StackPanel>
<Button Content="Add" Margin="120 0 120 0"
Command="{Binding AddPlayerCommand}"/>
<ListBox Margin="10" ItemsSource="{Binding PlayersInTournament}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding ID}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
Photo to understand better:
So basically there are 2 problems:
I don't know how to add to the PlayersInTournament collection a
player that is selected in ComboBox because I can't get the name
of that Player from TexBox(because its' Text property is bound to
another Property)
I don't know how to disable Add Button(CanAddPlayer method) when
there is no Player selected, I tried by adding IsSelected(see
Player model) property, but for it to work I have to bind to any
property in View that would change it, but I don't know which
property can be used for this thing.
ICommand implementation:
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private Action methodToExecute;
private Func<bool> canExecuteEvaluator;
public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
{
this.methodToExecute = methodToExecute;
this.canExecuteEvaluator = canExecuteEvaluator;
}
public RelayCommand(Action methodToExecute)
: this(methodToExecute, null)
{
}
public bool CanExecute(object parameter)
{
if (this.canExecuteEvaluator == null)
{
return true;
}
else
{
bool result = this.canExecuteEvaluator.Invoke();
return result;
}
}
public void Execute(object parameter)
{
this.methodToExecute.Invoke();
}
}
May I suggest the following.
You can override the ToString() method of your Player class to ease display in your ComboBox e.g.:
public class Player
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
By default ComboBox binding will call the ToString() method of whatever property it is bound to.
If you bind ComboBox.SelectedItem to a new Player property in the ViewModel, you can clear the selected player text in the ComboBox from code in the ViewModel.
If you add a CommandParameter to your Button binding, you can pass the selected player instance to the command, but this isn't strictly needed once you have a bound property in your ViewModel.
Thus your XAML becomes something like this:
<ComboBox x:Name="ComboBox"
HorizontalAlignment="Left"
Margin="0,0,0,0"
VerticalAlignment="Top"
Width="100"
Text="Select player"
SelectedItem="{Binding SelectedPlayer}"
ItemsSource="{Binding Players}"/>
<Button x:Name="ButtonAddPlayer"
Content="Add"
Command="{Binding AddPlayerCommand}"
CommandParameter="{Binding SelectedPlayer}"
HorizontalAlignment="Left"
Margin="62,176,0,0"
VerticalAlignment="Top"
Width="75"/>
And your ViewModel contains:
public ObservableCollection<Player> PlayersInTournament { get; set; }
public ObservableCollection<Player> Players { get; set; }
private Player _selectedPlayer;
public Player SelectedPlayer
{
get => _selectedPlayer;
set => SetField(ref _selectedPlayer, value);
}
public ICommand AddPlayerCommand { get; set; }
private bool CanAddPlayer(object obj)
{
return SelectedPlayer != null;
}
private void AddPlayer(object param)
{
if (param is Player player)
{
PlayersInTournament.Add(player);
Players.Remove(player);
SelectedPlayer = null;
};
}
Note that in the above code, as a player is added to the tournament list it is removed from the available players list preventing reselection of the same player.
Setting the SelectedPlayer property to null not only clears the ComboBox.SelectedItem display but also disables the Add button.
Also if you are likely to have several properties that you implement a helper function to handle your INotifyPropertyChanged events.
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
You can use CommandParameter in xaml:
<Button Content="Add" Margin="120 0 120 0"
Command="{Binding AddPlayerCommand}"
CommandParameter="{Binding Path=SelectedItem, Source=PlayersComboBox}"/>
in your ViewModel:
private ICommand _addPlayerCommand;
public ICommand AddPlayerCommand
{
get
{
if (_addPlayerCommand== null)
{
_addPlayerCommand= new RelayCommand(param => OnAddPlayerClicked(param));
}
return _addPlayerCommand;
}
}
private void AddPlayer(object param)
{
Player selectedPlayer = (player)param;
PlayersInTournamen.Add(SelectedPlayer);
}
RelayCommand:
public class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute ?? throw new ArgumentNullException("execute");
_canExecute = canExecute;
}
[DebuggerStepThrough]
public bool CanExecute(object parameters)
{
return _canExecute == null ? true : _canExecute(parameters);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameters)
{
_execute(parameters);
}
}
I hope this helps.

[UWP/MVVM]Enable/Disable Button in RadDataGrid Data Template Column that have commands bound to them upon conditions

I have set a bool property and have bound it to the IsEnabled in the xaml but the ICommand CanExecute method overrides the IsEnabled in xaml, so my bool property is ineffective.
When I define the conditions within the CanExecute method in the view model, It either disables all buttons in which the method is bound to, or enables all of them.
Its a grid that displays 3 different buttons for each row, and each button goes to a new xaml screen. If there is no data for the particular condition on the row the button is on then the button needs to be disabled.
How do i go about setting this so that buttons are disabled upon a condition?
Custom Command:
public class CustomCommand : ICommand
{
private Action<object> execute;
private Predicate<object> canExecute;
public CustomCommand(Action<object> execute, Predicate<object> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
}
remove
{
}
}
public bool CanExecute(object parameter)
{
//throw new NotImplementedException();
bool b = canExecute == null ? true : canExecute(parameter);
return b;
}
public void Execute(object parameter)
{
execute(parameter);
}
}
xaml
<DataTemplate>
<Button Command="{Binding Source={StaticResource VM},
Path=Command}" CommandParameter="{Binding}" >
<SymbolIcon Symbol="Edit" Foreground="AliceBlue" />
</Button>
</DataTemplate>
CanExecute in VM
private bool CanGetDetails(object obj)
{
return true;
}
You can always do your conditional statement within the CanExecute function of your custom command, no need for you to bind IsEnabled property with your button that is bound to a command. Here's a sample implementation, hope this helps.
Custom Command:
public class CustomCommand<T> : ICommand
{
private readonly Action<T> _action;
private readonly Predicate<T> _canExecute;
public CustomCommand(Action<T> action, Predicate<T> canExecute)
{
_action = action;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute((T)parameter);
}
public void Execute(object parameter)
{
_action((T)parameter);
}
public event EventHandler CanExecuteChanged;
}
As you can see here, I created an object that implements the ICommand interface, this custom command accepts a generic type parameter which is used to evaluate a condition (CanExecute: this tells whether to enable or disable a command (in UI, the button), normally use to check for permissions, and other certain conditions) this parameter is also used to execute the action (Execute: the actual logic/action to be performed), The command contructor accepts delegate parameters that contain signatures for these 2 methods, the caller may choose lambda or standard methods to fillup these parameters.
Sample ViewModel:
public class ViewModel1: INotifyPropertyChanged
{
public ViewModel1()
{
// Test Data.
Items = new ObservableCollection<ItemViewModel>
{
new ItemViewModel{ Code = "001", Description = "Paint" },
new ItemViewModel{ Code = "002", Description = "Brush" },
new ItemViewModel{ Code = "003", Description = "" }
};
EditCommand = new CustomCommand<ItemViewModel>(Edit, CanEdit);
}
public CustomCommand<ItemViewModel> EditCommand { get; }
private bool CanEdit(ItemViewModel item)
{
return item?.Description != string.Empty;
}
private void Edit(ItemViewModel item)
{
Debug.WriteLine("Selected Item: {0} - {1}", item.Code, item.Description);
}
private ObservableCollection<ItemViewModel> _items { get; set; }
public ObservableCollection<ItemViewModel> Items
{
get => _items;
set
{
_items = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<Page x:Name="root"
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vms="using:App1.ViewModels"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
d:DesignHeight="450" d:DesignWidth="800">
<Page.DataContext>
<vms:ViewModel1 x:Name="Model"/>
</Page.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0 0 0 15">
<TextBlock Text="{Binding Code}" />
<TextBlock Text="{Binding Description}" />
<Button Content="Edit" Command="{Binding DataContext.EditCommand, ElementName=root}" CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Page>
I think you can pick a lot of code from the RelayCommand of MVVMLight. Try to change your event to
public event EventHandler CanExecuteChanged
{
add
{
if (canExecute != null)
{
CommandManager.RequerySuggested += value;
}
}
remove
{
if (canExecute != null)
{
CommandManager.RequerySuggested -= value;
}
}
}
and add also a function
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
Then, whatever you put as your Predicate on the command, at the Predicate's boolean setter do:
SomeCustomCommand.RaiseCanExecuteChanged()
Hope I helped.

Binding User Control from second lib project not work - wpf mvvm

My task: I want to bind textbox and button.
Although I found many topics about it I cannot manage my problem.
I have project: Client with WPF application WITH DEFAULT XAML no BINDING, which takes context from MenuWindow project, which is library. Inside MenuWindow project I have User Control WPF called: MenuProgram.
<UserControl x:Class="MenuWindow.MenuProgram"
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:MenuWindow"
mc:Ignorable="d"
d:DesignHeight="550" d:DesignWidth="780">
<UserControl.DataContext>
<local:MenuViewModel/>
</UserControl.DataContext>
<Grid Background="#FF6F6FA4">
<Label x:Name="lblTitle" Content="GUI Export Revit Data" HorizontalAlignment="Left" Margin="277,31,0,0" VerticalAlignment="Top" Height="50" Width="258" FontSize="24" FontWeight="Bold"/>
<Label x:Name="lblPrtdPath" Content="File prtd path" HorizontalAlignment="Left" Margin="200,176,0,0" VerticalAlignment="Top"/>
<Label x:Name="lblXmlPath1" Content="File xml path1" HorizontalAlignment="Left" Margin="200,222,0,0" VerticalAlignment="Top"/>
<Label x:Name="lblXmlPath2" Content="File xml path2" HorizontalAlignment="Left" Margin="200,266,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="tbxPrtd" HorizontalAlignment="Left" Height="23" Margin="302,176,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="268" Text="{Binding PrtdFilePath}"/>
<TextBox x:Name="tbxXml1" HorizontalAlignment="Left" Height="23" Margin="302,222,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="268" Text="{Binding XmlFilePath1}"/>
<TextBox x:Name="tbxXml2" HorizontalAlignment="Left" Height="23" Margin="302,266,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="268" Text="{Binding XmlFilePath2}"/>
<Button x:Name="SayHi" Content="Start" HorizontalAlignment="Left" Margin="302,450,0,0" VerticalAlignment="Top" Width="174" Height="84" FontSize="22" Command="{Binding SayHi}" />
<Button x:Name="btnAbout" Content="About" HorizontalAlignment="Left" Margin="705,496,0,0" VerticalAlignment="Top" Width="55" Height="38" Command="{Binding SayHi}"/>
</Grid>
so I have
<UserControl.DataContext>
<mv:MenuViewModel/>
</UserControl.DataContext>
and with textBoxs or button I want to use binding.
in codeBehind this User Control there is nothing but default initialization.
In Project Menu there are:
MenuArguments.cs with mapping:
public string PrtdFilePath { get; set; }
public string XmlFilePath1 { get; set; }
public string XmlFilePath2 { get; set; }
RelayCommand:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace MenuWindow
{
public class RelayCommand : ICommand
{
private readonly Func<Boolean> _canExecute;
private readonly Action _execute;
public RelayCommand(Action execute)
: this(execute, null)
{
}
public RelayCommand(Action execute, Func<Boolean> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (_canExecute != null)
CommandManager.RequerySuggested -= value;
}
}
public Boolean CanExecute(Object parameter)
{
return _canExecute == null ? true : _canExecute();
}
public void Execute(Object parameter)
{
_execute();
}
}
}
and MenuViewModel.cs
namespace MenuWindow
{
public class MenuViewModel : INotifyPropertyChanged
{
public string gowno;
public MenuArguments _menuArgumenty;
public string PrtdFilePath
{
get { return _menuArgumenty.PrtdFilePath; }
set
{
_menuArgumenty.PrtdFilePath = value;
OnPropertyChanged("PrtdFilePath");
}
}
public string XmlFilePath1
{
get { return _menuArgumenty.XmlFilePath1; }
set
{
_menuArgumenty.XmlFilePath1 = value;
OnPropertyChanged("XmlFilePath1");
}
}
public string XmlFilePath2
{
get { return _menuArgumenty.XmlFilePath2; }
set
{
_menuArgumenty.XmlFilePath2 = value;
OnPropertyChanged("XmlFilePath2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public MenuViewModel()
{
_menuArgumenty = new MenuArguments();
}
public ICommand SayHi
{
get
{
return new RelayCommand(SayHiExcute, CanSayHiExcute);
}
}
private void SayHiExcute()
{
if (!MenuArgumentsExists(_menuArgumenty))
{
MessageBox.Show(string.Format("Hi {0} {1}!", _menuArgumenty.PrtdFilePath, _menuArgumenty.XmlFilePath1));
SavePerosn(_menuArgumenty);
}
else
{
MessageBox.Show(string.Format("Hey {0} {1}, you exists in our database!", _menuArgumenty.PrtdFilePath, _menuArgumenty.XmlFilePath1));
}
}
private void SavePerosn(MenuArguments _menuArgumenty)
{
//Some Database Logic
}
private bool CanSayHiExcute()
{
return !MenuArgumentsExists(_menuArgumenty);
}
private bool MenuArgumentsExists(MenuArguments _menuArgumenty)
{
//Some logic
return false;
}
}
}
When I start program debuger goes through binding properties. After window appears there is no reaction from binding. What do I do wrong? Please help me.
BR,
student Cenarius
Thanks for comments, answers to your comments:
#tabby - I want to bind textBoxes: PrtdFilePath, XmlFilePath1, XmlFilePath1 and button SayHi
#maulik kansara - You are right, I was trying some another methods and I didnt remove code. It should be only version with local.
#grek40 - My example works for one-project in solution for Window not for UserControl which is set in another project. Here is picture:
#mm8 - I expected by puting data ino textBoxes or clicking button to see breakpoint in:
public string PrtdFilePath
{
get { return _menuArgumenty.PrtdFilePath; }
set
{
_menuArgumenty.PrtdFilePath = value;
OnPropertyChanged("PrtdFilePath");
}
}
Finally, I think that code in XAML is problem. I was reading about parent-child relations with finding binding/viewmodel/path but I am confused and I dont know how to solve it. Please help me thanks You for all comments.
#grek40 here is Code in Main APP WPF, I add context from my MenuWindow.
This MainWindow WPF APP has default XAML.
public MainWindow()
{
InitializeComponent();
menuProgram = new MenuProgram();//User Control
sw = new SharedWindow();//WPF window
this.Close();
sw.Content = menuProgram.Content;// here I set context
sw.ShowDialog();
}
and XAML:
<Window x:Class="Client.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:Client"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
</Grid>
</Window>
Whole code with Your change:
public partial class MainWindow : Window
{
private SharedWindow sw;
private MenuProgram menuProgram;
public MainWindow()
{
InitializeComponent();
menuProgram = new MenuProgram();
SetForContext();
}
private void SetForContext()
{
sw = new SharedWindow();
this.Close();
sw.Content = menuProgram;
sw.ShowDialog();
}
You need to set the UserControl as window Content, not the Content of UserControl:
sw.Content = menuProgram;// here I set context
/* Bad: sw.Content = menuProgram.Content; */
Your DataContext is assigned to the UserControl itself, so if you move the Content tree to a different Parent, it will no longer have its old DataContext.

WPF MVVM: Display View for DataGrid's SelectedItem

I'm new to MVVM and WPF and have been completely hung up on this issue for some time now. I'm trying to display a View (UserControl) based on the SelectedItem in a DataGrid. The UserControl renders with the data set in the constructor, but never updates.
I would really appreciate some insight from someone with experience in this. I tried adding a Mediator via setUpdateCallback and now the first row in the datagrid updates with the values of the other rows I click on, but this obviously isn't what I want, I need those updates to happen in the separate client view outside of the datagrid.
ClientPanel.xaml
<UserControl x:Class="B2BNet.View.ClientPanel"
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:VM="clr-namespace:B2BNet.ViewModel"
xmlns:V="clr-namespace:B2BNet.View"
xmlns:local="clr-namespace:B2BNet"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d">
<Grid>
<Grid.DataContext>
<VM:ClientPanel/>
</Grid.DataContext>
<TextBlock HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="43" Width="280" Text="{Binding Title}" FontSize="36" FontFamily="Global Monospace"/>
<DataGrid AutoGenerateColumns="False" x:Name="dataGrid" HorizontalAlignment="Left" Margin="10,60,0,10" VerticalAlignment="Top" ItemsSource="{Binding Clients}" SelectedItem="{Binding currentClient}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding name}"></DataGridTextColumn>
<DataGridTextColumn Header="Active" Binding="{Binding active}"></DataGridTextColumn>
<DataGridTextColumn Header="Status" Binding="{Binding status}"></DataGridTextColumn>
</DataGrid.Columns>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding clientSelectionChanged_command}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
<V:Client HorizontalAlignment="Left" Margin="163,58,-140,0" VerticalAlignment="Top" Content="{Binding currentClient}" Height="97" Width="201"/>
</Grid>
Client.xaml
<UserControl x:Class="B2BNet.View.Client"
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:VM="clr-namespace:B2BNet.ViewModel"
xmlns:local="clr-namespace:B2BNet"
mc:Ignorable="d">
<Grid>
<Grid.DataContext>
<VM:Client/>
</Grid.DataContext>
<Label x:Name="name" Content="Name:" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<Label x:Name="active" Content="Active:" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="9,41,0,0"/>
<Label x:Name="status" Content="Status:" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10,72,0,0"/>
<Label x:Name="namevalue" HorizontalAlignment="Left" Margin="59,10,0,0" VerticalAlignment="Top" Width="200" Height="26" Content="{Binding name}"/>
<Label x:Name="activevalue" HorizontalAlignment="Left" Margin="59,41,0,0" VerticalAlignment="Top" Width="200" Height="26" Content="{Binding active}"/>
<Label x:Name="statusvalue" HorizontalAlignment="Left" Margin="60,67,-1,0" VerticalAlignment="Top" Width="200" Height="26" Content="{Binding status}"/>
</Grid>
ViewModel - ClientPanel.cs
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using B2BNet.MVVM;
namespace B2BNet.ViewModel
{
public class ClientPanel : ObservableObject
{
public string Title { get; set; }
private Client _currentClient;
public Client currentClient
{
get
{
return _currentClient;
}
set
{
_currentClient = value;
RaisePropertyChangedEvent( "currentClient" );
Utility.print("currentClient Changed: " + _currentClient.name);
Mediator.Instance.Notify(ViewModelMessages.UpdateClientViews, currentClient);
}
}
private ObservableCollection<Client> _Clients;
public ObservableCollection<Client> Clients
{
get
{
return _Clients;
}
set
{
_Clients = value;
RaisePropertyChangedEvent("Clients");
Utility.print("Clients Changed");
}
}
public ClientPanel()
{
Title = "Clients";
Clients = new ObservableCollection<Client>();
for ( int i = 0; i < 10; i++ )
{
Clients.Add( new Client { name = "Client-" + i, status = "Current", active = true } );
}
currentClient = Clients.First();
currentClient.setUpdateCallback();
}
////////////
//COMMANDS//
////////////
public ICommand clientSelectionChanged_command
{
get { return new DelegateCommand( clientSelectionChanged ); }
}
private void clientSelectionChanged( object parameter )
{
B2BNet.Utility.print("clientSelectionChanged");
}
}
}
ViewModel - Client.cs
using System;
using B2BNet.MVVM;
namespace B2BNet.ViewModel
{
public class Client : ObservableObject
{
private String _name;
public String name
{
get
{
return _name;
}
set
{
_name = value;
RaisePropertyChangedEvent( "name" );
Utility.print("Client.name changed: " + value );
}
}
private Boolean _active;
public Boolean active
{
get
{
return _active;
}
set
{
_active = value;
RaisePropertyChangedEvent( "active" );
Utility.print("Client.active changed" + value );
}
}
private String _status;
public String status
{
get
{
return _status;
}
set
{
_status = value;
RaisePropertyChangedEvent( "status" );
Utility.print("Client.status changed" + value );
}
}
public Client()
{
name = "Set in Client Constuctor";
status = "Set in Client Constructor";
}
public void setUpdateCallback()
{
////////////
//Mediator//
////////////
//Register a callback with the Mediator for Client
Mediator.Instance.Register(
(Object o) =>
{
Client client = (Client)o;
name = client.name;
active = client.active;
status = client.status;
}, B2BNet.MVVM.ViewModelMessages.UpdateClientViews
);
}
}
}
Very Basic MVVM Framework
ObservableObject.cs
using System.ComponentModel;
namespace B2BNet.MVVM
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent( string propertyName )
{
var handler = PropertyChanged;
if ( handler != null )
handler( this, new PropertyChangedEventArgs( propertyName ) );
}
}
}
Mediator.cs
using System;
using B2BNet.lib;
namespace B2BNet.MVVM
{
/// <summary>
/// Available cross ViewModel messages
/// </summary>
public enum ViewModelMessages { UpdateClientViews = 1,
DebugUpdated = 2
};
public sealed class Mediator
{
#region Data
static readonly Mediator instance = new Mediator();
private volatile object locker = new object();
MultiDictionary<ViewModelMessages, Action<Object>> internalList
= new MultiDictionary<ViewModelMessages, Action<Object>>();
#endregion
#region Ctor
//CTORs
static Mediator()
{
}
private Mediator()
{
}
#endregion
#region Public Properties
/// <summary>
/// The singleton instance
/// </summary>
public static Mediator Instance
{
get
{
return instance;
}
}
#endregion
#region Public Methods
/// <summary>
/// Registers a callback to a specific message
/// </summary>
/// <param name="callback">The callback to use
/// when the message it seen</param>
/// <param name="message">The message to
/// register to</param>
public void Register(Action<Object> callback,
ViewModelMessages message)
{
internalList.AddValue(message, callback);
}
/// <summary>
/// Notify all callbacks that are registed to the specific message
/// </summary>
/// <param name="message">The message for the notify by</param>
/// <param name="args">The arguments for the message</param>
public void Notify(ViewModelMessages message,
object args)
{
if (internalList.ContainsKey(message))
{
//forward the message to all listeners
foreach (Action<object> callback in
internalList[message])
callback(args);
}
}
#endregion
}
}
DelegateCommand.cs
using System;
using System.Windows.Input;
namespace B2BNet.MVVM
{
public class DelegateCommand : ICommand
{
private readonly Action<object> _action;
public DelegateCommand( Action<object> action )
{
_action = action;
}
public void Execute( object parameter )
{
_action( parameter );
}
public bool CanExecute( object parameter )
{
return true;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged;
#pragma warning restore 67
}
}
THE SOLUTION
The solution was a simple one. Thanks for the quick response Rachel :). I simple removed the following from each of the Views:
<!--<Grid.DataContext>
<VM:ClientPanel/>
</Grid.DataContext>-->
<!--<Grid.DataContext>
<VM:Client/>
</Grid.DataContext>-->
and it works perfectly, even allowing me to update the datagrid:
The problem is you are hardcoding the DataContext in your UserControls.
So your Client UserControl has it's DataContext hardcoded to a new instance of Client, and it is NOT the same instance that your ClientPanel UserControl is using. So the <V:Client Content="{Binding currentClient}" binding is pointing to a different instance of the property than the one the DataGrid is using.
So get rid of
<Grid.DataContext>
<VM:Client/>
</Grid.DataContext>
and change your Content binding to a DataContext one
<V:Client DataContext="{Binding currentClient}" .../>
and it should work fine.
I thought for sure I had some rant on SO about why you should NEVER hardcode the DataContext property of a UserControl, but the closest thing I can find right now is this. Hopefully it can help you out :)
Oh boy that's a lot of code and I'm not sure I understand everything you're trying to do. Here are a few important notes when binding with WPF.
Make sure the properties have public { get; set; }
If the value is going to be changing, make sure you add Mode=TwoWay, UpdateSourceTrigger=PropertyChanged to your bindings. This keeps your view and view model in sync and tells each other to update.
Use ObservableCollections and BindingLists for collections. Lists don't seem to work very well.
Make sure the DataContext matches the properties you are trying to bind to. If your DataContext doesn't have those properties, they aren't going to update.

Disable Buttons in a ListView's DataTemplate

So let's say I have a simple little WPF app that looks like this:
-------------------------------
Amount Left: 1,000
[Subtract 1]
[Subtract 5]
[Subtract 15]
[Subtract 30]
-------------------------------
"Amount Left:" and "1,000" are stand alone TextBlocks.
The "Subtract x" are all buttons, inside a ListView, inside a DataTemplate. Each time a button is clicked, the amount of the button is subtracted from the 1,000. All of that I have working.
Here's what I can't figure out. When the Amount Left falls below 30, the last button needs to become disabled. When the amount falls below 15, the second to last button becomes disabled. Etc and so on, until the Amount Left is Zero and all buttons are disabled. I can not figure out how to disable the buttons.
This example I'm giving here is not exactly what I'm trying to do, but it's a greatly simplified example that will make this post a lot shorter and simpler. Here, in essence, is what I have now.
XAML:
<DockPanel>
<TextBlock Text="Amount Left:" />
<TextBlock x:Name="AmountLeft" Text="1,000.00" />
</DockPanel>
<DockPanel>
<ListBox x:Name="AuthorListBox">
<ListView.ItemTemplate>
<DataTemplate>
<Button x:Name="SubButtom" Content="{Binding SubtractAmount}" Click="clickSubtract" />
<DataTemplate>
</ListBox>
</DockPanel>
XAML.cs
private void clickSubtract(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
Int32 SubtractAmount = ((Data.AppInformation)button.DataContext).SubtractAmount; // This is the amount to be subtracted
// logic to update the amount remaining. This works.
// What I need to figure out is how to disable the buttons
}
You can accomplish using MVVM, by having an IsEnabled property for your Button ViewModels. With this approach, you will not need any 'code behind' as you currently have using a click event handler.
Xaml:
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Amount Left:" />
<TextBlock Text="{Binding CurrentAmount}" />
</StackPanel>
<ListBox ItemsSource="{Binding Buttons}">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Command="{Binding SubtractCommand}" Width="200" Height="75" x:Name="SubButtom" Content="{Binding SubtractAmount}" IsEnabled="{Binding IsEnabled}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
Xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
We want the main ViewModel that will have a list of Button ViewModels.
ButtonViewModel.cs:
namespace WpfApplication1
{
public class ButtonViewModel : INotifyPropertyChanged
{
private bool _isEnabled;
private ViewModel _viewModel;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
OnPropertyChanged();
}
}
public int SubtractAmount { get; set; }
public ICommand SubtractCommand { get; private set; }
public ButtonViewModel(ViewModel viewModel)
{
_viewModel = viewModel;
IsEnabled = true;
SubtractCommand = new CommandHandler(() =>
{
_viewModel.CurrentAmount -= SubtractAmount;
}, true);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class CommandHandler : ICommand
{
private readonly Action _action;
private readonly bool _canExecute;
public CommandHandler(Action action, bool canExecute)
{
_action = action;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action();
}
}
}
and now the main ViewModel.
ViewModel.cs:
namespace WpfApplication1
{
public class ViewModel : INotifyPropertyChanged
{
private int _currentAmount;
public int CurrentAmount
{
get { return _currentAmount; }
set
{
_currentAmount = value;
OnPropertyChanged();
if (Buttons != null)
{
foreach (var button in Buttons)
{
if ((value - button.SubtractAmount) <= 0)
{
button.IsEnabled = false;
}
}
}
}
}
public List<ButtonViewModel> Buttons { get; private set; }
public ViewModel()
{
CurrentAmount = 1000;
Buttons = new List<ButtonViewModel>
{
new ButtonViewModel(this)
{
SubtractAmount = 1
},
new ButtonViewModel(this)
{
SubtractAmount = 5
},
new ButtonViewModel(this)
{
SubtractAmount = 15
},
new ButtonViewModel(this)
{
SubtractAmount = 30
}
};
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
As you can see, each Button ViewModel will decrement the CurrentAmount using a Command (the preferred method over a click event). Whenever the CurrentAmount is changed, some simple logic is done by the main ViewModel that will disable associated buttons.
This is tested and works. Let me know if you have any questions.
I would go ahead creating a Converter and bind the IsEnabled property of the button. pass the value and do the logic.
Namespace
System.Windows.Data
System.Globalization
CODE
public class IsEnabledConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
// Do the logic
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
// Do the Logic
}
}
XAML
Add the resurce like this
<Window.Resources>
<local:IsEnabledConverter x:Key="converter" />
</Window.Resources>
<Button x:Name="SubButtom" IsEnabled="{Binding Value, Converter= {StaticResource converter}}" Content="{Binding SubtractAmount}" Click="clickSubtract" />
You can learn about converters from below link
http://wpftutorial.net/ValueConverters.html
When you build the class with Converter all Xaml Errors will go off.
Your best option would be to use a command on the viewmodel instead of a click event handler:
public ICommand SubtractCommand = new DelegateCommand<int>(Subtract, i => i <= AmountLeft);
private void Subtract(int amount)
{
AmountLeft = AmountLeft - amount;
}
XAML:
<ListBox x:Name="AuthorListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<Button x:Name="SubButtom" Content="{Binding SubtractAmount}"
Command="{Binding SubtractCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding SubtractAmount}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Categories