My ViewModel knows about the View, how do I fix this? - c#

I think the code is self-explanatory.
<Interactivity:Interaction.Triggers>
<Interactivity:EventTrigger EventName="Deleting">
<MVVMLight:EventToCommand Command="{Binding Deleting, Mode=OneWay}"
PassEventArgsToCommand="True" />
</Interactivity:EventTrigger>
</Interactivity:Interaction.Triggers>
I have my own custom control with a delete event and want to bind it to a command in the ViewModel.
But in the view model, I have now either
public void OnDeleting(EventArgs args)
{
var e = args as MapDeletingEventArgs;
if (e == null)
throw new ArgumentNullException("args");
Database.Delete(e.Maps);
Database.Commit();
}
or worse
public void OnDeleting(MapDeletingEventArgs args)
{
if (args == null)
throw new ArgumentNullException("args");
Database.Delete(args.Maps);
Database.Commit();
}
And I know how bad it is to have view logic in the ViewModel. I can think of no better way, does anyone advice? I use the framework MVVMLight as you can see maybe.

This can be achieved with an ICommand implementation that takes a Map instance as it's command parameter:
//WARNING: all code typed in SO window
public class DeleteMapsCommand : ICommand
{
private Database _db;
public DeleteMapsCommand(Database db)
{
_db = db;
}
public void CanExecute(object parameter)
{
//only allow delete if the parameter passed in is a valid Map
return (parameter is Map);
}
public void Execute(object parameter)
{
var map = parameter as Map;
if (map == null) return;
_db.Delete(map);
_db.Commit();
}
public event EventHandler CanExecuteChanged; //ignore this for now
}
You then create a public property in your view model to expose an instance of the command
public class ViewModel
{
public ViewModel() {
//get the Database reference from somewhere?
this.DeleteMapCommand = new DeleteMapsCommand(this.Database);
}
public ICommand DeleteMapCommand { get; private set; }
}
Finally you need to bind your action to the command property and bind the command property to the map to be deleted. You haven't really given me enough of your XAML to state how this should be done in your case, but you could do something like the below with a ListBox:
<ListBox x:Name="ListOfMaps" ItemsSource="{Binding AllTheMaps}" />
<Button Command="{Binding DeleteMapCommand}" CommandParameter="{Binding SelectedItem, ElementName=ListOfMaps}">Delete Selected Map</Button>
Update
To attach the command to the event you can use an attached property:
public static class Helper
{
public static IComparable GetDeleteMapCommand(DependencyObject obj)
{
return (IComparable)obj.GetValue(DeleteMapCommandProperty);
}
public static void SetDeleteMapCommand(DependencyObject obj, IComparable value)
{
obj.SetValue(DeleteMapCommandProperty, value);
}
public static readonly DependencyProperty DeleteMapCommandProperty =
DependencyProperty.RegisterAttached("DeleteMapCommand", typeof(IComparable), typeof(Helper), new UIPropertyMetadata(null, OnDeleteMapCommandChanged));
private static void OnDeleteMapCommandChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
//when we attach the command, grab a reference to the control
var mapControl = sender as MapControl;
if (mapControl == null) return;
//and the command
var command = GetDeleteMapCommand(sender);
if (command == null) return;
//then hook up the event handler
mapControl.Deleting += (o,e) =>
{
if (command.CanExecute(e.Maps))
command.Execute(e.Maps);
};
}
}
You then need to bind the command like this:
<MapControl local:Helper.DeleteMapCommand="{Binding DeleteMapCommand}" />
Now your view model has no reference to the view-specific types.

If you don't want to hand your EventArgs off to your viewmodel, you could try using a Behavior (this is similar to Steve Greatrex's solution, but uses the Blend SDK'a behavior instead):
Here is an example I use in one of my applications.
First, here's my custom behavior base class:
/// <summary>
/// "Better" Behavior base class which allows for safe unsubscribing. The default Behavior class does not always call <see cref="Behavior.OnDetaching"/>
/// </summary>
/// <typeparam name="T">The dependency object this behavior should be attached to</typeparam>
public abstract class ZBehaviorBase<T> : Behavior<T> where T : FrameworkElement
{
private bool _isClean = true;
/// <summary>
/// Called after the behavior is attached to an AssociatedObject.
/// </summary>
/// <remarks>Override this to hook up functionality to the AssociatedObject.</remarks>
protected sealed override void OnAttached()
{
base.OnAttached();
AssociatedObject.Unloaded += OnAssociatedObjectUnloaded;
_isClean = false;
ValidateRequiredProperties();
Initialize();
}
/// <summary>
/// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
/// </summary>
/// <remarks>Override this to unhook functionality from the AssociatedObject.</remarks>
protected sealed override void OnDetaching()
{
CleanUp();
base.OnDetaching();
}
/// <summary>
/// Validates the required properties. This method is called when the object is attached, but before
/// the <see cref="Initialize"/> is invoked.
/// </summary>
protected virtual void ValidateRequiredProperties()
{
}
/// <summary>
/// Initializes the behavior. This method is called instead of the <see cref="OnAttached"/> which is sealed
/// to protect the additional behavior.
/// </summary>
protected abstract void Initialize();
/// <summary>
/// Uninitializes the behavior. This method is called when <see cref="OnDetaching"/> is called, or when the
/// <see cref="AttachedControl"/> is unloaded.
/// <para />
/// If dependency properties are used, it is very important to use <see cref="ClearValue"/> to clear the value
/// of the dependency properties in this method.
/// </summary>
protected abstract void Uninitialize();
/// <summary>
/// Called when the <see cref="AssociatedObject"/> is unloaded.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private void OnAssociatedObjectUnloaded(object sender, EventArgs e)
{
CleanUp();
}
/// <summary>
/// Actually cleans up the behavior because <see cref="OnDetaching"/> is not always called.
/// </summary>
/// <remarks>
/// This is based on the blog post: http://dotnetbyexample.blogspot.com/2011/04/safe-event-detachment-pattern-for.html.
/// </remarks>
private void CleanUp()
{
if (_isClean)
{
return;
}
_isClean = true;
if (AssociatedObject != null)
{
AssociatedObject.Unloaded -= OnAssociatedObjectUnloaded;
}
Uninitialize();
}
}
Now, my concrete implementation used to attach a command to the TextBlock's "click" event
public class TextBlockClickCommandBehavior : ZBehaviorBase<TextBlock>
{
public ICommand ClickCommand
{
get { return (ICommand)GetValue(ClickCommandProperty); }
set { SetValue(ClickCommandProperty, value); }
}
public static readonly DependencyProperty ClickCommandProperty =
DependencyProperty.Register("ClickCommand", typeof(ICommand), typeof(TextBlockClickCommandBehavior));
protected override void Initialize()
{
this.AssociatedObject.MouseLeftButtonUp += new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonUp);
}
protected override void Uninitialize()
{
if (this.AssociatedObject != null)
{
this.AssociatedObject.MouseLeftButtonUp -= new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonUp);
}
}
void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// if you want to pass a command param to CanExecute, need to add another dependency property to bind to
if (ClickCommand != null && ClickCommand.CanExecute(null))
{
ClickCommand.Execute(null);
}
}
}
And I use it like this:
<!--Make the TextBlock for "Item" clickable to display the item search window-->
<TextBlock x:Name="itemTextBlock" Text="Item:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Column="2" FontWeight="Bold">
<e:Interaction.Behaviors>
<ZViewModels:TextBlockClickCommandBehavior ClickCommand="{Binding Path=ItemSearchCommand}"/>
</e:Interaction.Behaviors>
</TextBlock>
Now, in your case, instead of passing NULL to the command's execute method, you'd want to pass your arguments' Maps collection

Related

How can I let my view model know when XAML data validation has failed

I have a form where users set parameters for a numerical process. Each parameter object has a default value.
public double DefaultValue
{
get => _defaultValue;
set
{
_defaultValue = value;
OnPropertyChanged("DefaultValue");
}
}
Although the property is a double, it might represent a Boolean, or an integer. For most parameters validation is not required, but I have two parameters, Min and Max, which are limited. I must not have Min > Max or Max < Min. I have implemented validation in XAML, which visually warns the user if the data is not valid. The data template for the Min parameter is as follows.
<DataTemplate x:Key="MinParameterDataTemplateThin">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding DisplayName, StringFormat='{}{0}:'}" Grid.Column="0" Margin="10,5,5,10" VerticalAlignment="Top" TextWrapping="Wrap"
Visibility="{Binding Visibility}" ToolTipService.ShowDuration="20000">
<TextBlock.ToolTip>
<ToolTip DataContext="{Binding Path=PlacementTarget.DataContext, RelativeSource={x:Static RelativeSource.Self}}">
<TextBlock Text="{Binding Description}"/>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBox Name ="MinTextBox" Margin="5" Width="50" VerticalAlignment="Top"
Visibility="{Binding Visibility}" IsEnabled="{Binding IsEnabled}">
<TextBox.Resources>
<validations:BindingProxy x:Key="proxy" Data="{Binding}"/>
</TextBox.Resources>
<TextBox.Text>
<Binding Path="DefaultValue" StringFormat="N2" Mode="TwoWay"
UpdateSourceTrigger="LostFocus"
ValidatesOnExceptions="True"
NotifyOnValidationError="True"
ValidatesOnNotifyDataErrors="True">
<Binding.ValidationRules>
<validations:MaximumValueValidation ValidatesOnTargetUpdated="True">
<validations:MaximumValueValidation.MaxValueWrapper>
<validations:MaxValueWrapper MaxValue="{Binding Data.MaxValue, Source={StaticResource proxy}}"/>
</validations:MaximumValueValidation.MaxValueWrapper>
</validations:MaximumValueValidation>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock Text="{Binding UnitSymbol}" Margin="5" VerticalAlignment="Top" Visibility="{Binding Visibility}"/>
</StackPanel>
<Label Name="ValidationLabel" Content="{Binding ElementName=MinTextBox, Path=(Validation.Errors)[0].ErrorContent}" Foreground="Red" Grid.Row="1" VerticalAlignment="Top"/>
</Grid>
</Grid>
</DataTemplate>
There is a similar template for the Max parameter. In addition to the visual warning, I need to prevent the user from saving the data. I would like to have a Boolean IsValid property in the parameter object to test when the user tries to save. How would I bind from the XAML to this IsValid property?
When you see an error appear with a red border, there is a routed error event raised which will bubble up through the visual tree.
When you fix an error then you get the same event raised tells you that the error is fixed.
You can therefore add up errors occur and subtract errors fixed. If you have more than zero you have something needs fixing.
This is the Validation.ErrorEvent
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.validation.error?view=windowsdesktop-7.0
You can then pass the result to the viewmodel or call a command to pass the event result and your viewmodel does that addition.
Here's some markup and code.
In a parent of all the controls which may error:
<i:Interaction.Triggers>
<UIlib:RoutedEventTrigger RoutedEvent="{x:Static Validation.ErrorEvent}">
<e2c:EventToCommand
Command="{Binding ConversionErrorCommand, Mode=OneWay}"
EventArgsConverter="{StaticResource BindingErrorEventArgsConverter}"
PassEventArgsToCommand="True" />
</UIlib:RoutedEventTrigger>
Not sure this will still be copy paste friendly since it's a bit old now.
public RelayCommand<PropertyError> ConversionErrorCommand
{
get
{
return conversionErrorCommand
?? (conversionErrorCommand = new RelayCommand<PropertyError>
(PropertyError =>
{
if (PropertyError.Added)
{
AddError(PropertyError.PropertyName, PropertyError.Error, ErrorSource.Conversion);
}
FlattenErrorList();
}));
}
}
Converter
public class BindingErrorEventArgsConverter : IEventArgsConverter
{
public object Convert(object value, object parameter)
{
ValidationErrorEventArgs e = (ValidationErrorEventArgs)value;
PropertyError err = new PropertyError();
err.PropertyName = ((System.Windows.Data.BindingExpression)(e.Error.BindingInError)).ResolvedSourcePropertyName;
err.Error = e.Error.ErrorContent.ToString();
// Validation.ErrorEvent fires both when an error is added AND removed
if (e.Action == ValidationErrorEventAction.Added)
{
err.Added = true;
}
else
{
err.Added = false;
}
return err;
}
}
Routedeventtrigger
// This is necessary in order to grab the bubbling routed source changed and conversion errors
public class RoutedEventTrigger : EventTriggerBase<DependencyObject>
{
RoutedEvent routedEvent;
public RoutedEvent RoutedEvent
{
get
{
return routedEvent;
}
set
{
routedEvent = value;
}
}
public RoutedEventTrigger()
{
}
protected override void OnAttached()
{
Behavior behavior = base.AssociatedObject as Behavior;
FrameworkElement associatedElement = base.AssociatedObject as FrameworkElement;
if (behavior != null)
{
associatedElement = ((IAttachedObject)behavior).AssociatedObject as FrameworkElement;
}
if (associatedElement == null)
{
throw new ArgumentException("This only works with framework elements");
}
if (RoutedEvent != null)
{
associatedElement.AddHandler(RoutedEvent, new RoutedEventHandler(this.OnRoutedEvent));
}
}
void OnRoutedEvent(object sender, RoutedEventArgs args)
{
base.OnEvent(args);
args.Handled = true;
}
protected override string GetEventName()
{
return RoutedEvent.Name;
}
}
Event to command is from mvvm light. Which is now deprecated but the code still works.
You might like
MVVM Passing EventArgs As Command Parameter
I think this explains a prism approach:
https://weblogs.asp.net/alexeyzakharov/silverlight-commands-hacks-passing-eventargs-as-commandparameter-to-delegatecommand-triggered-by-eventtrigger
But I think mvvmlight is open source, here's the code for eventtocommand:
// ****************************************************************************
// <copyright file="EventToCommand.cs" company="GalaSoft Laurent Bugnion">
// Copyright © GalaSoft Laurent Bugnion 2009-2016
// </copyright>
// ****************************************************************************
// <author>Laurent Bugnion</author>
// <email>laurent#galasoft.ch</email>
// <date>3.11.2009</date>
// <project>GalaSoft.MvvmLight.Extras</project>
// <web>http://www.mvvmlight.net</web>
// <license>
// See license.txt in this solution or http://www.galasoft.ch/license_MIT.txt
// </license>
// ****************************************************************************
using Microsoft.Xaml.Behaviors;
using System;
using System.Windows;
using System.Windows.Input;
////using GalaSoft.Utilities.Attributes;
namespace GalaSoft.MvvmLight.CommandWpf
{
/// <summary>
/// This <see cref="T:System.Windows.Interactivity.TriggerAction`1" /> can be
/// used to bind any event on any FrameworkElement to an <see cref="ICommand" />.
/// Typically, this element is used in XAML to connect the attached element
/// to a command located in a ViewModel. This trigger can only be attached
/// to a FrameworkElement or a class deriving from FrameworkElement.
/// <para>To access the EventArgs of the fired event, use a RelayCommand<EventArgs>
/// and leave the CommandParameter and CommandParameterValue empty!</para>
/// </summary>
////[ClassInfo(typeof(EventToCommand),
//// VersionString = "5.2.8",
//// DateString = "201504252130",
//// Description = "A Trigger used to bind any event to an ICommand.",
//// UrlContacts = "http://www.galasoft.ch/contact_en.html",
//// Email = "laurent#galasoft.ch")]
public class EventToCommand : TriggerAction<DependencyObject>
{
/// <summary>
/// Identifies the <see cref="CommandParameter" /> dependency property
/// </summary>
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(EventToCommand),
new PropertyMetadata(
null,
(s, e) =>
{
var sender = s as EventToCommand;
if (sender == null)
{
return;
}
if (sender.AssociatedObject == null)
{
return;
}
sender.EnableDisableElement();
}));
/// <summary>
/// Identifies the <see cref="Command" /> dependency property
/// </summary>
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(EventToCommand),
new PropertyMetadata(
null,
(s, e) => OnCommandChanged(s as EventToCommand, e)));
/// <summary>
/// Identifies the <see cref="MustToggleIsEnabled" /> dependency property
/// </summary>
public static readonly DependencyProperty MustToggleIsEnabledProperty = DependencyProperty.Register(
"MustToggleIsEnabled",
typeof(bool),
typeof(EventToCommand),
new PropertyMetadata(
false,
(s, e) =>
{
var sender = s as EventToCommand;
if (sender == null)
{
return;
}
if (sender.AssociatedObject == null)
{
return;
}
sender.EnableDisableElement();
}));
private object _commandParameterValue;
private bool? _mustToggleValue;
/// <summary>
/// Gets or sets the ICommand that this trigger is bound to. This
/// is a DependencyProperty.
/// </summary>
public ICommand Command
{
get
{
return (ICommand) GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
/// <summary>
/// Gets or sets an object that will be passed to the <see cref="Command" />
/// attached to this trigger. This is a DependencyProperty.
/// </summary>
public object CommandParameter
{
get
{
return GetValue(CommandParameterProperty);
}
set
{
SetValue(CommandParameterProperty, value);
}
}
/// <summary>
/// Gets or sets an object that will be passed to the <see cref="Command" />
/// attached to this trigger. This property is here for compatibility
/// with the Silverlight version. This is NOT a DependencyProperty.
/// For databinding, use the <see cref="CommandParameter" /> property.
/// </summary>
public object CommandParameterValue
{
get
{
return _commandParameterValue ?? CommandParameter;
}
set
{
_commandParameterValue = value;
EnableDisableElement();
}
}
/// <summary>
/// Gets or sets a value indicating whether the attached element must be
/// disabled when the <see cref="Command" /> property's CanExecuteChanged
/// event fires. If this property is true, and the command's CanExecute
/// method returns false, the element will be disabled. If this property
/// is false, the element will not be disabled when the command's
/// CanExecute method changes. This is a DependencyProperty.
/// </summary>
public bool MustToggleIsEnabled
{
get
{
return (bool) GetValue(MustToggleIsEnabledProperty);
}
set
{
SetValue(MustToggleIsEnabledProperty, value);
}
}
/// <summary>
/// Gets or sets a value indicating whether the attached element must be
/// disabled when the <see cref="Command" /> property's CanExecuteChanged
/// event fires. If this property is true, and the command's CanExecute
/// method returns false, the element will be disabled. This property is here for
/// compatibility with the Silverlight version. This is NOT a DependencyProperty.
/// For databinding, use the <see cref="MustToggleIsEnabled" /> property.
/// </summary>
public bool MustToggleIsEnabledValue
{
get
{
return _mustToggleValue == null
? MustToggleIsEnabled
: _mustToggleValue.Value;
}
set
{
_mustToggleValue = value;
EnableDisableElement();
}
}
/// <summary>
/// Called when this trigger is attached to a FrameworkElement.
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
EnableDisableElement();
}
#if SILVERLIGHT
private Control GetAssociatedObject()
{
return AssociatedObject as Control;
}
#else
/// <summary>
/// This method is here for compatibility
/// with the Silverlight version.
/// </summary>
/// <returns>The FrameworkElement to which this trigger
/// is attached.</returns>
private FrameworkElement GetAssociatedObject()
{
return AssociatedObject as FrameworkElement;
}
#endif
/// <summary>
/// This method is here for compatibility
/// with the Silverlight 3 version.
/// </summary>
/// <returns>The command that must be executed when
/// this trigger is invoked.</returns>
private ICommand GetCommand()
{
return Command;
}
/// <summary>
/// Specifies whether the EventArgs of the event that triggered this
/// action should be passed to the bound RelayCommand. If this is true,
/// the command should accept arguments of the corresponding
/// type (for example RelayCommand<MouseButtonEventArgs>).
/// </summary>
public bool PassEventArgsToCommand
{
get;
set;
}
/// <summary>
/// Gets or sets a converter used to convert the EventArgs when using
/// <see cref="PassEventArgsToCommand"/>. If PassEventArgsToCommand is false,
/// this property is never used.
/// </summary>
public IEventArgsConverter EventArgsConverter
{
get;
set;
}
/// <summary>
/// The <see cref="EventArgsConverterParameter" /> dependency property's name.
/// </summary>
public const string EventArgsConverterParameterPropertyName = "EventArgsConverterParameter";
/// <summary>
/// Gets or sets a parameters for the converter used to convert the EventArgs when using
/// <see cref="PassEventArgsToCommand"/>. If PassEventArgsToCommand is false,
/// this property is never used. This is a dependency property.
/// </summary>
public object EventArgsConverterParameter
{
get
{
return GetValue(EventArgsConverterParameterProperty);
}
set
{
SetValue(EventArgsConverterParameterProperty, value);
}
}
/// <summary>
/// Identifies the <see cref="EventArgsConverterParameter" /> dependency property.
/// </summary>
public static readonly DependencyProperty EventArgsConverterParameterProperty = DependencyProperty.Register(
EventArgsConverterParameterPropertyName,
typeof(object),
typeof(EventToCommand),
new PropertyMetadata(null));
/// <summary>
/// The <see cref="AlwaysInvokeCommand" /> dependency property's name.
/// </summary>
public const string AlwaysInvokeCommandPropertyName = "AlwaysInvokeCommand";
/// <summary>
/// Gets or sets a value indicating if the command should be invoked even
/// if the attached control is disabled. This is a dependency property.
/// </summary>
public bool AlwaysInvokeCommand
{
get
{
return (bool)GetValue(AlwaysInvokeCommandProperty);
}
set
{
SetValue(AlwaysInvokeCommandProperty, value);
}
}
/// <summary>
/// Identifies the <see cref="AlwaysInvokeCommand" /> dependency property.
/// </summary>
public static readonly DependencyProperty AlwaysInvokeCommandProperty = DependencyProperty.Register(
AlwaysInvokeCommandPropertyName,
typeof(bool),
typeof(EventToCommand),
new PropertyMetadata(false));
/// <summary>
/// Provides a simple way to invoke this trigger programatically
/// without any EventArgs.
/// </summary>
public void Invoke()
{
Invoke(null);
}
/// <summary>
/// Executes the trigger.
/// <para>To access the EventArgs of the fired event, use a RelayCommand<EventArgs>
/// and leave the CommandParameter and CommandParameterValue empty!</para>
/// </summary>
/// <param name="parameter">The EventArgs of the fired event.</param>
protected override void Invoke(object parameter)
{
if (AssociatedElementIsDisabled()
&& !AlwaysInvokeCommand)
{
return;
}
var command = GetCommand();
var commandParameter = CommandParameterValue;
if (commandParameter == null
&& PassEventArgsToCommand)
{
commandParameter = EventArgsConverter == null
? parameter
: EventArgsConverter.Convert(parameter, EventArgsConverterParameter);
}
if (command != null
&& command.CanExecute(commandParameter))
{
command.Execute(commandParameter);
}
}
private static void OnCommandChanged(
EventToCommand element,
DependencyPropertyChangedEventArgs e)
{
if (element == null)
{
return;
}
if (e.OldValue != null)
{
((ICommand) e.OldValue).CanExecuteChanged -= element.OnCommandCanExecuteChanged;
}
var command = (ICommand) e.NewValue;
if (command != null)
{
command.CanExecuteChanged += element.OnCommandCanExecuteChanged;
}
element.EnableDisableElement();
}
private bool AssociatedElementIsDisabled()
{
var element = GetAssociatedObject();
return AssociatedObject == null
|| (element != null
&& !element.IsEnabled);
}
private void EnableDisableElement()
{
var element = GetAssociatedObject();
if (element == null)
{
return;
}
var command = GetCommand();
if (MustToggleIsEnabledValue
&& command != null)
{
element.IsEnabled = command.CanExecute(CommandParameterValue);
}
}
private void OnCommandCanExecuteChanged(object sender, EventArgs e)
{
EnableDisableElement();
}
}
}
I think you may need ieventargsconverter
namespace GalaSoft.MvvmLight.CommandWpf
{
/// <summary>
/// The definition of the converter used to convert an EventArgs
/// in the <see cref="EventToCommand"/> class, if the
/// <see cref="EventToCommand.PassEventArgsToCommand"/> property is true.
/// Set an instance of this class to the <see cref="EventToCommand.EventArgsConverter"/>
/// property of the EventToCommand instance.
/// </summary>
////[ClassInfo(typeof(EventToCommand))]
public interface IEventArgsConverter
{
/// <summary>
/// The method used to convert the EventArgs instance.
/// </summary>
/// <param name="value">An instance of EventArgs passed by the
/// event that the EventToCommand instance is handling.</param>
/// <param name="parameter">An optional parameter used for the conversion. Use
/// the <see cref="EventToCommand.EventArgsConverterParameter"/> property
/// to set this value. This may be null.</param>
/// <returns>The converted value.</returns>
object Convert(object value, object parameter);
}
}

Label does not change even model changed the value in mvvm

Can someone please tell me what is the mistake I have done ? Label in the view does not change even the Error.ErroMsg changed.
xaml view
<Label
x:Name="ErrorMsg"
Content="{Binding Path=Error.ErrorMsg, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="0"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Width="200"
Height="26"/>
View Model
private ErrorModel error = new ErrorModel();
public ErrorModel Error
{
get { return error; }
set { error = value; }
}
// This method will be called by a state machine using a delegate
public void displayErrorMessage(string message)
{
Error.ErrorMsg = message;
CommandManager.InvalidateRequerySuggested();
logger.Trace(" Successfully displayed the error message");
}
Model
// ModelBase inherited by INotifyPropertyChanged and IDisposable
class ErrorModel : ModelBase
{
private string errorMsg;
public string ErrorMsg
{
get { return errorMsg; }
set { errorMsg = value; OnPropertyChanged("ErrorMsg"); }
}
public ErrorModel() {
errorMsg = "TestHello";
}
}
ModelBase
public class ModelBase : INotifyPropertyChanged, IDisposable
{
///
/// Default constructor of view model base
///
protected ModelBase()
{
}
/// <summary>
/// Initialized property changed event handler
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// This function will call by public setters of the model class
/// </summary>
/// <param name="propertyName">Specific property name that needs to identify unique model member</param>
protected virtual void OnPropertyChanged(string propertyName)
{
// Initialize handler by using property changed event handler delegate
PropertyChangedEventHandler handler = this.PropertyChanged;
// Check that current handler is available or not
if (handler != null)
{
// Initialize property changed event args
var propertyChangedEventArgs = new PropertyChangedEventArgs(propertyName);
// Call back using property changed event handler
handler(this, propertyChangedEventArgs);
}
}
/// <summary>
/// This is calle when leaving the view model
/// </summary>
public void Dispose()
{
this.OnDispose();
}
/// <summary>
/// Model dependent data is cleared here
/// </summary>
protected virtual void OnDispose() { }
}
//Calling displayErrorMessage through a delegate
string cpDoesntWorkError = "Charging point not available."
this.uiDelegator.showErrorMessage(cpDoesntWorkError);
GUIDelegator
public delegate void DisplayErrorMessageDelegate(string message);
/// <summary>
/// This will register the ErrorMesssage delegate passed by the RCU_GUI
/// </summary>
/// <param name="del">ErrorMessageDelegate object which need to registered will be passed</param>
public void setDisplayErrorMessageDelegate(DisplayErrorMessageDelegate del)
{
displayErrorMessageDelegate = del;
logger.Trace("Error message delegate successfully set by RCU_GUI");
}
/// <summary>
/// Call back to the displayErrorMessage which is in RCU_GUI
/// </summary>
/// <param name="message">Error message need to be displayed</param>
/// <returns></returns>
public void showErrorMessage(string message)
{
// If the error message delegate is not null
if (displayErrorMessageDelegate != null)
{
logger.Trace("Displaying the error message");
displayErrorMessageDelegate(message);
}
else
logger.Trace("null display error message delegate");
}
You have to be aware of few things:
Whether your method OnPropertyChanged in ModelBase properly raises PropertyChanged event
In your View Model in property Error you are not notifying view that this property changes. Then some other code might assign new ErrorModel instance before displayErrorMessage method is called. Due to that view is not aware that Error property changed, so it still display TestHello string. If that's the case you can modify your View Model:
public ErrorModel Error
{
get { return error; }
set { error = value; OnPropertyChanged("Error"); }
}
Your delegate which is calling displayErrorMessage method must use the same View Model object instance which is assigned to DataContext property of View. Because if not, then definitely View doesn't get notified about property changes.

How to bind an Observable Collection to a ListView

I've set up the binding of a ListView following this example, binding to observable collection but when I run the application the collection values aren't displayed in the ListView.
The output window isn't throwing any binding errors, so not sure what the binding error could be.
Also I've set a breakpoint on the list before its sent to the second VM and it's populated, ie, not null.
My guess is that the list is null in the second VM as it's not being initialized properly after being passed over.
Can anyone advise how to debug the ListView being empty?
This is the binding set in the View:
<ListBox ItemsSource="{Binding AddedSubjectGradePairsCopy}" Height="400" Margin="0,0,0,-329" VerticalAlignment="Top">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Subject}" /><Run Text=" - " /><Run Text="{Binding Points}" />
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The data context for the View is set as follows in the code behind:
namespace LC_Points.View
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class ViewSubjectGradePage : Page
{
private NavigationHelper navigationHelper;
private ViewSubjectGradeViewModel ViewModel;
public ViewSubjectGradePage()
{
this.InitializeComponent();
this.navigationHelper = new NavigationHelper(this);
this.navigationHelper.LoadState += this.NavigationHelper_LoadState;
this.navigationHelper.SaveState += this.NavigationHelper_SaveState;
ViewModel = new ViewSubjectGradeViewModel();
this.DataContext = ViewModel;
}
/// <summary>
/// Gets the <see cref="NavigationHelper"/> associated with this <see cref="Page"/>.
/// </summary>
public NavigationHelper NavigationHelper
{
get { return this.navigationHelper; }
}
/// <summary>
/// Populates the page with content passed during navigation. Any saved state is also
/// provided when recreating a page from a prior session.
/// </summary>
/// <param name="sender">
/// The source of the event; typically <see cref="NavigationHelper"/>
/// </param>
/// <param name="e">Event data that provides both the navigation parameter passed to
/// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested and
/// a dictionary of state preserved by this page during an earlier
/// session. The state will be null the first time a page is visited.</param>
private void NavigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
}
/// <summary>
/// Preserves state associated with this page in case the application is suspended or the
/// page is discarded from the navigation cache. Values must conform to the serialization
/// requirements of <see cref="SuspensionManager.SessionState"/>.
/// </summary>
/// <param name="sender">The source of the event; typically <see cref="NavigationHelper"/></param>
/// <param name="e">Event data that provides an empty dictionary to be populated with
/// serializable state.</param>
private void NavigationHelper_SaveState(object sender, SaveStateEventArgs e)
{
}
#region NavigationHelper registration
/// <summary>
/// The methods provided in this section are simply used to allow
/// NavigationHelper to respond to the page's navigation methods.
/// <para>
/// Page specific logic should be placed in event handlers for the
/// <see cref="NavigationHelper.LoadState"/>
/// and <see cref="NavigationHelper.SaveState"/>.
/// The navigation parameter is available in the LoadState method
/// in addition to page state preserved during an earlier session.
/// </para>
/// </summary>
/// <param name="e">Provides data for navigation methods and event
/// handlers that cannot cancel the navigation request.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.navigationHelper.OnNavigatedTo(e);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
this.navigationHelper.OnNavigatedFrom(e);
}
#endregion
}
}
And receiving the list in the ViewSubjectGradeVM via the constructor:
namespace LC_Points.ViewModel
{
public class ViewSubjectGradeViewModel
{
public ViewSubjectGradeViewModel()
{
}
/// <summary>
/// Initializes a new instance of the ViewSubjectGradeViewModel class.
/// </summary>
public ViewSubjectGradeViewModel(IEnumerable<ScoreModel> addedSubjectGradePairs)
{
this.AddedSubjectGradePairsCopy = addedSubjectGradePairs;
}
//Property for collection passed from MainViewModel
public IEnumerable<ScoreModel> AddedSubjectGradePairsCopy { get; set; }
}
}
And this is the backing Model for the List being passed from the MainVM to the ViewSubjectGradeVM:
namespace LC_Points.Model
{
public class ScoreModel : INotifyPropertyChanged
{
// The name of the subject.
public string Subject { get; set; }
// The points paired with each grade type.
public int Points { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Try something like this. Of course, your model should comes form some source which you should map to your viewmodel. But this illustrates you the way to set correct datacontext.
var scoremodels = new List<ScoreModel>
{
new ScoreModel {Subject = "Subj1", Points = 6},
new ScoreModel {Subject = "Subj2", Points = 3},
new ScoreModel {Subject = "Subj3", Points = 8},
}
ViewModel = new ViewSubjectGradeViewModel(scoreModels);
this.DataContext = ViewModel;

c# DataBinding does not update Control

I'm playing with databinding on c# compact framework. I develop a simple form with a Textbox and a Label. I want to change the data binded to Textbox (bindModelTextBox) and show these changes by the Label (bindModelLabel), which is binded to the same data. Here is the code:
public partial class CreateShipment : Form {
//simple bean. Just one property: id, a string
private BasicShipmentBean toBindBasicShipment = null;
public CreateShipment() {
InitializeComponent();
BindingSource bsProva = new BindingSource();
toBindBasicShipment = new BasicShipmentBean();
toBindBasicShipment.id = "boo";
bsProva.Add(toBindBasicShipment);
bindModelLabel.DataBindings.Add("Text", bsProva, "id", true, DataSourceUpdateMode.OnPropertyChanged);
bindModelTextBox.DataBindings.Add("Text", bsProva, "id", true, DataSourceUpdateMode.OnPropertyChanged);
bindModelTextBox.LostFocus += textLoseFocus;
}
...
private void textLoseFocus(object sender, System.EventArgs e)
{
System.Diagnostics.Debug.WriteLine("focus lost. "+toBindBasicShipment.id);
}
When textbox loose focus I can see the data is updated in the bean, but, the label still shows bean's original id value. What am I missing?
You need to implement INotifyPropertyChanged on your BasicShipmentBean class. I forgot where exactly I found this originally, but here is an ObservableObject base class that implements INotifyPropertyChanged, that I use for all of my data sources.
public abstract class ObservableObject : INotifyPropertyChanged
{
#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 virtual 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 INotifyPropertyChanged Members
/// <summary>
/// Raises the PropertyChange event for the property specified
/// </summary>
/// <param name="propertyName">Property name to update. Is case-sensitive.</param>
public virtual void RaisePropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
OnPropertyChanged(propertyName);
}
/// <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
}
Then, you need to raise the OnPropertyChanged event in your setter for the id in BasicShipmentBean, e.g.:
private string _id;
public string id
{
get { return _id; }
set
{
if (value != _id)
{
_id = value;
OnPropertyChanged("id");
}
}
}
Data binding in the Compact Framework is a bit more tedious than in WPF, but much of the implementation is pretty similar.

WPF MVVM - How to detect if a View is "Dirty"

I currently have a requirement to notify my application user if any fields have been changed/updated on a View.
For example, if the user changes a date field on the View and then tries to close the View, the application would display a message asking the user to Continue and lose changes or Cancel so that they can click the Save button.
Problem is: How do I detect that any of the data fields changed in the View?
Hope this makes sense, than you in advance, regards,
One approach you can take is to leverage the IChangeTracking and INotifyPropertyChanged interfaces.
If you create an abstract base class that your view models inherit from (ViewModelBase) which implements the IChangeTracking and INotifyPropertyChanged interfaces, you can have your view model base attach to notification of property changes (in effect signaling that the view model has been modified) and which will set the IsChanged property to true to indicate that the view model is 'dirty'.
Using this approach, you are relying on property change notification via data binding to track changes and would reset the change tracking after any commits are made.
In the case you described you could handle the Unloaded or Closing event of your view to inspect the DataContext; and if the DataContext implements IChangeTracking you can use the IsChanged property to determine if any unaccepted changes have been made.
Simple example:
/// <summary>
/// Provides a base class for objects that support property change notification
/// and querying for changes and resetting of the changed status.
/// </summary>
public abstract class ViewModelBase : IChangeTracking, INotifyPropertyChanged
{
//========================================================
// Constructors
//========================================================
#region ViewModelBase()
/// <summary>
/// Initializes a new instance of the <see cref="ViewModelBase"/> class.
/// </summary>
protected ViewModelBase()
{
this.PropertyChanged += new PropertyChangedEventHandler(OnNotifiedOfPropertyChanged);
}
#endregion
//========================================================
// Private Methods
//========================================================
#region OnNotifiedOfPropertyChanged(object sender, PropertyChangedEventArgs e)
/// <summary>
/// Handles the <see cref="INotifyPropertyChanged.PropertyChanged"/> event for this object.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">A <see cref="PropertyChangedEventArgs"/> that contains the event data.</param>
private void OnNotifiedOfPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e != null && !String.Equals(e.PropertyName, "IsChanged", StringComparison.Ordinal))
{
this.IsChanged = true;
}
}
#endregion
//========================================================
// IChangeTracking Implementation
//========================================================
#region IsChanged
/// <summary>
/// Gets the object's changed status.
/// </summary>
/// <value>
/// <see langword="true"/> if the object’s content has changed since the last call to <see cref="AcceptChanges()"/>; otherwise, <see langword="false"/>.
/// The initial value is <see langword="false"/>.
/// </value>
public bool IsChanged
{
get
{
lock (_notifyingObjectIsChangedSyncRoot)
{
return _notifyingObjectIsChanged;
}
}
protected set
{
lock (_notifyingObjectIsChangedSyncRoot)
{
if (!Boolean.Equals(_notifyingObjectIsChanged, value))
{
_notifyingObjectIsChanged = value;
this.OnPropertyChanged("IsChanged");
}
}
}
}
private bool _notifyingObjectIsChanged;
private readonly object _notifyingObjectIsChangedSyncRoot = new Object();
#endregion
#region AcceptChanges()
/// <summary>
/// Resets the object’s state to unchanged by accepting the modifications.
/// </summary>
public void AcceptChanges()
{
this.IsChanged = false;
}
#endregion
//========================================================
// INotifyPropertyChanged Implementation
//========================================================
#region PropertyChanged
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region OnPropertyChanged(PropertyChangedEventArgs e)
/// <summary>
/// Raises the <see cref="INotifyPropertyChanged.PropertyChanged"/> event.
/// </summary>
/// <param name="e">A <see cref="PropertyChangedEventArgs"/> that provides data for the event.</param>
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
#endregion
#region OnPropertyChanged(string propertyName)
/// <summary>
/// Raises the <see cref="INotifyPropertyChanged.PropertyChanged"/> event for the specified <paramref name="propertyName"/>.
/// </summary>
/// <param name="propertyName">The <see cref="MemberInfo.Name"/> of the property whose value has changed.</param>
protected void OnPropertyChanged(string propertyName)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
#endregion
#region OnPropertyChanged(params string[] propertyNames)
/// <summary>
/// Raises the <see cref="INotifyPropertyChanged.PropertyChanged"/> event for the specified <paramref name="propertyNames"/>.
/// </summary>
/// <param name="propertyNames">An <see cref="Array"/> of <see cref="String"/> objects that contains the names of the properties whose values have changed.</param>
/// <exception cref="ArgumentNullException">The <paramref name="propertyNames"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
protected void OnPropertyChanged(params string[] propertyNames)
{
if (propertyNames == null)
{
throw new ArgumentNullException("propertyNames");
}
foreach (var propertyName in propertyNames)
{
this.OnPropertyChanged(propertyName);
}
}
#endregion
}
In MVVM a View is binded to a View-Model which in turn is binded to a Model.
The view can not be dirty, since it's changes are reflected immediately to the View-Model.
If you want changes to be applied to Model only on "OK" or "Accept",
bind View to a View-Model that doesn't apply changes to Model,
until an ApplyCommand or AcceptCommand (that you define and implement) is executed.
(The commands that the View is binded to are implemented by the View-Model.)
Example - VM:
public class MyVM : INotifyPropertyChanged
{
public string MyText
{
get
{
return _MyText;
}
set
{
if (value == _MyText)
return;
_MyText = value;
NotifyPropertyChanged("MyText");
}
}
private string _MyText;
public string MyTextTemp
{
get
{
return _MyTextTemp;
}
set
{
if (value == _MyTextTemp)
return;
_MyTextTemp = value;
NotifyPropertyChanged("MyTextTemp");
NotifyPropertyChanged("IsTextDirty");
}
}
private string _MyTextTemp;
public bool IsTextDirty
{
get
{
return MyText != MyTextTemp;
}
}
public bool IsMyTextBeingEdited
{
get
{
return _IsMyTextBeingEdited;
}
set
{
if (value == _IsMyTextBeingEdited)
return;
_IsMyTextBeingEdited = value;
if (!value)
{
MyText = MyTextTemp;
}
NotifyPropertyChanged("IsMyTextBeingEdited");
}
}
private bool _IsMyTextBeingEdited;
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Example - View:
<Label Content="{Binding MyText}" />
<!-- You can translate the events to commands by using a suitable framework -->
<!-- or use code behind to update a new dependency property as in this example -->
<TextBox
LostFocus="TextBox_LostFocus"
GotFocus="TextBox_GotFocus"
Text="{Binding Path=MyTextTemp, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
/>
Example - view - code behind:
public MainWindow()
{
InitializeComponent();
SetBinding(IsTextBoxFocusedProperty,
new Binding
{
Path = new PropertyPath("IsMyTextBeingEdited"),
Mode = BindingMode.OneWayToSource,
});
}
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
IsTextBoxFocused = false;
}
private void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
IsTextBoxFocused = true;
}
#region IsTextBoxFocused
/// <summary>
/// Gets or Sets IsTextBoxFocused
/// </summary>
public bool IsTextBoxFocused
{
get
{
return (bool)this.GetValue(IsTextBoxFocusedProperty);
}
set
{
this.SetValue(IsTextBoxFocusedProperty, value);
}
}
/// <summary>
/// The backing DependencyProperty behind IsTextBoxFocused
/// </summary>
public static readonly DependencyProperty IsTextBoxFocusedProperty = DependencyProperty.Register(
"IsTextBoxFocused", typeof(bool), typeof(MainWindow), new PropertyMetadata(default(bool)));
#endregion
Idea:  check  entitystate:
Problem is that this refers to the whole VIEW, so when a new participant (refreshes form) is selected before any editing,  the value is also "Modified". After a save, if nothing else changes and we don’t switch participants, the value is  "Unchanged" 
 
 

Categories