I have a listview and a button in each column. When the user clicks a button, it triggers an asynchronous action in the viewmodel where I disable all buttons and do a big action. Once the action is completed, I re-enable them.
If the action takes too long, though, the buttons don't automatically get re-enabled, even though I'm setting the bound property to true and am notifying the view. If the user does ANY GUI action after the action is complete, the buttons will re-enable.
The other weird thing: If I do an await Task.Delay instead of doing Thread.Sleep (NB: I'm doing real work in the full application), it works correctly.
What's going on here?
I've simplified the code here by eliminating the model (all logic lives in the VM).
View-model code:
namespace WpfTestApp
{
public class viewmodel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public RelayAsyncCommand<object> RunCommand { get; private set; }
private ObservableCollection<subVM> _subVMs;
public ObservableCollection<subVM> SubVMs
{
get => _subVMs; set
{
_subVMs = value;
NotifyPropertyChanged();
}
}
public viewmodel()
{
RunCommand = new RelayAsyncCommand<object>(OnRun);
SubVMs = new ObservableCollection<subVM>
{
new subVM("ItemA"),
new subVM("ItemB"),
};
}
private async void OnRun(object o)
{
subVM vm = o as subVM;
if (vm != null)
{
ChangeRunMode(false);
Thread.Sleep(500);
}
ChangeRunMode(true);
}
private void ChangeRunMode(bool on)
{
foreach (subVM vm in SubVMs)
{
vm.ButtonEnabled = on;
}
}
}
public class subVM : INotifyPropertyChanged
{
private string name = "";
public string Name
{
get => name;
set
{
if (value != name)
{
name = value;
}
}
}
public subVM(string name)
{
Name = name;
}
private bool tsk = true;
public bool ButtonEnabled
{
get => tsk;
set
{
if (tsk != value)
{
tsk = value;
NotifyPropertyChanged("ButtonEnabled");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View XAML:
<Window x:Class="WpfTestApp.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:WpfTestApp"
mc:Ignorable="d"
Title="MainWindow" Height="130" Width="350">
<Window.DataContext>
<local:viewmodel/>
</Window.DataContext>
<ListView Margin="5"
BorderBrush="DarkSlateGray" BorderThickness="1"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding SubVMs}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name"
Width="200" DisplayMemberBinding ="{Binding Name}"/>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button
Content="Load"
IsEnabled="{Binding ButtonEnabled, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0"
VerticalAlignment="Center"
Command="{Binding Path=DataContext.RunCommand, IsAsync=True, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding}"
/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Window>
Things I've tried:
I tried await Task.Run on my actual problem, and it still happens.
RelayCommand and the Async version (I thought these were standard boilerplate, but here you go):
public class RelayAsyncCommand<T> : RelayCommand<T>
{
private bool isExecuting = false;
public event EventHandler Started;
public event EventHandler Ended;
public bool IsExecuting
{
get { return this.isExecuting; }
}
public RelayAsyncCommand(Action<T> execute, Predicate<T> canExecute)
: base(execute, canExecute)
{
}
public RelayAsyncCommand(Action<T> execute)
: base(execute)
{
}
public override Boolean CanExecute(Object parameter)
{
return ((base.CanExecute(parameter)) && (!this.isExecuting));
}
public override void Execute(object parameter)
{
try
{
this.isExecuting = true;
if (this.Started != null)
{
this.Started(this, EventArgs.Empty);
}
Task task = Task.Factory.StartNew(() =>
{
this._execute((T)parameter);
});
task.ContinueWith(t =>
{
this.OnRunWorkerCompleted(EventArgs.Empty);
}, TaskScheduler.FromCurrentSynchronizationContext());
}
catch (Exception ex)
{
this.OnRunWorkerCompleted(new RunWorkerCompletedEventArgs(null, ex, true));
}
}
private void OnRunWorkerCompleted(EventArgs e)
{
this.isExecuting = false;
if (this.Ended != null)
{
this.Ended(this, e);
}
}
}
public class RelayCommand<T> : ICommand
{
#region Fields
readonly protected Action<T> _execute;
readonly protected Predicate<T> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public virtual bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public virtual void Execute(object parameter)
{
_execute((T)parameter);
}
#endregion // ICommand Members
}
inside the OnRun method you are actually locking the UI thread, preventing any UI update/refresh; you should await for the long operation (and that's exactly what you noticed doing await Task.Delay):
private async void OnRun(object o)
{
subVM vm = o as subVM;
if (vm != null)
{
ChangeRunMode(false);
await Task.Run(() =>
{
//put here your long operation as per your example
for (int i = 0; i < 500; i++)
{
for (int k = 0; k < 100000; k++) ;
}
});
}
ChangeRunMode(true);
}
indeed if you take a closer look to your window, you'll see that currently everything is freezed during the long operation execution, not just only the buttons being disabled.
I figured out my problem:
The button deactivating isn't coming from my binding when I'm running asynchronously. It's coming from the RelayAsyncCommand's CanExecute. CanExecute here returns false while the task is running, but we don't trigger a requery ever when it's done.
Easily fixed by adding a private set to the IsExecuting property, which calls the invalidate/requery function on change (just like the standard notifypropertychanged pattern). For posterity, here's the full fixed RelayAsyncCommand:
public class RelayAsyncCommand<T> : RelayCommand<T>
{
private bool _isExecuting = false;
public event EventHandler Started;
public event EventHandler Ended;
public bool IsExecuting
{
get { return _isExecuting; }
private set
{
if (value != _isExecuting)
{
_isExecuting = value;
CommandManager.InvalidateRequerySuggested();
}
}
}
public RelayAsyncCommand(Action<T> execute, Predicate<T> canExecute)
: base(execute, canExecute)
{
}
public RelayAsyncCommand(Action<T> execute)
: base(execute)
{
}
public override bool CanExecute(object parameter)
{
return ((base.CanExecute(parameter)) && (!IsExecuting));
}
public override void Execute(object parameter)
{
try
{
IsExecuting = true;
Started?.Invoke(this, EventArgs.Empty);
Task task = Task.Factory.StartNew(() =>
{
_execute((T)parameter);
});
task.ContinueWith(t =>
{
OnRunWorkerCompleted(EventArgs.Empty);
}, TaskScheduler.FromCurrentSynchronizationContext());
}
catch (Exception ex)
{
OnRunWorkerCompleted(new RunWorkerCompletedEventArgs(null, ex, true));
}
}
private void OnRunWorkerCompleted(EventArgs e)
{
IsExecuting = false;
Ended?.Invoke(this, e);
}
}
Thanks Peter for making me actually look at (what I thought was) boilerplate code.
Related
What I'm trying to do is: when the textbox contains the value "123" it should enable the button and allow me to click it.
Solution Image &
View Image
I can not find a way to trigger the Button Command (Class called SpecialCommand.cs) based on my Button parameters. Could you support where I'm getting this MVVM pattern wrong?
WPF View [MainWindow.xaml]:
<Window.Resources>
<ViewModel:MainWindowVM x:Key="WindowVm"></ViewModel:MainWindowVM>
</Window.Resources>
<Grid>
<StackPanel>
<TextBox x:Name="textBox" Margin="0, 5" Text="123"/>
<Button Content="Click me!" Margin="0, 5" Command="{Binding SpecialCommand, Source={StaticResource WindowVm}}" CommandParameter="{Binding Text, ElementName=textBox, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</StackPanel>
</Grid>
ViewModel [MainWindowVM.cs]:
public class MainWindowVM
{
private SpecialCommand _specialCommand;
public SpecialCommand SpecialCommand { get => _specialCommand; set => _specialCommand = value; }
public MainWindowVM()
{
_specialCommand = new SpecialCommand();
}
}
Command [SpecialCommand.cs]
public class SpecialCommand : ICommand
{
public bool CanExecute(object parameter)
{
if (parameter != null && (parameter as string) == "123")
return true;
return false;
}
public void Execute(object parameter)
{
MessageBox.Show("Button Pressed!");
}
public event EventHandler CanExecuteChanged;
}
What I believe, maybe this is what I'm getting wrong is since Button & Textbox are in the View I don't need add/modify any method in my SpecialCommand implementation. They should be able to see when a property is changed.
Like the CanExecuteChanged() below, this command raises a lot of times and seems overkill for this small task.
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
There is no need to make it more complicated than it needs to be.
public class MainWindowVM
{
private string m_OneTwoThree;
public string OneTwoThree{
get { return OneTwoThree; }
set {
if (m_OneTwoThree != value){
m_OneTwoThree = value;
NotifyPropertyChanged(nameof(OneTwoThree)); //if you need this let me know
}
}
}
public MainWindowVM()
{
}
public ICommand RandomCommand { get { return new RelayCommand(OnRandom, IsOneTwoThree); } }
private void OnRandom()
{
//do stuff
}
private bool IsOneTwoThree(){
if (OneTwoThree == "123"){
return true;
}
return false;
}
}
You'll also have to update your xaml I don't think it'll be able to find 'OneTwoThree' so you'll have to bind it yourself, however you usually would.
<StackPanel>
<TextBox x:Name="textBox" Margin="0, 5" Text="{Binding OneTwoThree}"/>
<Button Content="Click me!" Margin="0, 5" Command="{Binding RandomCommand, Source={StaticResource WindowVm}}"/>
</StackPanel>
If you have any questions just ask.
This is my RelayCommand : Use RelayCommand("thing to execute", "if function returns true you can execute");
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();
}
}
You need a way to tell the command that it should check whether it CanExecute. This is done by triggering the event CanExecuteChanged. It tells the button to requery the CanExecute property.
For this to work, I would add a text property to your view model and bind the textbox to it.
In SpecialCommand add the method:
public void TriggerCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
In the class MainWindowVM add the property:
private string _text;
public string Text
{
get { return _text; }
set {
if (value != _text) {
_text = value;
_specialCommand.TriggerCanExecuteChanged();
}
}
}
View Model to implementing INotifyPropertyChanged (see comments):
public class MainWindowVM : INotifyPropertyChanged
{
public SpecialCommand SpecialCommand { get; set; } = new SpecialCommand();
private string _text;
public string Text
{
get { return _text; }
set {
if (value != _text) {
_text = value;
OnPropertyChanged(nameof(Text));
SpecialCommand.TriggerCanExecuteChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Help me please!!!
I have 3 UserControls
I select user on List Users UC listbox
Then send message from SendMessage UC to Database
when i send message to Db it must refresh my chat listBox in Correspondence UC, but problem is in my ChatWrapper.
PropertyChanged in ChatWrapper is always null, and I can't refresh my ListBox in Correspondence UC with new message
List Users:
public IEnumerable<EmployeesDb> getListNames
{
get { return Db.Instance.EmployeesDbs.ToList(); }
}
static EmployeesDb m_selectedUser;
public static EmployeesDb selectedUser
{
get { return m_selectedUser; }
set
{
if (value != null)
m_selectedUser = value;
Correspondence correspondence = new Correspondence();
correspondence.CorrespondenceChat();
}
}
}
Send Message ( I try to refresh -> SendInfo.FirstOrDefault().RefreshGUI();)
public static DependencyProperty SendInfoProperty =
DependencyProperty.Register(
"SendInfo",
typeof(IEnumerable<ChatWrapper>),
typeof(SendMessage));
public IEnumerable<ChatWrapper> SendInfo
{
get { return GetValue(SendInfoProperty) as IEnumerable<ChatWrapper>; }
set { SetValue(SendInfoProperty, value); }
}
void SendMessageCommandExecute()
{
//...
SendInfo.FirstOrDefault().RefreshGUI();
//...
}
ChatWrapper
public class ChatWrapper : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void FirePropertyChanged(string name)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(name));
}
public void RefreshGUI()
{
FirePropertyChanged("message");
}
public ChatDb chatDb { get; set; }
public string message
{
get
{
return (chatDb != null) ? string.Format("{0} {1}.{2} / {3} / {4}\n{5}",
chatDb.FromEmployeesDb.surname,
chatDb.FromEmployeesDb.name[0],
chatDb.FromEmployeesDb.middleName[0],
chatDb.messageDateTime,
chatDb.computerName,
chatDb.message) : null;
}
}
Correspondence
//...
public partial class Correspondence : UserControl, INotifyPropertyChanged
{
public static DependencyProperty GetCorrespondenceInfoProperty =
DependencyProperty.Register(
"GetCorrespondenceInfo",
typeof(IEnumerable<ChatWrapper>),
typeof(Correspondence),
new PropertyMetadata(OnChanged));
public IEnumerable<ChatWrapper> GetCorrespondenceInfo
{
get { return GetValue(GetCorrespondenceInfoProperty) as IEnumerable<ChatWrapper>; }
set { SetValue(GetCorrespondenceInfoProperty, value); }
}
static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var me = d as Correspondence;
me.chat = me.GetCorrespondenceInfo;
}
ICollectionView m_CollectionView;
public static IEnumerable<ChatWrapper> m_chat;
public IEnumerable<ChatWrapper> chat
{
get { return m_chat; }
set
{
m_chat = value;
if (ListUsers.selectedUser != null)
CorrespondenceChat();
FirePropertyChanged("chat");
}
}
public void CorrespondenceChat()
{
if (m_chat == null)
return;
m_CollectionView = CollectionViewSource.GetDefaultView(m_chat);
//...
FirePropertyChanged("chat");
}
XAML of Correspondence (refresh
<Grid>
<ListBox x:Name="correspondenceListBox" ItemsSource="{Binding chat, RelativeSource={RelativeSource AncestorType={x:Type local:Correspondence}}}"
Height="auto" Grid.Row="0" Grid.Column="1" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding message}" TextWrapping="Wrap" Width="auto"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I tried to write
public event PropertyChangedEventHandler PropertyChanged = delegate { };
PropertyChanged is no longer null, but it's still not updated
So I have the following XAML:
<TextBlock Text="{Binding DisconnectedDevices, UpdateSourceTrigger=PropertyChanged}" />
The view model has the following properties:
public string DisconnectedDevices {get; set;}
public IEnumerable<IDeviceInformationVM> DeviceCollection {get; set;}
There's a method that gets called which raises the property notified event:
public void DeviceCollectionChanged()
{
RaisePropertyChanged(() => DeviceCollection);
}
I'd like to update the value in the TextBlock when DeviceCollection changes. I'm aware that I could just call RaisePropertyChanged on DisconnectedDevices but I'm wondering if its possible to update a TextBlock on a different property change event.
Thanks all!
EDIT: Thanks for the suggestions for using an ObservableCollection instead of IEnumerable, unfortunately, I'm not at liberty to change the collection type..
The DeviceCollectionChanged method is called whenever the collection changes (tedious I know...)
FURTHER EDIT:
Have just gone ahead with
RaisePropertyChanged(() => DisconnectedDevices);
I appreciate not enough information might have been provided in the question to get what I was trying to do, apologies for that
I am not sure if your current code works, but assuming it works.
Why not use - ObservableCollection<IDeviceInformationVM> instead of IEnumerable<IDeviceInformationVM> DeviceCollection you wont need the DeviceCollectionChanged event. It will be taken care.
Yes you can raise
public void DeviceCollectionChanged()
{
RaisePropertyChanged(() => DeviceCollection);
RaisePropertyChanged(() => DisconnectedDevices);
// or RaisePropertyChanged("DisconnectedDevices"); Whichever works
}
See this question, it might help you with implementation of NotifyPropertyChanged for multiple properties- WPF Notify PropertyChanged for a Get Property
Do you call the DeviceCollectionChanged() method everytime you change your DeviceCollection? How do you set DeviceCollection?
You can implement an ObservableCollection (bottom of this answer), or, depending on how you set your DeviceCollection, if for example DeviceCollection comes from a list, you can implement something like this:
private IEnumerable<IDeviceInformationVM> deviceCollection;
public IEnumerable<IDeviceInformationVM> DeviceCollection
{
get
{
return deviceCollection;
}
set
{
deviceCollection = value;
RaisePropertyChanged(() => DisconnectedDevices);
RaisePropertyChanged(() => DeviceCollection);
}
}
DeviceCollection = GetListOfIDeviceInformationVM(); //will automatically raise property changed and update your TextBlock
You won't have to keep on calling RaisePropertyChanged() which looks rather tedious
Change the type of the CollectionDevice collection to ObservableCollection then, raise the event CollectionChanged as follows :
DeviceCollection.CollectionChanged + = DeviceCollection_CollectionChanged;
I give you an implemention in MVVM with a class RelayCommand
here the view : (MainView)
<Window x:Class="WpfApplication.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:WpfApplication"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding DisconnectedDevices, Mode=TwoWay}" Height="25" Width="175" Grid.Row="0" />
<Button Grid.Row="1" Content="Click" Command="{Binding ToggleExecuteCommand}" Width="100" Height="25"/>
</Grid>
the ViewModel (Main ViewModel)
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace WpfApplication
{
public class MainViewModel : INotifyPropertyChanged
{
private string disconnectedDevices;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public MainViewModel()
{
ToggleExecuteCommand = new RelayCommand(ChangeCollection);
DeviceCollection = new ObservableCollection<DeviceInformationVM>();
DeviceCollection.CollectionChanged += DeviceCollection_CollectionChanged;
}
private void ChangeCollection(object obj)
{
DeviceCollection.Add(new DeviceInformationVM { MyProperty = "TEST" });
}
private void DeviceCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedAction action = e.Action;
if (action == NotifyCollectionChangedAction.Add)
{
DisconnectedDevices = "Somme thing added to collection";
}
if (action == NotifyCollectionChangedAction.Remove)
{
DisconnectedDevices = "Somme thing removed from collection";
}
}
public string DisconnectedDevices
{
get { return this.disconnectedDevices; }
set
{
if (value != this.disconnectedDevices)
{
this.disconnectedDevices = value;
NotifyPropertyChanged("DisconnectedDevices");
}
}
}
public ObservableCollection<DeviceInformationVM> DeviceCollection { get; set; }
public RelayCommand ToggleExecuteCommand { get; set; }
}
}
the RelayCommand :
using System;
using System.Windows.Input;
namespace WpfApplication
{
public class RelayCommand : ICommand
{
private Action execute;
private Predicate<object> canExecute;
private event EventHandler CanExecuteChangedInternal;
public RelayCommand(Action<object> execute)
: this(execute, DefaultCanExecute)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
if (canExecute == null)
{
throw new ArgumentNullException("canExecute");
}
this.execute = execute;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
this.CanExecuteChangedInternal += value;
}
remove
{
CommandManager.RequerySuggested -= value;
this.CanExecuteChangedInternal -= value;
}
}
public bool CanExecute(object parameter)
{
return this.canExecute != null && this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
public void OnCanExecuteChanged()
{
EventHandler handler = this.CanExecuteChangedInternal;
if (handler != null)
{
handler.Invoke(this, EventArgs.Empty);
}
}
public void Destroy()
{
this.canExecute = _ => false;
this.execute = _ => { return; };
}
private static bool DefaultCanExecute(object parameter)
{
return true;
}
}
}
and finaly DeviceInformation
using System;
namespace WpfApplication
{
public interface IDeviceInformationVM
{
string MyProperty { get; set; }
}
public class DeviceInformationVM : IDeviceInformationVM
{
public string MyProperty
{
get; set;
}
}
}
Hope it helps
I have a MVVM - WPF Browser Application application using Entity Framework 6.
I have tried to select a supplierDataGrid row and display the Products related in another datagrid. But it doesn't work. The application freezes when I select a row. Thanks for your help!
ViewModelBase
public class CommandBase<T> : INotifyPropertyChanged
{
#region "INotifyPropertyChanged members"
public event PropertyChangedEventHandler PropertyChanged;
//This routine is called each time a property value has been set.
//This will //cause an event to notify WPF via data-binding that a change has occurred.
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private ObservableCollection<T> collection;
public ObservableCollection<T> Collection
{
get
{
if (collection == null)
{
Get();
}
return collection;
}
set { collection = value;
OnPropertyChanged("Collection"); }
}
private T _selected;
public T Selected
{
get {
if(_selected != null)
GetSub();
return _selected;
}
set { _selected = value;
OnPropertyChanged("Selected"); }
}
private ICommand getCommand;
private ICommand saveCommand;
private ICommand removeCommand;
private ICommand getSubCommand;
protected virtual void Save()
{
//return true;
}
protected virtual bool CanSave()
{
return true;
}
public ICommand DeleteCommand
{
get
{
// return removeCommand ?? (removeCommand = new RelayCommand(Delete,CanDelete));
return removeCommand ?? (removeCommand = new RelayCommand(p => this.Delete(), p => this.CanDelete()));
}
}
protected virtual void Delete()
{ }
protected virtual bool CanDelete()
{
if (Selected != null)
return true;
else
return false;
}
public ICommand GetSubCommand
{
get
{
return getSubCommand ?? (getSubCommand = new RelayCommand(p => this.GetSub(), p => this.CanGetSub()));
}
}
protected virtual async Task GetSub()
{
await Task.Delay(0);
}
protected virtual bool CanGetSub()
{
return true;
}
}
ViewModel
public class SupplierViewModel : CommandBase<foodSupplier>
{
public Context ctx = new Context();
protected override void Get()
{
ctx.foodSuppliers.ToList().ForEach(supplier => ctx.foodSuppliers.Local.Add(supplier));
Collection = ctx.foodSuppliers.Local;
}
protected override bool CanGet()
{
return true;
}
protected override void Save()
{
foreach (foodSupplier item in Collection)
{
if (ctx.Entry(item).State == System.Data.Entity.EntityState.Added)
{
ctx.foodSuppliers.Add(item);
}
}
ctx.SaveChanges();
}
protected override void Delete()
{
var id = Selected;
var supp = (from s in ctx.foodSuppliers
where s.idfoodSupplier == id.idfoodSupplier
select s).SingleOrDefault();
ctx.foodSuppliers.Remove(supp);
ctx.SaveChanges();
Collection.Remove(supp);
}
protected virtual bool CanDelete()
{
return true;
}
protected override async Task GetSub()
{
var supplier = Selected;
var pro = await (from p in ctx.products
where p.supplier == supplier.idfoodSupplier
select p).ToListAsync();
Products = pro;
}
private IList<product> _products;
public IList<product> Products
{
get
{
return _products;
}
set
{
_products = value;
OnPropertyChanged("Products");
}
}
}
View
<DataGrid x:Name="dataGrid"
Margin="5"
ItemsSource="{Binding Collection}"
AutoGenerateColumns="False"
SelectedItem="{Binding Selected, Mode=TwoWay}"
SelectionMode="Extended"
SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn x:Name="dataGridTextColumn"
Header="Supplier"
Binding="{Binding idfoodSupplier, UpdateSourceTrigger=PropertyChanged}"
Visibility="Hidden" />
<DataGridTextColumn Header="Supplier"
Binding="{Binding supplier, UpdateSourceTrigger=PropertyChanged}" />
</DataGrid.Columns>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding GetCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
<Button Height="Auto"
Width="Auto"
Content="Delete"
Command="{Binding DeleteCommand}" />
EDIT: the program freezes, because you have infinite recursion. Selected getter call GetSub, it calls getter of Selected it call GetSub, etc...
I recommend you to call GetSub in Selected setter, but some additional refactoring may be needed.
TIP: when somethig freezes, pause the program in visual studio and take a look at callstack window. You would immediatelly know whats going on in this case. Maybe you will need to switch to Main thread in Threads window (Ctrl+Alt+H) after you press pause button.
I'm trying to send a variable from the ViewModel as a parameter to a command. The command looks like this:
public class EditPersonCommand : ICommand
{
private bool _CanExecute = false;
public bool CanExecute(object parameter)
{
PersonModel p = parameter as PersonModel;
CanExecuteProperty = (p != null) && (p.Age > 0);
return CanExecuteProperty;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter) { }
private bool CanExecuteProperty
{
get { return _CanExecute; }
set
{
if (_CanExecute != value)
{
_CanExecute = value;
EventHandler can_execute = CanExecuteChanged;
if (can_execute != null)
{
can_execute.Invoke(this, EventArgs.Empty);
}
}
}
}
}
The ViewModel looks like this:
public class PersonViewModel : ViewModelBase
{
private PersonModel _PersonModel;
private EditPersonCommand _EditPersonCommand;
///<remarks>
/// must use the parameterless constructor to satisfy <Window.Resources>
///</remarks>
public PersonViewModel()
: this(new PersonModel())
{
}
public PersonViewModel(PersonModel personModel)
{
_PersonModel = personModel;
}
public ICommand EditPersonCommand
{
get
{
if (_EditPersonCommand == null)
{
_EditPersonCommand = new EditPersonCommand();
}
return _EditPersonCommand;
}
}
}
The xaml looks like this:
<Button Content="Edit" HorizontalAlignment="Right" Height="20" Width="80"
Command="{Binding EditPersonCommand}"
CommandParameter="{Binding _PersonModel}" />
I've tried creating a property in the ViewModel instead of using the private local variable name, but that didnt work either. The object parameter always shows null in the call to CanExecute and the button is never enabled. If I change the CommandParameter value to Hello, then I receive Hello in the call to CanExecute, so I'm not sure why the variable doesnt work. Any help would be appreciated.
Update: I've also tried making a public property to the model (which I dont really want to expose the model, but just tried it to see if it works, but it doesnt).
// Added this to the ViewModel
public PersonModel PersonModelProp
{
get
{
return _PersonModel;
}
set
{
_PersonModel = value;
OnPropertyChanged("PersonModelProp");
}
}
And changed the xaml to this:
<Button Content="Edit" HorizontalAlignment="Right" Height="20" Width="80"
Command="{Binding EditPersonCommand}"
CommandParameter="{Binding PersonModelProp}" />
But still no luck. The ViewModel does implement INotifyPropertyChanged
Is the CommandParameter always null or are you only checking the first time it is being executed?
It appears that the order in which you declare your properties matters in this case since setting the Command property causes the CanExecute to fire immediately before the CommandParameter has been set.
Try moving the CommandParameter property before the Command property:
<Button Content="Edit" HorizontalAlignment="Right" Height="20" Width="80"
CommandParameter="{Binding PersonModelProp}"
Command="{Binding EditPersonCommand}" />
Also, see here and here.
Edit
To ensure that your events are being raised properly you should raise the CanExecuteChanged event when the PersonModelProp value changes.
The Command:
public class EditPersonCommand : ICommand
{
public bool CanExecute(object parameter)
{
PersonModel p = parameter as PersonModel;
return p != null && p.Age > 0;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
//command implementation
}
public void RaiseCanExecuteChanged()
{
var handler = CanExecuteChanged;
if(handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
And the view model:
public class PersonViewModel : ViewModelBase
{
private PersonModel _PersonModel;
private EditPersonCommand _EditPersonCommand;
///<remarks>
/// must use the parameterless constructor to satisfy <Window.Resources>
///</remarks>
public PersonViewModel()
: this(new PersonModel())
{
_EditPersonCommand = new EditPersonCommand();
}
public PersonViewModel(PersonModel personModel)
{
_PersonModel = personModel;
}
public ICommand EditPersonCommand
{
get
{
return _EditPersonCommand;
}
}
public PersonModel PersonModelProp
{
get
{
return _PersonModel;
}
set
{
_PersonModel = value;
OnPropertyChanged("PersonModelProp");
EditPersonCommand.RaiseCanExecuteChanged();
}
}
}
Two points to the answer:
First, as #akton mentioned, you can only bind to public properties. It doesn't have to be a DependencyProperty though.
Second, which took me some tome to figure out, is that you have to set the binding for the CommandParameter before the Command property. i.e.
<Button Content="Edit" HorizontalAlignment="Right" Height="20" Width="80"
CommandParameter="{Binding PersonModelProp}"
Command="{Binding EditPersonCommand}" />
Hope this helps :)
_PersonModel is private and so cannot be accessed. Create a public property that exposes it and bind to that in the CommandParameter. Remember to make the property a dependency property (technically not required but it helps) and the ViewModel should implement INotifyProperty changed and fire the PropertyChanged event so the binding is updated.
I think you have a problem in your EditPersonCommand (it not fired ok).I check it with relayCommand and it work!
This is the code:
ViewModel:
public class PersonViewModel : ViewModelBase
{
private PersonModel _PersonModel;
private ICommand _EditPersonCommand;
///<remarks>
/// must use the parameterless constructor to satisfy <Window.Resources>
///</remarks>
public PersonViewModel()
: this(new PersonModel())
{
}
public PersonViewModel(PersonModel personModel)
{
PersonModelProp = personModel;
}
public ICommand EditPersonCommand
{
get
{
if (_EditPersonCommand == null)
{
_EditPersonCommand = new RelayCommand(ExecuteEditPerson,CanExecuteEditPerson);
}
return _EditPersonCommand;
}
}
private bool CanExecuteEditPerson(object parameter)
{
PersonModel p = parameter as PersonModel;
return (p != null) && (p.Age > 0);
}
private void ExecuteEditPerson(object o)
{
}
public PersonModel PersonModelProp
{
get
{
return _PersonModel;
}
set
{
_PersonModel = value;
NotifyPropertyChanged("PersonModelProp");
}
}
}
And this RelayCommand (Fire events ok!)
public class RelayCommand : ICommand
{
#region Constants and Fields
private readonly Predicate<object> canExecute;
private readonly Action<object> execute;
#endregion
#region Constructors and Destructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
this.execute = execute;
this.canExecute = canExecute;
}
#endregion
#region Events
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
#endregion
#region Implemented Interfaces
#region ICommand
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
#endregion
#endregion
}
Xmal:
<Button Content="Edit" HorizontalAlignment="Right" Height="20" Width="80"
CommandParameter="{Binding PersonModelProp}"
Command="{Binding EditPersonCommand}" />