All,
Binded complete Model, values are getting displayed in controls but not able to get button click working... any suggestion? what I m missing or doing wrong ? Thanks
<Window x:Class="test" Title="test" Height="350" Width="525">
<StackPanel Name="abc" Orientation="Vertical" DataContext="{Binding Path=EMP, Mode=TwoWay}" Margin="4" Height="153">
<Label Content="Last Name:" Margin="0,0,4,0"/>
<TextBox Width="250" Text="{Binding Path=LastName}" Height="20"/>
<Button Grid.Row="2" Margin="0,0,4,0" Height="40" Width="40"
Command="{Binding Path=SaveCommand}" />
</StackPanel>
</Window>
class EmployeeVM: ViewModelBase
{
private bool _Execute = true;
public EmployeeVM()
{
emp = new Model.Employee { FirstName = "abc", LastName = "xyz" };
}
private string sFirstName;
private string sLastName;
private Model.Employee emp;
public Model.Employee EMP
{
get{return emp;}
set{emp = value;
OnPropertyChanged("EMP");}
}
public string LastName
{
get { return sLastName; }
set
{
sLastName = value;
OnPropertyChanged("LastName");
}
}
#region Commands
private ICommand _SaveCommand;
public ICommand SaveCommand
{
get
{
return _SaveCommand = new CommandHandler(Save, _Execute);
}
}
#endregion
private void Save(object param)
{
ObservableCollection<Model.Employee> newIM = new ObservableCollection<Model.Employee>();
foreach(Model.Employee e in newIM)
{
string a = e.FirstName;
string b = e.LastName;
}
}
}
public class CommandHandler : ICommand
{
Action<object> _act;
bool _canExecute;
public CommandHandler(Action<object> act, bool canExecute)
{
_act = act;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_act(parameter);
}
}
You can write your own command.
Here is the baseclass I use for my commands.
It has some very basic things that make life easier.
the Execute method accepts an object, so you will be able to pass arrays
a viewmodel can be easily passed in, that is the one you will work with in your commands (this is most of the time the case, swipe it out if you do not need that)
the changed handler leverages the CommandManager. This is really very helpful
Perhaps you want to change some things. All I have added is in there because it is very helpful. (especially the viewmodel)
public abstract class CommandBase : ICommand
{
public abstract bool CanExecute(object o);
public abstract void Execute(object o);
public PropertyChangedBase ViewModel { get; set; }
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
You would have implementations like
public class ExampleCommand : CommandBase
{
public ExampleCommand (PropertyChangedBase viewModel)
{
this.ViewModel = viewModel;
}
public override void Execute(object o)
{
// something like
var settings = UnityContainer.Resolve<ISettings>();
settings.MagicValue = (this.ViewModel as ConcreteViewModel).MagicValue;
}
public override bool CanExecute(object o)
{
return true;
}
}
in your ViewModel, you expose the command to the view by having a property:
public class ExampleViewModel : PropertyChangedBase
{
public ExampleViewModel ()
{
this.DoThisAndThatCommand = new ExampleCommand(this);
}
public CommandBase DoThisAndThatCommand { get; set; }
}
// and in XAML, you can use it like
<Button x:Name="Ok"
Command="{Binding DoThisAndThatCommand }" />
(Given you have connected the ViewModel and the View correctly by setting the DataContext of the View)
Now, whenever the Button is clicked, the Execute Method of the Command will get called.
You have your ViewModel right in the Command, so you can easily work with it.
It is very unusual to have a button inside a command or inside a ViewModel.
The trick about MVVM is to separate the View from the ViewModel and to not have
UIElements in the ViewModel.
If you do not have the PropertyChangedBase (this one comes with Caliburn.Micro) then I would suggest to use some easy INotifyPropertyChanged implementation.
I found this one here, should be german though
public abstract class NotifyPropertyChangedBase : INotifyPropertyChanged
{
#region < INotifyPropertyChanged > Members
/// <summary>
/// Is connected to a method which handle changes to a property (located in the WPF Data Binding Engine)
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise the [PropertyChanged] event
/// </summary>
/// <param name="propertyName">The name of the property</param>
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private Dictionary<string, object> propertyValueStorage;
#region Constructor
public NotifyPropertyChangedBase()
{
this.propertyValueStorage = new Dictionary<string, object>();
}
#endregion
/// <summary>
/// Set the value of the property and raise the [PropertyChanged] event
/// (only if the saved value and the new value are not equal)
/// </summary>
/// <typeparam name="T">The property type</typeparam>
/// <param name="property">The property as a lambda expression</param>
/// <param name="value">The new value of the property</param>
protected void SetValue<T>(Expression<Func<T>> property, T value)
{
LambdaExpression lambdaExpression = property as LambdaExpression;
if (lambdaExpression == null)
{
throw new ArgumentException("Invalid lambda expression", "Lambda expression return value can't be null");
}
string propertyName = this.getPropertyName(lambdaExpression);
T storedValue = this.getValue<T>(propertyName);
if (!object.Equals(storedValue, value))
{
this.propertyValueStorage[propertyName] = value;
this.OnPropertyChanged(propertyName);
}
}
/// <summary> Get the value of the property </summary>
/// <typeparam name="T">The property type</typeparam>
/// <param name="property">The property as a lambda expression</param>
/// <returns>The value of the given property (or the default value)</returns>
protected T GetValue<T>(Expression<Func<T>> property)
{
LambdaExpression lambdaExpression = property as LambdaExpression;
if (lambdaExpression == null)
{
throw new ArgumentException("Invalid lambda expression", "Lambda expression return value can't be null");
}
string propertyName = this.getPropertyName(lambdaExpression);
return getValue<T>(propertyName);
}
/// <summary>
/// Try to get the value from the internal dictionary of the given property name
/// </summary>
/// <typeparam name="T">The property type</typeparam>
/// <param name="propertyName">The name of the property</param>
/// <returns>Retrieve the value from the internal dictionary</returns>
private T getValue<T>(string propertyName)
{
object value;
if (propertyValueStorage.TryGetValue(propertyName, out value))
{
return (T)value;
}
else
{
return default(T);
}
}
/// <summary>
/// Extract the property name from a lambda expression
/// </summary>
/// <param name="lambdaExpression">The lambda expression with the property</param>
/// <returns>The extracted property name</returns>
private string getPropertyName(LambdaExpression lambdaExpression)
{
MemberExpression memberExpression;
if (lambdaExpression.Body is UnaryExpression)
{
var unaryExpression = lambdaExpression.Body as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else
{
memberExpression = lambdaExpression.Body as MemberExpression;
}
return memberExpression.Member.Name;
}
}
It is very easy to use!
In your ViewModel you have to provide public properties for Binding (this is VERY important) and fire a change notification.
Here is an example of how to use that basic implementation of INPC (INotifyPropertyChanged)
public class LoginViewModel : NotifyPropertyChangedBase
{
public string UserName { get;set; }
}
This INPC implementation makes the NotifyOfPropertyChange call for you, you do not have to care for it! But you will have to inspect what fits your case best.
In your Question you already have a ViewModelBase. Perhaps you want to use this one instead of the above.
Please try to write in English, because I was confused what you wrote(such as "If u c above", "b/c", and so on :P..)
Anyway, as for your problem, this should fix it:
<UserControl.Resources>
<C:MultiValueConverter x:Key="MultiParamConverter"></C:MultiValueConverter>
</UserControl.Resources>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Button Name="Expander" Content="+" Width="25" Margin="4,0,4,0" Command="{Binding ExpanderCommand}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource MultiParamConverter}">
<Binding ElementName="Content"/>
<Binding ElementName="Expander"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
<Label FontWeight="Bold">GENERAL INFORMATION</Label>
</StackPanel>
<StackPanel Name="Content" Orientation="Vertical" Visibility="Collapsed">
<Label>Test</Label>
</StackPanel>
</StackPanel>
Command:
public ICommand ExpanderCommand
{
get
{
return new RelayCommand(delegate(object param)
{
var args = (object[])param;
var content = (UIElement)args[0];
var button = (Button)args[1];
content.Visibility = (content.Visibility == Visibility.Visible) ? Visibility.Collapsed : Visibility.Visible;
button.Content = (content.Visibility == Visibility.Visible) ? "-" : "+";
});
}
}
and the converter:
public class MultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return values.ToArray();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException("No two way conversion, one way binding only.");
}
}
First of all, your view model class should either be a DependencyObject, or implementing INotifyPropertyChanged interface. You can always find a suitable MVVM library out there and use their base view model class.
Judging from your XAML that your CheckBox can bind to the same context of your button. So, while you are binding your buttonGetDetails into the ClickCommand you can also bind your chkDuplicates into a view model property, lets say CheckDuplicates. Therefore you won't need this as a parameter to your command, because the property will already be inside your view model. Such as:
class TestViewModel : ViewModelBase
{
bool checkDuplicates;
public bool CheckDuplicates
{
get { return checkDuplicates; }
set
{
if(checkDuplicates != value)
{
checkDuplicates = value;
OnPropertyChanged("CheckDuplicates");
}
}
}
//Everything else is same as before
// except the action
public void AnyAction(object param)
{
//no need for param anymore
//var parmValues = (Object)param;
bool test = this.CheckDuplicates;
}
}
Since this is supposed to model your view, you can get rid of any parameters of your command binding and make them part of your view model.
Related
I am developing something using wpf mvvm.
Currently, my implementation style is a bit unusual.
This is to prevent the sauce from gathering in one place.
Async void should be avoided, but I don't know how to avoid it.
Any solution?
Below is the source I used. (RelayCommand is generic and I need to create a new async command.)
<View.xaml>, <View.xaml.cs>
<BusyIndicator IsBusy="{IsBusy, mode=TwoWay}">
<Listbox ItemsSource="{Binding Models}"/>
<Button Command="{Binding OnClickButtonCommand}"
CommandParameter="whether the value(string) exists or not"/>
</BusyIndicator>
this.DataContext = new ViewModel();
<ViewModel.cs>
public ViewModel() { }
private isBusy;
public isBusy
{
get => isBusy;
set => SetField(ref isBusy, value);
}
public ObservableCollection<Model> Models { get; set; }
public ICommand OnClickButtonCommand { get => new ButtonCommand(this); }
<ButtonCommand.cs>
public ButtonCommand : RelayCommand
{
private ViewModel viewModel;
public ViewModel(ViewModel viewModel)
{
this.viewModel = viewModel
}
public override async void Execute(object obj)
{
viewModel.IsBusy = true;
await Task.Delay(1500);
viewModel.Models = new ObservableCollection<Model>(await Data());
viewModel.IsBusy = false;
}
private async Task<List<Model>> Data()
{
await Task.Delay(100);
var data = new List<Model>();
...
return data;
}
}
*Inserting await delay is to prevent debugging warning.
<CommandBase.cs>
public interface IRelayCommand : ICommand
{
new void Execute(object obj);
}
public abstract class RelayCommand : IRelayCommand
{
public RelayCommand() { }
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
bool ICommand.CanExecute(object obj)
{
if (obj?.ToString().Length == 0)
{
return false;
}
else
{
return true;
}
//return canExecute == null || this.canExecute.Invoke(parameter);
}
public abstract void Execute(object obj);
}
And if my code is weird or there is better code, please let me know!
Thanks. :)
https://johnthiriet.com/mvvm-going-async-with-async-command/
I've seen a lot of this site and other samples, but it's an example using a typical fuction,
so I can't apply it...
I'm using the AsyncCommand implementation from James Montemagno's MVVM-Helpers library (currently also implemented in the Xamarin Community Toolkit), you can take a look at the implementation here: AsyncCommand implementation
You can use it like this:
Install the Refactored.MvvmHelpers nuget package.
MyViewModel.cs
public class MyViewModel {
//other code goes here (Models collection, IsBusy, etc)
public ICommand DoWorkCommand => new AsyncCommand(DoWorkAsync);
public ICommand DoWorkWithParameterCommand => new AsyncCommand<bool>(DoWorkWithParameterAsync);
private async Task DoWorkAsync(){
//simulate work
await Task.Delay(1000);
}
private async Task DoWorkWithParameterAsync(bool itemExists){
//simulate work
await Task.Delay(1000);
}
}
MyView.cs
<BusyIndicator IsBusy="{IsBusy, mode=TwoWay}">
<Listbox ItemsSource="{Binding Models}"/>
<Button Command="{Binding DoWorkCommand}" />
<Button Command="{Binding DoWorkWithParameterCommand}" CommandParameter="False"/>
</BusyIndicator>
An example of my implementation of base classes:
BaseInpc,
RelayCommand, RelayCommandAsync,
RelayCommand<T>, RelayCommandAsync<T>.
There are a lot of verbose documentation tags in the code.
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Simplified
{
/// <summary>Base class with implementation of the <see cref="INotifyPropertyChanged"/> interface.</summary>
public abstract class BaseInpc : INotifyPropertyChanged
{
/// <inheritdoc cref="INotifyPropertyChanged"/>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>The protected method for raising the event <see cref = "PropertyChanged"/>.</summary>
/// <param name="propertyName">The name of the changed property.
/// If the value is not specified, the name of the method in which the call was made is used.</param>
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary> Protected method for assigning a value to a field and raising
/// an event <see cref = "PropertyChanged" />. </summary>
/// <typeparam name = "T"> The type of the field and assigned value. </typeparam>
/// <param name = "propertyFiled"> Field reference. </param>
/// <param name = "newValue"> The value to assign. </param>
/// <param name = "propertyName"> The name of the changed property.
/// If no value is specified, then the name of the method
/// in which the call was made is used. </param>
/// <remarks> The method is intended for use in the property setter. <br/>
/// To check for changes,
/// used the <see cref = "object.Equals (object, object)" /> method.
/// If the assigned value is not equivalent to the field value,
/// then it is assigned to the field. <br/>
/// After the assignment, an event is created <see cref = "PropertyChanged" />
/// by calling the method <see cref = "RaisePropertyChanged (string)" />
/// passing the parameter <paramref name = "propertyName" />. <br/>
/// After the event is created,
/// the <see cref = "OnPropertyChanged (string, object, object)" />
/// method is called. </remarks>
protected void Set<T>(ref T propertyFiled, in T newValue, [CallerMemberName] in string propertyName = null)
{
if (!Equals(propertyFiled, newValue))
{
T oldValue = propertyFiled;
propertyFiled = newValue;
RaisePropertyChanged(propertyName);
OnPropertyChanged(propertyName, oldValue, newValue);
}
}
/// <summary> The protected virtual method is called after the property has been assigned a value and after the event is raised <see cref = "PropertyChanged" />. </summary>
/// <param name = "propertyName"> The name of the changed property. </param>
/// <param name = "oldValue"> The old value of the property. </param>
/// <param name = "newValue"> The new value of the property. </param>
/// <remarks> Can be overridden in derived classes to respond to property value changes. <br/>
/// It is recommended to call the base method as the first operator in the overridden method. <br/>
/// If the overridden method does not call the base class, then an unwanted change in the base class logic is possible. </remarks>
protected virtual void OnPropertyChanged(in string propertyName, in object oldValue, in object newValue) { }
}
}
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
namespace Simplified
{
/// <summary> A class that implements <see cref = "ICommand" />. <br/>
/// Implementation taken from <see href="https://www.cyberforum.ru/wpf-silverlight/thread2390714-page4.html#post13535649"/>
/// and added a constructor for methods without a parameter.</summary>
public class RelayCommand : ICommand
{
protected readonly CanExecuteHandler<object> canExecute;
protected readonly ExecuteHandler<object> execute;
private readonly EventHandler requerySuggested;
/// <inheritdoc cref="ICommand.CanExecuteChanged"/>
public event EventHandler CanExecuteChanged;
/// <summary> Command constructor. </summary>
/// <param name = "execute"> Command method to execute. </param>
/// <param name = "canExecute"> Method that returns the state of the command. </param>
public RelayCommand(ExecuteHandler<object> execute, CanExecuteHandler<object> canExecute = null)
: this()
{
this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
this.canExecute = canExecute;
}
/// <inheritdoc cref="RelayCommand(ExecuteHandler{object}, CanExecuteHandler{object})"/>
public RelayCommand(ExecuteHandler execute, CanExecuteHandler canExecute = null)
: this
(
p => execute(),
p => canExecute?.Invoke() ?? true
)
{ }
private readonly Dispatcher dispatcher = Application.Current.Dispatcher;
/// <summary> The method that raises the event <see cref="CanExecuteChanged"/>.</summary>
public void RaiseCanExecuteChanged()
{
if (dispatcher.CheckAccess())
{
invalidate();
}
else
{
_ = dispatcher.BeginInvoke(invalidate);
}
}
private readonly Action invalidate;
private RelayCommand()
{
invalidate = () => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
requerySuggested = (o, e) => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
CommandManager.RequerySuggested += requerySuggested;
}
/// <inheritdoc cref="ICommand.CanExecute(object)"/>
public bool CanExecute(object parameter)
{
return canExecute?.Invoke(parameter) ?? true;
}
/// <inheritdoc cref="ICommand.Execute(object)"/>
public void Execute(object parameter)
{
execute?.Invoke(parameter);
}
}
}
using System;
using System.Collections;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Simplified
{
/// <summary>A class that implements asynchronous commands with the execution
/// of the <see cref="RelayCommand.Execute(object)"/> method in Task. </summary>
/// <remarks>Only one call to the <see cref="RelayCommand.Execute(object)"/>
/// method is allowed at a time.
/// During the execution of the method, the <see cref="IsBusy"/> flag is set and
/// the <see cref="RelayCommand.CanExecute(object)"/> method of the command will return false.
/// The <see cref="INotifyDataErrorInfo"/> interface is implemented to notify about
/// an erroneous call to the <see cref="RelayCommand.Execute(object)"/> method before
/// the previous execution completes and about exceptions during the
/// execution of <see cref="RelayCommand.Execute(object)"/>.
/// To notify about changes in property values, the <see cref="INotifyPropertyChanged"/>
/// interface is implemented.</remarks>
public class RelayCommandAsync : RelayCommand, ICommand, INotifyPropertyChanged, INotifyDataErrorInfo
{
/// <inheritdoc cref="INotifyPropertyChanged.PropertyChanged"/>
public event PropertyChangedEventHandler PropertyChanged;
/// <inheritdoc cref="INotifyDataErrorInfo.ErrorsChanged"/>
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
/// <summary>The command is in the execution state of the <see cref="RelayCommand.Execute(object)"/> method.</summary>
public bool IsBusy { get; private set; }
/// <inheritdoc cref="INotifyDataErrorInfo.HasErrors"/>
public bool HasErrors { get; private set; }
/// <summary>Exception from the last execution of the <see cref="RelayCommand.Execute(object)"/> method.</summary>
public Exception ExecuteException { get; private set; }
// A flag indicating a "call to execute busy a command" error.
private bool isBusyExecuteError;
/// <summary>Sets a value to the <see cref="IsBisy"/> property and notifies of its change.</summary>
/// <param name="isBusy">The value for the property.</param>
protected void SetIsBusy(bool isBusy)
{
if (IsBusy != isBusy)
{
IsBusy = isBusy;
PropertyChanged?.Invoke(this, Args.IsBusyPropertyEventArgs);
RaiseCanExecuteChanged();
}
}
/// <summary>Sets the HasErrors property and reports an entity-level error.</summary>
/// <param name="hasErrors">The value for the property.</param>
protected void SetEntityHasErrors(bool hasErrors)
=> SetHasErrors(hasErrors, Args.EntityLevelErrorsEventArgs);
/// <summary>Sets the HasErrors property and reports an entity or property level error.</summary>
/// <param name="hasErrors">The value for the property.</param>
/// <param name="args">Argument with data about the error level.</param>
protected void SetHasErrors(bool hasErrors, DataErrorsChangedEventArgs args)
{
if (HasErrors != hasErrors)
{
HasErrors = hasErrors;
PropertyChanged?.Invoke(this, Args.HasErrorsPropertyEventArgs);
}
ErrorsChanged?.Invoke(this, args);
}
/// <summary>Sets a value to the <see cref="ExecuteException"/> property and notifies of its change.</summary>
/// <param name="exception">The value for the property.</param>
protected void SetExecuteException(Exception exception)
{
if (ExecuteException != exception)
{
ExecuteException = exception;
PropertyChanged?.Invoke(this, Args.ExecuteExceptionPropertyEventArgs);
}
}
/// <inheritdoc cref="RelayCommand(ExecuteHandler{object}, CanExecuteHandler{object})"/>
public RelayCommandAsync(ExecuteHandler<object> execute, CanExecuteHandler<object> canExecute = null)
: this(new AsyncData(execute, canExecute))
{ }
/// <inheritdoc cref="RelayCommand(ExecuteHandler, CanExecuteHandler)"/>
public RelayCommandAsync(ExecuteHandler execute, CanExecuteHandler canExecute = null)
: this(new AsyncData(execute, canExecute))
{ }
// The field for storing additional, auxiliary data generated
// during the generation of the asynchronous method, wrapping
// the one obtained in the constructor.
private readonly AsyncData data;
/// <inheritdoc cref="RelayCommand(ExecuteHandler{object}, CanExecuteHandler{object})"/>
protected RelayCommandAsync(AsyncData data)
: base(data.ExecuteAsync, data.CanExecuteAsync)
{
this.data = data;
this.data.commandAsync = this;
}
/// <inheritdoc cref="INotifyDataErrorInfo.GetErrors(string)"/>
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
if (isBusyExecuteError)
{
yield return Args.BusyExecuteErrorMessage;
}
if (ExecuteException != null)
{
yield return ExecuteException;
}
}
IEnumerable errors = GetErrorsOverride(propertyName);
if (errors != null)
{
foreach (var error in errors)
{
yield return error;
}
}
}
/// <summary>Method overridden in derived classes to add error information</summary>
/// <param name="propertyName">The name of the property to retrieve validation
/// errors for; or null or Empty, to retrieve entity-level errors.</param>
/// <returns>The validation errors for the property or entity.</returns>
protected virtual IEnumerable GetErrorsOverride(string propertyName)
=> null;
/// <summary>A class with persistent elements to avoid re-creating them frequently.</summary>
public static class Args
{
public const string BusyExecuteErrorMessage = "Called the execution of a command when it is busy.";
public static readonly PropertyChangedEventArgs IsBusyPropertyEventArgs = new PropertyChangedEventArgs(nameof(IsBusy));
public static readonly PropertyChangedEventArgs HasErrorsPropertyEventArgs = new PropertyChangedEventArgs(nameof(HasErrors));
public static readonly PropertyChangedEventArgs ExecuteExceptionPropertyEventArgs = new PropertyChangedEventArgs(nameof(ExecuteException));
public static readonly DataErrorsChangedEventArgs EntityLevelErrorsEventArgs = new DataErrorsChangedEventArgs(string.Empty);
}
/// <summary>A class for storing additional, auxiliary data and methods that are generated
/// when generating asynchronous methods that wrap the synchronous methods received
/// in the constructor.</summary>
protected class AsyncData
{
public RelayCommandAsync commandAsync;
public async void ExecuteAsync(object parameter)
{
if (commandAsync.IsBusy)
{
commandAsync.isBusyExecuteError = true;
commandAsync.SetEntityHasErrors(true);
}
else
{
commandAsync.SetIsBusy(true);
try
{
await Task.Run(() => execute(parameter));
commandAsync.isBusyExecuteError = false;
commandAsync.SetExecuteException(null);
commandAsync.SetEntityHasErrors(false);
}
catch (Exception ex)
{
commandAsync.SetExecuteException(ex);
commandAsync.SetEntityHasErrors(true);
}
finally
{
commandAsync.SetIsBusy(false);
}
}
}
public CanExecuteHandler<object> CanExecuteAsync { get; }
private bool canExecuteNullAsync(object parameter) => !commandAsync.IsBusy;
private bool canExecuteAsync(object parameter) => !commandAsync.IsBusy && canExecute(parameter);
private readonly ExecuteHandler<object> execute;
private readonly CanExecuteHandler<object> canExecute;
/// <inheritdoc cref="AsyncData(ExecuteHandler, CanExecuteHandler)"/>
public AsyncData(ExecuteHandler<object> execute, CanExecuteHandler<object> canExecute)
{
this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
if (canExecute == null)
{
CanExecuteAsync = canExecuteNullAsync;
}
else
{
this.canExecute = canExecute;
CanExecuteAsync = canExecuteAsync;
}
}
/// <summary>Creates an instance.</summary>
/// <param name="execute">Synchronous Execute method.</param>
/// <param name="canExecute">Synchronous CanExecute method.</param>
public AsyncData(ExecuteHandler execute, CanExecuteHandler canExecute)
{
if (execute == null)
{
throw new ArgumentNullException(nameof(execute));
}
this.execute = p => execute();
if (canExecute == null)
{
CanExecuteAsync = canExecuteNullAsync;
}
else
{
this.canExecute = p => canExecute();
CanExecuteAsync = canExecuteAsync;
}
}
}
}
}
Some of the classes did not fit into the answer.
Take them from here.
An example of using RelayCommand for your code.
public class ViewModel : BaseInpc
{
private bool _isBusy;
public bool IsBusy
{
get => _isBusy;
set => Set(ref _isBusy, value);
}
public ObservableCollection<Model> Models { get; set; }
// Under no circumstances should you get an instance of a command in this way.
// As such, each time the property is accessed, a NEW instance of the command is created.
// As a result, in each element of the Window, in the ViewMode, etc. there are different instances everywhere.
// This is a very common cause of various bugs.
//
//public ICommand OnClickButtonCommand { get => new ButtonCommand(this); }
// One of the options for the correct team creation.
private RelayCommand _onClickButtonCommand;
public RelayCommand OnClickButtonCommand => _onClickButtonCommand
?? (_onClickButtonCommand = new RelayCommand(ExecuteOnClick));
// Executing command method
private async void ExecuteOnClick(object parameter)
{
IsBusy = true;
await Task.Delay(1500);
Models = new ObservableCollection<Model>(await Data());
IsBusy = false;
}
private async Task<List<Model>> Data()
{
await Task.Delay(100);
var data = new List<Model>();
// Some code
return data;
}
}
In such an implementation, you can create a method for each command, its own IsBusy property and other members of which it needs.
But to a large extent, all this (except for the synchronous executing method) can be encapsulated in the command itself.
Encapsulating the execution method does not make sense, since it is because of it that the command instances differ from each other.
My example using asynchronous commands
In the example, a blinking background, in order to understand at what moment the GUI lags occur - blinking during lags stops.
There are also several buttons to demonstrate the difference between synchronous and asynchronous method execution, and how errors and exceptions in them affect applications.
Click them in various combinations.
When errors and exceptions appear, they are displayed under the buttons at the bottom of the window.
If you have any questions during testing - ask.
using Simplified;
using System;
using System.Threading;
namespace RelayCommandAsyncTest
{
public class TestViewModel : BaseInpc
{
private int _count;
// Command execution counter (common for all commands)
public int Count { get => _count; set => Set(ref _count, value); }
// Аsynchronous command
public RelayCommandAsync TestCommandAsync { get; }
// Synchronous command
public RelayCommand TestCommand { get; }
// Synchronous command with Lags
public RelayCommand TestLagsCommand { get; }
// Synchronous command executing asynchronous command
// without checking it IsBusy.
public RelayCommand TestExecuteCommandAsync { get; }
// Long running execution method for validating
// an asynchronous command
private void ExecuteForAsync(object obj)
{
Thread.Sleep(2000);
Count++;
// If the command parameter is not passed,
// then there will be an exception
Thread.Sleep(Convert.ToInt32(obj));
}
// Long running execution method for creating
// lags in a synchronous command
private void ExecuteLags(object obj)
{
isExecuteLagsCommand = true;
TestLagsCommand.RaiseCanExecuteChanged();
Thread.Sleep(2000);
Count++;
// If the command parameter is not passed,
// then there will be an exception
Thread.Sleep(Convert.ToInt32(obj));
isExecuteLagsCommand = false;
TestLagsCommand.RaiseCanExecuteChanged();
}
bool isExecuteLagsCommand;
// Fast executing method for checking lags with
// a synchronous command
private void Execute(object obj)
{
Count++;
// If the command parameter is not passed,
// then there will be an exception
Thread.Sleep(Convert.ToInt32(obj));
}
// A command that executes an asynchronous command without checking if it is busy.
// If the asynchronous command is busy, it will generate a validation error.
private void ExecuteCommandAsync(object obj)
{
Count++;
TestCommandAsync.Execute(obj);
}
public TestViewModel()
{
TestCommandAsync = new RelayCommandAsync(ExecuteForAsync);
TestCommand = new RelayCommand(Execute);
TestLagsCommand = new RelayCommand(ExecuteLags, p => !isExecuteLagsCommand);
TestExecuteCommandAsync = new RelayCommand(ExecuteCommandAsync);
timer = new Timer
(
_ => TestCommandAsync.RaiseCanExecuteChanged(),
null,
0,
5000
);
}
// Timer to check for raising CanExecuteChanged from any thread.
private readonly Timer timer;
}
}
<Window x:Class="RelayCommandAsyncTest.CommandTestWindow"
x:Name="main"
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:RelayCommandAsyncTest" xmlns:common="clr-namespace:Common;assembly=Common"
mc:Ignorable="d"
Title="CommandTestWindow" Height="1000" Width="800"
FontSize="30">
<FrameworkElement.Triggers>
<EventTrigger RoutedEvent="Loaded" SourceName="main">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="FlashingBrush"
Storyboard.TargetProperty="Color"
Duration="0:0:0.5"
To="LightYellow"
AutoReverse="True"
RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</FrameworkElement.Triggers>
<FrameworkElement.DataContext>
<local:TestViewModel/>
</FrameworkElement.DataContext>
<FrameworkElement.Resources>
<ControlTemplate x:Key="validationTemplate">
<Border BorderThickness="5"
BorderBrush="Red">
<Grid IsHitTestVisible="False">
<Viewbox>
<TextBlock Foreground="Red" FontSize="80"
Text="! ! !"/>
</Viewbox>
<AdornedElementPlaceholder/>
</Grid>
</Border>
</ControlTemplate>
<Style TargetType="Button">
<Setter Property="Validation.ErrorTemplate" Value="{DynamicResource validationTemplate}"/>
</Style>
</FrameworkElement.Resources>
<Grid>
<Grid.Background>
<SolidColorBrush x:Name="FlashingBrush" Color="LightGreen"/>
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto" />
<RowDefinition/>
</Grid.RowDefinitions>
<UniformGrid Columns="2">
<Button x:Name="button" Content="Async throw Exception"
Command="{Binding TestCommandAsync}"
CommandParameter="qwerty" Margin="15"/>
<Button Content="Async Normal"
Command="{Binding TestCommandAsync}"
CommandParameter="2000" Margin="15"/>
<Button Content="Sync Lags"
Command="{Binding TestLagsCommand}"
CommandParameter="2000" Margin="15"/>
<Button Content="IsBusy is not checked"
Command="{Binding TestExecuteCommandAsync}" Margin="15"
CommandParameter="0">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding TestCommandAsync.HasErrors}"
Value="True">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontWeight" Value="ExtraBold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Content="Sync throw Exception"
Command="{Binding TestCommand}"
CommandParameter="qwerty" Margin="15"/>
<Button Content="Sync Normal"
Command="{Binding TestCommand}" Margin="15"
CommandParameter="2000"/>
</UniformGrid>
<TextBlock Grid.Row="1" Margin="5">
<Run Text="IsBusy:"/>
<Run Text="{Binding TestCommandAsync.IsBusy, Mode=OneWay}"/>
<Run Text=" "/>
<Run Text="HasErrors:"/>
<Run Text="{Binding TestCommandAsync.HasErrors, Mode=OneWay}"/>
<Run Text=" "/>
<Run Text="Count:"/>
<Run Text="{Binding Count}"/>
</TextBlock>
<ScrollViewer Grid.Row="2"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding ElementName=button, Path=(Validation.Errors)}"
DisplayMemberPath="ErrorContent"
FontSize="15"/>
</ScrollViewer>
</Grid>
</Window>
I have a small WPF application and I'm beginning to learn the MVVM Data Binding pattern. I have an empty textBox and I have bound it to "FirstName" however when I run my code it's not updating. I have an ObservableObject class which inherits from the INotifyPropertyChanged class to check if the property has been updated. When I run the code the property does take the correct value however the UI never updates.
My code is as follows
MainWindow.xaml
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding FirstName}" VerticalAlignment="Center" HorizontalAlignment="Left" TextWrapping="Wrap" FontSize="16" Margin="20,20,0,20" Width="132"></TextBox>
Main Windows.xaml.cs
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
Person.cs
class Person:ObservableObject
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
RaisePropertyChanged("FirstName");
}
}
}
MainViewModel.cs
class MainViewModel:ObservableObject
{
List<Person> pList = new List<Person>();
public MainViewModel()
{
pList = new List<Person>()
{
new Person() {FirstName="Craig"}
};
Init();
}
public void Init()
{
var li = pList.FirstOrDefault();
}
}
OnservableObject.cs
[Serializable]
public abstract class ObservableObject : INotifyPropertyChanged, IDisposable
{
#region Constructor
protected ObservableObject()
{
}
#endregion Constructor
#region DisplayName
/// <summary>
/// Returns the user-friendly name of this object.
/// Child classes can set this property to a new value,
/// or override it to determine the value on-demand.
/// </summary>
public virtual string DisplayName { get; protected set; }
#endregion DisplayName
#region INotifyPropertyChanged Members
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
this.PropertyChanged?.Invoke(this, e);
}
protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpresssion)
{
var propertyName = PropertySupport.ExtractPropertyName(propertyExpresssion);
this.RaisePropertyChanged(propertyName);
}
protected void RaisePropertyChanged(String propertyName)
{
VerifyPropertyName(propertyName);
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
#endregion INotifyPropertyChanged Members
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion Debugging Aides
#region IDisposable Members
/// <summary>
/// Invoked when this object is being removed from the application
/// and will be subject to garbage collection.
/// </summary>
public void Dispose()
{
this.OnDispose();
}
/// <summary>
/// Child classes can override this method to perform
/// clean-up logic, such as removing event handlers.
/// </summary>
protected virtual void OnDispose()
{
}
/// <summary>
/// Useful for ensuring that ViewModel objects are properly garbage collected.
/// </summary>
~ObservableObject()
{
string msg = string.Format("{0} ({1}) ({2}) Finalized", this.GetType().Name, this.DisplayName, this.GetHashCode());
Debug.WriteLine(msg);
}
#endregion IDisposable Members
}
PropertySupport.cs
public static class PropertySupport
{
public static String ExtractPropertyName<T>(Expression<Func<T>> propertyExpresssion)
{
if (propertyExpresssion == null)
{
throw new ArgumentNullException("propertyExpresssion");
}
var memberExpression = propertyExpresssion.Body as MemberExpression;
if (memberExpression == null)
{
throw new ArgumentException("The expression is not a member access expression.", "propertyExpresssion");
}
var property = memberExpression.Member as PropertyInfo;
if (property == null)
{
throw new ArgumentException("The member access expression does not access a property.", "propertyExpresssion");
}
var getMethod = property.GetGetMethod(true);
if (getMethod.IsStatic)
{
throw new ArgumentException("The referenced property is a static property.", "propertyExpresssion");
}
return memberExpression.Member.Name;
}
}
You should make a property CurrentPerson, or something like this, in your MainViewModel. CurrentPerson should be e.g. the first entry of your pList. Then you can bind to CurrentPerson.FirstName.
The MainViewModel has no property called FirstName. In fact, it has no property at all.
If you define pList as a public property:
class MainViewModel : ObservableObject
{
public List<Person> pList { get; }
public MainViewModel()
{
pList = new List<Person>()
{
new Person() {FirstName="Craig"}
};
}
}
...you can bind to the first Person object in the List<Person> like this:
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding pList[0].FirstName}" />
Alternatively, you could add a Person property that returns the value of pList.FirstOrDefault() to the MainViewModel class and bind to this one in your XAML.
But you can only bind to public properties. You cannot bind to fields.
You are trying to bind the Model's property directly to the UI.
Think what you are going to bind to the TextBox. You are having a list of Person, from which you may bind selected person's FirstName property to the TextBox. In that sense, create a SelectedPerson property in ViewModel and bind the SelectedPerson.FirstName to the TextBox which would work.
Add the below property to your viewmodel
private Person _selectedPerson;
public Person SelectedPerson
{
get { return _selectedPerson; }
set
{
_selectedPerson= value;
RaisePropertyChanged("SelectedPerson");
}
}
And try to bind as follows in XAML
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding SelectedPerson.FirstName}" VerticalAlignment="Center" HorizontalAlignment="Left" TextWrapping="Wrap" FontSize="16" Margin="20,20,0,20" Width="132"></TextBox>
I am trying to pass CommandParameter to the method in my ViewModel.
How to do this?
private void Open(object sender)
{
if (sender==this.objMainWindow.btnHistory)
{
objMainWindow.Container.Child = objHistory;
}
if (sender == this.objMainWindow.btnNew_Item)
{
objMainWindow.Container.Child = objNewItem;
}
if (sender == this.objMainWindow.btnSide_Effects)
{
objMainWindow.Container.Child = objSideEffect;
}
}
This is my meyhod in ViewModel that I want to pass CommandParameter. I use CommandParameter for button.
"ViewModel" implies MVVM. If you're doing MVVM you shouldn't be passing views into your view models. Typically you do something like this in your XAML:
<Button Content="Edit"
Command="{Binding EditCommand}"
CommandParameter="{Binding ViewModelItem}" >
And then this in your view model:
private ViewModelItemType _ViewModelItem;
public ViewModelItemType ViewModelItem
{
get
{
return this._ViewModelItem;
}
set
{
this._ViewModelItem = value;
RaisePropertyChanged(() => this.ViewModelItem);
}
}
public ICommand EditCommand { get { return new RelayCommand<ViewModelItemType>(OnEdit); } }
private void OnEdit(ViewModelItemType itemToEdit)
{
... do something here...
}
Obviously this is just to illustrate the point, if you only had one property to edit called ViewModelItem then you wouldn't need to pass it in as a command parameter.
Just using Data Binding syntax. For example,
<Button x:Name="btn"
Content="Click"
Command="{Binding ClickCmd}"
CommandParameter="{Binding ElementName=btn,Path=Content}" />
Not only can we use Data Binding to get some data from View Models, but also pass data back to View Models. In CommandParameter, must use ElementName to declare binding source explicitly.
If you are that particular to pass elements to viewmodel You can use
CommandParameter="{Binding ElementName=ManualParcelScanScreen}"
Try this:
public class MyVmBase : INotifyPropertyChanged
{
private ICommand _clickCommand;
public ICommand ClickCommand
{
get
{
return _clickCommand ?? (_clickCommand = new CommandHandler( MyAction));
}
}
public void MyAction(object message)
{
if(message == null)
{
Notify($"Method {message} not defined");
return;
}
switch (message.ToString())
{
case "btnAdd":
{
btnAdd_Click();
break;
}
case "BtnEdit_Click":
{
BtnEdit_Click();
break;
}
default:
throw new Exception($"Method {message} not defined");
break;
}
}
}
public class CommandHandler : ICommand
{
private Action<object> _action;
private Func<object, bool> _canExecute;
/// <summary>
/// Creates instance of the command handler
/// </summary>
/// <param name="action">Action to be executed by the command</param>
/// <param name="canExecute">A bolean property to containing current permissions to execute the command</param>
public CommandHandler(Action<object> action, Func<object, bool> canExecute)
{
if (action == null) throw new ArgumentNullException(nameof(action));
_action = action;
_canExecute = canExecute ?? (x => true);
}
public CommandHandler(Action<object> action) : this(action, null)
{
}
/// <summary>
/// Wires CanExecuteChanged event
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
/// <summary>
/// Forcess checking if execute is allowed
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_action(parameter);
}
public void Refresh()
{
CommandManager.InvalidateRequerySuggested();
}
}
And in xaml:
<Button
Command="{Binding ClickCommand}"
CommandParameter="BtnEdit_Click"/>
I have a problem when UI does not update to changing in variables, that are binded to control properties.
Help me understand why.
1) I have a class which inherited from UserControl and from InotifyPropertyChanged
public class BindableControl:UserControl, INotifyPropertyChanged
{
#region Data
private static readonly Dictionary<string, PropertyChangedEventArgs> eventArgCache;
private const string ERROR_MSG = "{0} is not a public property of {1}";
#endregion // Data
#region Constructors
static BindableControl()
{
eventArgCache = new Dictionary<string, PropertyChangedEventArgs>();
}
protected BindableControl()
{
}
#endregion // Constructors
#region Public Members
/// <summary>
/// Raised when a public property of this object is set.
/// </summary>
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Returns an instance of PropertyChangedEventArgs for
/// the specified property name.
/// </summary>
/// <param name="propertyName">
/// The name of the property to create event args for.
/// </param>
public static PropertyChangedEventArgs
GetPropertyChangedEventArgs(string propertyName)
{
if (String.IsNullOrEmpty(propertyName))
throw new ArgumentException(
"propertyName cannot be null or empty.");
PropertyChangedEventArgs args;
// Get the event args from the cache, creating them
// and adding to the cache if necessary.
lock (typeof(BindableObject))
{
bool isCached = eventArgCache.ContainsKey(propertyName);
if (!isCached)
{
eventArgCache.Add(
propertyName,
new PropertyChangedEventArgs(propertyName));
}
args = eventArgCache[propertyName];
}
return args;
}
#endregion // Public Members
#region Protected Members
/// <summary>
/// Derived classes can override this method to
/// execute logic after a property is set. The
/// base implementation does nothing.
/// </summary>
/// <param name="propertyName">
/// The property which was changed.
/// </param>
protected virtual void AfterPropertyChanged(string propertyName)
{
}
/// <summary>
/// Attempts to raise the PropertyChanged event, and
/// invokes the virtual AfterPropertyChanged method,
/// regardless of whether the event was raised or not.
/// </summary>
/// <param name="propertyName">
/// The property which was changed.
/// </param>
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
this.VerifyProperty(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
// Get the cached event args.
PropertyChangedEventArgs args =
GetPropertyChangedEventArgs(propertyName);
// Raise the PropertyChanged event.
handler(this, args);
}
this.AfterPropertyChanged(propertyName);
}
#endregion
#region Private Helpers
[Conditional("DEBUG")]
private void VerifyProperty(string propertyName)
{
Type type = this.GetType();
// Look for a public property with the specified name.
PropertyInfo propInfo = type.GetProperty(propertyName);
if (propInfo == null)
{
// The property could not be found,
// so alert the developer of the problem.
string msg = string.Format(
ERROR_MSG,
propertyName,
type.FullName);
Debug.Fail(msg);
}
}
#endregion
}
2) Then I have another classes, each of them inherited from BindableControl like this
public class CameraLocalization : BindableControl
{
public CameraLocalization()
{
headers = new CameraHeaders();
toolTips = new CameraToolTips();
SetRuLocal();
//SetEnLocal();
}
private Language lang = SettingsManager.Language.ru_RU;
private CameraHeaders headers;
private CameraToolTips toolTips;
public Language Lang
{
get { return lang; }
set
{
lang = value;
SetLocal();
RaisePropertyChanged();
}
}
3) In XAML I link this class as usercontrol and do binding like this:
xmlns:language ="clr-namespace:SettingsManager.Localization.Camera"
<Grid>
<language:CameraLocalization x:Name="Localization"></language:CameraLocalization>
<GroupBox Header="{Binding ElementName=Localization, Path=Headers.PositionGroupHeader, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
4) from another page I try to change language:
xmlns:language ="clr-namespace:SettingsManager.Localization.Camera"
<Grid Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Width="Auto" Margin="0,0,0,5">
<language:CameraLocalization x:Name="Localization"></language:CameraLocalization>
<ComboBox ItemsSource="{Binding Source={StaticResource Language}}" Width="70" HorizontalAlignment="Left"
SelectedValue="{Binding ElementName=Localization, Path=Lang, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></ComboBox>
And nothing happens. In debug mode i see that value of propertis changing, but they doesnot update on UI. What is the problem here? Who knows?
Your properties should NOT be declared in the UI elements (UserControl), and these should NOT implement INotifyPropertyChanged. You must separate UI from data/logic by using the MVVM Pattern.
You should create a proper ViewModel and put your properties and property change notification there.
I've searched and searched but can't obtain a proper, helpful answer.
I have a MainWindow wpf window. Its DataContext is set to its ViewModel.
I have a ListView which is binded to an ObservableCollection in the ViewModel:
<ListView Grid.Row="1" Grid.Column="0" Margin="2" Name="sources_ListView" Grid.RowSpan="1" ItemsSource="{Binding Path=Sources}">
<ListView.View>
<GridView>
<GridViewColumn Width="290" Header="Name"
DisplayMemberBinding="{Binding Path=OriginalPath}"/>
<GridViewColumn Width="80" Header="Type"
DisplayMemberBinding="{Binding Path=Type}"/>
</GridView>
</ListView.View>
</ListView>
RelayCommand:
public ICommand BrowseFileFolderCommand
{
get
{
if (_browseFileFolderCommand == null)
{
_browseFileFolderCommand = new RelayCommand(o =>
{
_sources.Add(new SourceItem(selectedPath, new DirectoryInfo(selectedPath)));
}, null);
}
return _browseFileFolderCommand;
}
}
Now obviously what the Lambda function does, wouldn't work in the real world as I have taken it out of context, but accept the fact that it does add SourceItem to the ObservableCollection _sources and that there is a Public Sources which gets the _sources. I have also made the type that ObservableCollection takes use INotifyChangedProperty.
When I use that RelayCommand which is inside a button which adds a source to the ObservableCollection, the ListView doesn't update?
Thanks for any help
EDIT SourceItem:
public class SourceItem : ISourceItem, INotifyPropertyChanged
{
DirectoryInfo _sourceFolder;
public DirectoryInfo SourceFolder { get { return _sourceFolder; } private set { _sourceFolder = value; } }
FileInfo _sourceFile;
public FileInfo SourceFiles { get { return _sourceFile; } private set { _sourceFile = value; } }
string _originalPath;
public string OriginalPath { get { return _originalPath; } private set { _originalPath = value; OnPropertyChanged("OriginalPath"); } }
bool _isFolder;
public bool IsFolder { get { return _isFolder; } }
// display friendly property of IsFolder
public string Type { get { return _isFolder == true ? "Folder" : "File"; } }
public SourceItem(string originalPath, DirectoryInfo sourceFolder)
{
_originalPath = originalPath;
_sourceFolder = sourceFolder;
_sourceFile = null;
_isFolder = true;
}
public SourceItem(string originalPath, FileInfo sourceFile)
{
_originalPath = originalPath;
_sourceFile = sourceFile;
_sourceFolder = null;
_isFolder = false;
}
#region INotifyPropertyChanged Members
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion // INotifyPropertyChanged Members
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real.
// public, instance property on this object
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = String.Format("Invalid property name: {0}", propertyName);
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion
}
Use the public version of the property to add the new item
Sources.Add(new SourceItem(selectedPath, new DirectoryInfo(selectedPath)));
You are currently adding the item to the private version of your property (_sources), while your UI is bound to the public version of the property (Sources), so your UI does not get the CollectionChanged notification the private version of the property raises, so does not know it needs to update.
The alternative is to simply raise the PropertyChanged event for your class manually to tell the UI to update. This is usually what I do when I want to add a lot of items to my collection at the same time, but only have the UI update once.
_sources.Add(new SourceItem(selectedPath, new DirectoryInfo(selectedPath)));
RaisePropertyChanged("Sources");