I've tried to handle the routed event ScrollViewer.ScrollChanged of DataGrid in obvious way:
<i:Interaction.Triggers>
<i:EventTrigger EventName="ScrollViewer.ScrollChanged">
<ei:CallMethodAction MethodName="ScrollChangedHandler" TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
But ScrollChangedHandler did not even fired.
Then, I've found this article about handling events, but I could not figure out what xml namespace(xmlns) is used for mvvmjaco:
<Image Width="360" Height="177" Source="Resources\PlayerArea.png">
<i:Interaction.Triggers>
<mvvmjoy:RoutedEventTrigger RoutedEvent="s:Contacts.ContactDown">
<mvvmjaco:CommandAction Command="{Binding TouchCommand}" />
</mvvmjoy:RoutedEventTrigger>
</i:Interaction.Triggers>
</Image>
mvvmjoy uses this class from the article:
public class RoutedEventTrigger :EventTriggerBase<DependencyObject>
{
RoutedEvent _routedEvent;
//The code omitted for the brevity
}
Basically, I have two questions:
What class or library should I use for mvvmjaco xml namespace?
How I can handle ScrollViewer.ScrollChanged event in my viewModel with its arguments?
I would solve it with the following Attached-Property:
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApplication2
{
public class DataGridExtensions
{
public static readonly DependencyProperty ScrollChangedCommandProperty = DependencyProperty.RegisterAttached(
"ScrollChangedCommand", typeof(ICommand), typeof(DataGridExtensions),
new PropertyMetadata(default(ICommand), OnScrollChangedCommandChanged));
private static void OnScrollChangedCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DataGrid dataGrid = d as DataGrid;
if (dataGrid == null)
return;
if (e.NewValue != null)
{
dataGrid.Loaded += DataGridOnLoaded;
}
else if (e.OldValue != null)
{
dataGrid.Loaded -= DataGridOnLoaded;
}
}
private static void DataGridOnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
DataGrid dataGrid = sender as DataGrid;
if (dataGrid == null)
return;
ScrollViewer scrollViewer = UIHelper.FindChildren<ScrollViewer>(dataGrid).FirstOrDefault();
if (scrollViewer != null)
{
scrollViewer.ScrollChanged += ScrollViewerOnScrollChanged;
}
}
private static void ScrollViewerOnScrollChanged(object sender, ScrollChangedEventArgs e)
{
DataGrid dataGrid = UIHelper.FindParent<DataGrid>(sender as ScrollViewer);
if (dataGrid != null)
{
ICommand command = GetScrollChangedCommand(dataGrid);
command.Execute(e);
}
}
public static void SetScrollChangedCommand(DependencyObject element, ICommand value)
{
element.SetValue(ScrollChangedCommandProperty, value);
}
public static ICommand GetScrollChangedCommand(DependencyObject element)
{
return (ICommand)element.GetValue(ScrollChangedCommandProperty);
}
}
}
The class UIHelper looks like:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
namespace WpfApplication2
{
internal static class UIHelper
{
internal static IList<T> FindChildren<T>(DependencyObject element) where T : FrameworkElement
{
List<T> retval = new List<T>();
for (int counter = 0; counter < VisualTreeHelper.GetChildrenCount(element); counter++)
{
FrameworkElement toadd = VisualTreeHelper.GetChild(element, counter) as FrameworkElement;
if (toadd != null)
{
T correctlyTyped = toadd as T;
if (correctlyTyped != null)
{
retval.Add(correctlyTyped);
}
else
{
retval.AddRange(FindChildren<T>(toadd));
}
}
}
return retval;
}
internal static T FindParent<T>(DependencyObject element) where T : FrameworkElement
{
FrameworkElement parent = VisualTreeHelper.GetParent(element) as FrameworkElement;
while (parent != null)
{
T correctlyTyped = parent as T;
if (correctlyTyped != null)
{
return correctlyTyped;
}
return FindParent<T>(parent);
}
return null;
}
}
}
Then you can write in the definition of your DataGrid:
<DataGrid ItemsSource="{Binding MySource}" extensionsNamespace:DataGridExtensions.ScrollChangedCommand="{Binding ScrollCommand}"/>
And in your ViewModel you have an ICommand which looks like:
private ICommand scrollCommand;
public ICommand ScrollCommand
{
get { return scrollCommand ?? (scrollCommand = new RelayCommand(Scroll)); }
}
private void Scroll(object parameter)
{
ScrollChangedEventArgs scrollChangedEventArgs = parameter as ScrollChangedEventArgs;
if (scrollChangedEventArgs != null)
{
}
}
For the first your question(special thanks to Andy ONeill and Magnus Montin ):
MVVMJaco is xmlns:mvvmjaco="galasoft.ch/mvvmlight"
And the libraries needed are:
GalaSoft.MVVmLight
GalaSoft.MVVmLight.Extras
GalaSoft.MVVmLight.Platform
Seems that mvvmjaco:CommandAction is an action to invoke command from your ViewModel. You can use i:InvokeCommandAction as substitute.
You can use RoutedEventTrigger from the article you've linked to handle scroll changed event.
XAML:
<Window x:Class="ScrollChangedTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ScrollChangedTest"
mc:Ignorable="d"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding DataItems}" AutoGenerateColumns="True">
<i:Interaction.Triggers>
<local:RoutedEventTrigger RoutedEvent="ScrollViewer.ScrollChanged">
<local:CustomCommandAction Command="{Binding ScrollCommand}" />
</local:RoutedEventTrigger>
</i:Interaction.Triggers>
</DataGrid>
<TextBlock Grid.Row="1" Text="{Binding ScrollData}" />
</Grid>
</Window>
ViewModel & stuff:
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
ObservableCollection<DataItem> _dataItems = new ObservableCollection<DataItem>();
public ObservableCollection<DataItem> DataItems { get { return _dataItems; } }
private TestCommand _scrollCommand;
public ICommand ScrollCommand { get { return _scrollCommand; } }
public string ScrollData { get; set; }
public MainWindowViewModel()
{
for (int i = 0; i < 100; i++)
{
_dataItems.Add(new DataItem() { Field1 = i.ToString(), Field2 = (i * 2).ToString(), Field3 = (i * 3).ToString() });
}
_scrollCommand = new TestCommand(OnScroll);
}
private void OnScroll(object param)
{
ScrollChangedEventArgs args = param as ScrollChangedEventArgs;
if (args != null)
{
ScrollData = $"VerticalChange = {args.VerticalChange}; VerticalOffset = {args.VerticalOffset}";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ScrollData)));
}
}
}
public class DataItem
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
}
public class TestCommand : ICommand
{
private Action<object> _execute;
public event EventHandler CanExecuteChanged;
public TestCommand(Action<object> execute)
{
_execute = execute;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
RoutedEventTrigger from the article:
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("Routed Event trigger can only be associated to framework elements");
}
if (RoutedEvent != null)
{
associatedElement.AddHandler(RoutedEvent, new RoutedEventHandler(this.OnRoutedEvent));
}
}
void OnRoutedEvent(object sender, RoutedEventArgs args)
{
base.OnEvent(args);
}
protected override string GetEventName()
{
return RoutedEvent.Name;
}
}
CustomCommandAction class for passing args to the command
public sealed class CustomCommandAction : TriggerAction<DependencyObject>
{
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(CustomCommandAction), null);
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof(ICommand), typeof(CustomCommandAction), null);
public ICommand Command
{
get
{
return (ICommand)this.GetValue(CommandProperty);
}
set
{
this.SetValue(CommandProperty, value);
}
}
public object CommandParameter
{
get
{
return this.GetValue(CommandParameterProperty);
}
set
{
this.SetValue(CommandParameterProperty, value);
}
}
protected override void Invoke(object parameter)
{
if (this.AssociatedObject != null)
{
ICommand command = this.Command;
if (command != null)
{
if (this.CommandParameter != null)
{
if (command.CanExecute(this.CommandParameter))
{
command.Execute(this.CommandParameter);
}
}
else
{
if (command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
}
}
}
}
I don't know mvvmjaco but I have some hint for 2nd question. You cann't add handler to ScrollChanged directly from DataGrid. You can extend DataGrid and add custom event there. For example:
public class ExtendedDataGrid : DataGrid
{
public event ScrollChangedEventHandler ScrollChanged;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var scrollViewer = (ScrollViewer)GetTemplateChild("DG_ScrollViewer");
scrollViewer.ScrollChanged += OnScrollChanged;
}
protected virtual void OnScrollChanged(ScrollChangedEventArgs e)
{
ScrollChangedEventHandler handler = ScrollChanged;
if (handler != null)
{
handler(this, e);
}
}
private void OnScrollChanged(object sender, ScrollChangedEventArgs e)
{
OnScrollChanged(e);
}
}
XAML:
<local:ExtendedDataGrid>
<i:Interaction.Triggers>
<i:EventTrigger EventName="ScrollChanged">
<ei:CallMethodAction TargetObject="{Binding}"
MethodName="OnScrollChanged" />
</i:EventTrigger>
</i:Interaction.Triggers>
</local:ExtendedDataGrid>
handler:
public void OnScrollChanged(object sender, ScrollChangedEventArgs e)
{
}
Related
I am trying to bind my ListView ItemSelected with with my ViewModel using MVVM in xamarin. For some I am getting a compile time error " No property, bindable property, or event found for 'ItemSelected', or mismatching type between value and property. Scheduler" I have binded my ViewModel in the UI code Behind. Below is my code
UI Code Behind
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ClientPage : ContentPage
{
private ClientViewModel viewModel {get;}
public ClientPage()
{
InitializeComponent();
BindingContext = viewModel = new ClientViewModel();
}
}
ViewModel
public class ClientViewModel : BaseViewModel
{
public ObservableCollection<Client> Clients { get; set; }
private Client _SelectedItem { get; set; }
public Client SelectedClient
{
get
{
return _SelectedItem;
}
set
{
_SelectedItem = value;
Task.Run(async () => await ShowClientDetailModal(_SelectedItem));
}
}
}
UI
<StackLayout>
<ListView x:Name="ClientListView"
ItemsSource="{Binding Clients}"
VerticalOptions="FillAndExpand"
HasUnevenRows="true"
RefreshCommand="{Binding LoadClientsCommand}"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding IsBusy, Mode=OneWay}"
CachingStrategy="RecycleElement"
ItemSelected="{Binding SelectedClient}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Clicked="OnDelete_Clicked" Text="Delete" CommandParameter="{Binding .}"/>
</ViewCell.ContextActions>
<StackLayout Padding="10">
<Label Text="{Binding FullName}"
d:Text="{Binding .}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
ItemSelected is an event, not a bindable property what you are looking for is a behaviour that converts your Event to a Command
If you check the Microsoft documents you can find one here: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/behaviors/reusable/event-to-command-behavior
Behavior:
public class EventToCommandBehavior : BehaviorBase<View>
{
Delegate eventHandler;
public static readonly BindableProperty EventNameProperty = BindableProperty.Create ("EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged);
public static readonly BindableProperty CommandProperty = BindableProperty.Create ("Command", typeof(ICommand), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create ("CommandParameter", typeof(object), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty InputConverterProperty = BindableProperty.Create ("Converter", typeof(IValueConverter), typeof(EventToCommandBehavior), null);
public string EventName {
get { return (string)GetValue (EventNameProperty); }
set { SetValue (EventNameProperty, value); }
}
public ICommand Command {
get { return (ICommand)GetValue (CommandProperty); }
set { SetValue (CommandProperty, value); }
}
public object CommandParameter {
get { return GetValue (CommandParameterProperty); }
set { SetValue (CommandParameterProperty, value); }
}
public IValueConverter Converter {
get { return (IValueConverter)GetValue (InputConverterProperty); }
set { SetValue (InputConverterProperty, value); }
}
protected override void OnAttachedTo (View bindable)
{
base.OnAttachedTo (bindable);
RegisterEvent (EventName);
}
protected override void OnDetachingFrom (View bindable)
{
DeregisterEvent (EventName);
base.OnDetachingFrom (bindable);
}
void RegisterEvent (string name)
{
if (string.IsNullOrWhiteSpace (name)) {
return;
}
EventInfo eventInfo = AssociatedObject.GetType ().GetRuntimeEvent (name);
if (eventInfo == null) {
throw new ArgumentException (string.Format ("EventToCommandBehavior: Can't register the '{0}' event.", EventName));
}
MethodInfo methodInfo = typeof(EventToCommandBehavior).GetTypeInfo ().GetDeclaredMethod ("OnEvent");
eventHandler = methodInfo.CreateDelegate (eventInfo.EventHandlerType, this);
eventInfo.AddEventHandler (AssociatedObject, eventHandler);
}
void DeregisterEvent (string name)
{
if (string.IsNullOrWhiteSpace (name)) {
return;
}
if (eventHandler == null) {
return;
}
EventInfo eventInfo = AssociatedObject.GetType ().GetRuntimeEvent (name);
if (eventInfo == null) {
throw new ArgumentException (string.Format ("EventToCommandBehavior: Can't de-register the '{0}' event.", EventName));
}
eventInfo.RemoveEventHandler (AssociatedObject, eventHandler);
eventHandler = null;
}
void OnEvent (object sender, object eventArgs)
{
if (Command == null) {
return;
}
object resolvedParameter;
if (CommandParameter != null) {
resolvedParameter = CommandParameter;
} else if (Converter != null) {
resolvedParameter = Converter.Convert (eventArgs, typeof(object), null, null);
} else {
resolvedParameter = eventArgs;
}
if (Command.CanExecute (resolvedParameter)) {
Command.Execute (resolvedParameter);
}
}
static void OnEventNameChanged (BindableObject bindable, object oldValue, object newValue)
{
var behavior = (EventToCommandBehavior)bindable;
if (behavior.AssociatedObject == null) {
return;
}
string oldEventName = (string)oldValue;
string newEventName = (string)newValue;
behavior.DeregisterEvent (oldEventName);
behavior.RegisterEvent (newEventName);
}
}
Also for this to work you will need the BehaviorBase class which can be found below:
public class BehaviorBase<T> : Behavior<T> where T : BindableObject
{
public T AssociatedObject { get; private set; }
protected override void OnAttachedTo (T bindable)
{
base.OnAttachedTo (bindable);
AssociatedObject = bindable;
if (bindable.BindingContext != null) {
BindingContext = bindable.BindingContext;
}
bindable.BindingContextChanged += OnBindingContextChanged;
}
protected override void OnDetachingFrom (T bindable)
{
base.OnDetachingFrom (bindable);
bindable.BindingContextChanged -= OnBindingContextChanged;
AssociatedObject = null;
}
void OnBindingContextChanged (object sender, EventArgs e)
{
OnBindingContextChanged ();
}
protected override void OnBindingContextChanged ()
{
base.OnBindingContextChanged ();
BindingContext = AssociatedObject.BindingContext;
}
}
Usage:
<ListView.Behaviors>
<local:EventToCommandBehavior EventName="ItemSelected" Command="{Binding SelectedClient}" />
</ListView.Behaviors>
Goodluck feel free to get back if you have questions
If you are trying to keep the selected item of ListView in your ViewModel, then use the SelectedItem property in the ListView like below,
<ListView x:Name="ClientListView"
SelectedItem="{Binding SelectedClient}">
I have a class:
public class CommandHamburgerMenu : FrameworkElement, ICommand
{
public HamburgerMenuItem Item
{
get { return (HamburgerMenuItem)GetValue(ItemProperty); }
set { SetValue(ItemProperty, value); }
}
public static readonly DependencyProperty ItemProperty =
DependencyProperty.Register("Item", typeof(HamburgerMenuItem), typeof(CommandHamburgerMenu), new UIPropertyMetadata(null, new PropertyChangedCallback(ItemChanged)));
private static void ItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandHamburgerMenu commandHamburgerMenu = (CommandHamburgerMenu)d;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
if (this.Item != null)
{
if (this.Item == MainWindow.Instance.itemHome) MessageBox.Show("Home item");
else if (this.Item == MainWindow.Instance.itemSearch) MessageBox.Show("Search item");
else MessageBox.Show("Other");
}
else
{
MessageBox.Show("Item is null!");
}
}
}
and the XAML code:
<HamburgerMenu:HamburgerMenu MenuIconColor="Black" SelectionIndicatorColor="Black" MenuItemForeground="Black" HorizontalAlignment="Left">
<HamburgerMenu:HamburgerMenu.Background>
<SolidColorBrush Color="{Binding Source={x:Static properties:Settings.Default}, Path=ColorHighlight, Mode=TwoWay}" />
</HamburgerMenu:HamburgerMenu.Background>
<HamburgerMenu:HamburgerMenuItem x:FieldModifier="public" x:Name="itemHome" Icon="Assets/home.png" Text="Home">
<HamburgerMenu:HamburgerMenuItem.SelectionCommand>
<commands:CommandHamburgerMenu Tag="{Binding ElementName=itemHome, Path=Text}" />
</HamburgerMenu:HamburgerMenuItem.SelectionCommand>
</HamburgerMenu:HamburgerMenuItem>
<HamburgerMenu:HamburgerMenuItem x:FieldModifier="public" x:Name="itemSearch" Icon="Assets/search.png" Text="Search">
<HamburgerMenu:HamburgerMenuItem.SelectionCommand>
<commands:CommandHamburgerMenu Tag="{Binding ElementName=itemHome, Path=Text}" />
</HamburgerMenu:HamburgerMenuItem.SelectionCommand>
</HamburgerMenu:HamburgerMenuItem>
<HamburgerMenu:HamburgerMenuItem x:FieldModifier="public" x:Name="itemFavorite" Icon="Assets/favorite.png" Text="Likes"/>
<HamburgerMenu:HamburgerMenuItem x:FieldModifier="public" x:Name="itemList" Icon="Assets/list.png" Text="Lists"/>
<HamburgerMenu:HamburgerMenuItem x:FieldModifier="public" x:Name="itemPerson" Icon="Assets/person.png" Text="Profile"/>
</HamburgerMenu:HamburgerMenu>
HamburgerMenu is a control taken from here, property SelectionCommand is a dependency property of ICommand type, it's firing by click on hamburger menu item. But when app starts, Item property is null, and ItemChanged not firing. It fires only after manually setting Item property from code behind. Why?
The PropertyChangedCallback of a dependency property is only called when the value of the dependency property is actually changed.
It is not called once initially for the default value of the dependency property.
But you could of course call the method yourself, for example in the constructor of the class:
public class CommandHamburgerMenu : FrameworkElement, ICommand
{
public CommandHamburgerMenu()
{
ItemChanged(this, new DependencyPropertyChangedEventArgs(CommandHamburgerMenu.ItemProperty, null, null));
}
public HamburgerMenuItem Item
{
get { return (HamburgerMenuItem)GetValue(ItemProperty); }
set { SetValue(ItemProperty, value); }
}
public static readonly DependencyProperty ItemProperty =
DependencyProperty.Register("Item", typeof(HamburgerMenuItem), typeof(CommandHamburgerMenu), new UIPropertyMetadata(null, new PropertyChangedCallback(ItemChanged)));
private static void ItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandHamburgerMenu commandHamburgerMenu = (CommandHamburgerMenu)d;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
if (this.Item != null)
{
if (this.Item == MainWindow.Instance.itemHome) MessageBox.Show("Home item");
else if (this.Item == MainWindow.Instance.itemSearch) MessageBox.Show("Search item");
else MessageBox.Show("Other");
}
else
{
MessageBox.Show("Item is null!");
}
}
}
I'm a newbie so excuse my question if it's too fade or if it's unclear.
any way, In my UI (WPF), i have a ListView that i created containing an observable collection of Type Collection = new ObservableCollection<type> and i have two Buttons "Add" & "Delete" I want to do this:
1-Whenever i select an item from my ListView in the UI(just click on it) , and click the "Add" button, the item is stored in a List called Scenario (Scenario = new List<type>).
2- Whenever i click the "Delete" button the Scenario list becomes empty.
I've tried something out but it doesn't work like it should, i can only add one item to the list Scenario and then it is blocked (when debugging) in
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
Can someone tell me why? and how to fix it?
As for the "Delete" Button i didn't get to it yet because the other one doesn't work properly.
if you can propose a new solution or a solution for this problem i would be so thankful.
This is what i've done so far.
This is the code in the MainWindowModel :
private ObservableCollection<Type> _collection,_scenario;
public MainWindowModel()
{
Collection = new ObservableCollection<type>();
Scenario=new ObservableCollection<Type>();
DeleteCommand = new RelayCommand(o => DeleteExecute());
AddTypeCommand = new RelayCommand(o => AddTypeExecute());
}
private Type _isSelected;
public Type IsSelected;
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
RaisePropertyChanged(nameof(IsSelected));
}
}
}
public ICommand DeleteCommand
{
get;
private set;
}
private RelayCommand _addTypeCommand;
public ICommand AddTypeCommand
{
get
{
if (_addTypeCommand == null)
{
_addTypeCommand = new RelayCommand(o => AddTypeExecute());
}
return _addTypeCommand;
}
set { }
}
private void DeleteExecute()
{
Scenario.Clear(); // Would this Work ?
}
private bool CanExecuteAddTypeCommand()
{
return true;
}
private void AddTypeExecute()
{
if (IsSelected != null)
{
Scenario.Add(IsSelected);
}
}
public ObservableCollection<Type> collection
{
get { return _collection; }
set { SetPropertyAndFireEvent(ref _collection, value); }
}
public ObservableCollection<Type> Scenario
{
get { return _scenario; }
set { SetPropertyAndFireEvent(ref _scenario, value); }
}
as for the MainWindowModel
<Window.DataContext>
<viewModels:MainWindowModel />
</Window.DataContext>
<Grid>
<ListView Grid.Row="2"
Grid.Column="0"
ItemsSource="{Binding Collection}"
SelectedItem="{Binding IsSelected}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Command="{Binding AddTypeCommand}"
Width="100"
Height="100"
Content="Add"
Grid.Row="0"
Grid.Column="2"/>
<Button Command="{Binding DeleteCommand}"
Content="Delete"
Width="100"
Height="100"
Grid.Row="2"
Grid.Column="2" />
</Grid>
As for the RelayCommand.cs
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
//Notifies the Button bounded to the ICommand that the value returned by CanExecute has changed
public event EventHandler CanExecuteChanged
{
//raised whenever the commandmanager thinks that something has changed that will affect the ability of commands to execute
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
Try passing selectedItem as parameter for command,you dont pass anything and try to add...
name your ListView:
<ListView x:Name="listView"
and pass selectedItem as commandParameter
<Button Command="{Binding AddTypeCommand}"
CommandParameter="{Binding ElementName=listView, Path=SelectedItem}"
Width="100"
Height="100"
Content="Add"
Grid.Row="0"
Grid.Column="2" />
and then do your logic for adding, now you have parameter to add to your list.
EDIT: Here is some code that works, as i have understand that u need something like this.
ViewModel _> where all collection and command are created:
public class TestVM : INotifyPropertyChanged
{
public TestVM()
{
ListOne = new ObservableCollection<string>()
{
"str1","str2","str3"
};
// command
AddTypeCommand = new RelayCommand(OnAddExecute);
DeleteTypeCommand = new RelayCommand(OnDeleteExecuted);
}
private void OnDeleteExecuted()
{
ListTwo.Clear();
}
private void OnAddExecute()
{
if (SelectedItem != null)
{
ListTwo.Add(SelectedItem);
}
}
private string _selectedItem;
public string SelectedItem
{
get { return _selectedItem; }
set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnPropertyChanged();
}
}
}
private ObservableCollection<string> _listOne;
public ObservableCollection<string> ListOne
{
get
{
return _listOne;
}
set
{
if (_listOne != value)
{
_listOne = value;
OnPropertyChanged();
}
}
}
public ObservableCollection<string> ListTwo { get; set; } = new ObservableCollection<string>();
public RelayCommand AddTypeCommand { get; private set; }
public RelayCommand DeleteTypeCommand { get; private set; }
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
RellayCommand how i implement it:
public class RelayCommand : ICommand
{
private Action _executeMethod;
private Func<bool> _canExecuteMethod;
#region RelayCommand ctor
public RelayCommand(Action executeMethod)
{
_executeMethod = executeMethod;
}
public RelayCommand(Action executeMethod, Func<bool> canExecuteMethod)
{
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
}
#endregion
public void RaiseCanExecuteChanged()
{
CanExecuteChanged(this, EventArgs.Empty);
}
#region ICommand Members
bool ICommand.CanExecute(object parameter)
{
if (_canExecuteMethod != null)
return _canExecuteMethod();
if (_executeMethod != null)
return true;
return false;
}
void ICommand.Execute(object parameter)
{
if (_executeMethod != null)
_executeMethod();
}
public event EventHandler CanExecuteChanged = delegate { };
#endregion
}
//--------------------------------------------------------------------------------------------
public class RelayCommand<T> : ICommand
{
private Action<T> _executeMethod;
private Func<T, bool> _canExecuteMethod;
#region RelayCommand ctor
public RelayCommand(Action<T> executeMethod)
{
_executeMethod = executeMethod;
}
public RelayCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
{
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
}
#endregion
public void RaiseCanExecuteChanged()
{
CanExecuteChanged(this, EventArgs.Empty);
}
#region ICommand Members
bool ICommand.CanExecute(object parameter)
{
var Tparam = (T)parameter;
if (_canExecuteMethod != null)
return _canExecuteMethod(Tparam);
if (_executeMethod != null)
return true;
return false;
}
void ICommand.Execute(object parameter)
{
if (_executeMethod != null)
_executeMethod((T)parameter);
}
public event EventHandler CanExecuteChanged = delegate { };
#endregion
}
And MainWindow.xaml just to show purpose. Selecting on one item in 1rst list and pressing button Add will add it to second ListView. DeleteButton will clear second list.
<Window x:Class="WpfApp5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp5"
mc:Ignorable="d"
Title="MainWindow"
Height="350"
Width="525">
<Window.DataContext>
<local:TestVM />
</Window.DataContext>
<Grid>
<ListView x:Name="listViewOne"
ItemsSource="{Binding ListOne}"
SelectedItem="{Binding SelectedItem,Mode=TwoWay}"
Width="100"
Height="200"
Margin="17,17,400,105" />
<ListView x:Name="listViewTwo"
ItemsSource="{Binding ListTwo}"
Width="100"
Height="200"
Margin="339,17,78,105" />
<Button Command="{Binding AddTypeCommand}"
Content="Add"
Grid.Row="0"
Margin="208,111,198,178" />
<Button Command="{Binding DeleteTypeCommand}"
Content="Delete"
Grid.Row="0"
Margin="208,157,198,132" />
</Grid>
</Window>
I have simulated the scenario with a simple example where window has one textbox and a buton beside it. The button gets activated after the value on textbox goes above 10000. But the button is not getting enabled.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="150" Width="225">
<Grid>
<WrapPanel>
<TextBox Text="{Binding X}" Width="100"/>
<Button Command="{Binding ButtonCommand}" CommandParameter="{Binding}" Width="100"/>
</WrapPanel>
</Grid>
public partial class MainWindow : Window
{
private ViewModel vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = vm;
}
protected override void OnContentRendered(EventArgs e)
{
Task.Run(new Action(() =>
{
int c = 0;
while (true)
{
vm.X = c++;
}
}));
base.OnContentRendered(e);
}
}
public class ViewModel : INotifyPropertyChanged
{
int x;
public int X
{
get { return x; }
set
{
if (x != value)
{
x = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("X"));
}
}
}
}
ICommand c = new MyCommand();
public ICommand ButtonCommand
{
get
{
return c;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class MyCommand : ICommand
{
public bool CanExecute(object parameter)
{
if (parameter != null && (parameter as ViewModel).X > 10000)
{
return true;
}
return false;
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
throw new NotImplementedException();
}
}
You need to have the following...
while (true)
{
vm.X = c++;
CommandManager.InvalidateRequerySuggested();
}
You have to raise the event CanExecuteChanged at any point you expect the CanExecute method
output will be changed
so for example you can add
CanExecuteChanged ();
vm.X = c++;
Here's a simple way to implement the ICommand
public class MyCommand : ICommand
{
private bool _CanExecute = true;
public bool CanExecute(object parameter)
{
return _CanExecute;
}
public void Execute(object parameter)
{
if(parameter!=null){
_CanExecute = false;
//do your thing here....
_CanExecute = true;
}
}
Purists won't like this pattern but... who cares about all the non-sense of hooking up an tearing down event handlers? Bottom line is the command can be executed or not regardless of re-query suggested.
I need Model to notify ViewModel if any property is changed, because I need to collect the changed model instances in a collection for further processing, also to enable and disable command buttons in the viewmodel.
So I've used ModelBase abstract class and added HasChanges property which I can test against in the viewmodel and catch the changed models.But it is not working and I don't know what i'm missing.
public abstract class ModelBase : INotifyPropertyChanged
{
protected ModelBase()
{
}
private bool _hasChanges;
public bool HasChanges
{
get
{
return _hasChanges;
}
set
{
if (_hasChanges != value)
{
_hasChanges = value;
RaisePropertyChanged("HasChanges");
}
}
}
protected void RaisePropertyChanged(string propertyName)
{
HasChanges = true;
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
The Model is wrapped inside the ViewModel and bound to the View which is a DataGrid:
private Model_selectedModel;
public Mode SelectedModel
{
get
{
return _selectedModel;
}
set
{
if (_selectedModel != value)
{
_selectedModel = value;
NotifyPropertyChanged("SelectedModel");
}
}
}
Thanks for valuable help.
I tested your class and it's okay. I think the point is a typo here:
private Model_selectedModel;
public Mode SelectedModel
{
get
{
return _selectedModel;
}
set
{
if (_selectedModel != value)
{
_selectedModel = value;
NotifyPropertyChanged("SelectedModel");
}
}
}
There should be RaisePropertyChanged instead of NotifyPropertyChanged.
Below it's my test:
XAML
<Window x:Class="TestUpdatePropertyChanged.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:TestUpdatePropertyChanged"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<this:TestViewModel />
</Window.DataContext>
<Grid>
<TextBox Width="100" Height="25" Text="{Binding Path=TestString, UpdateSourceTrigger=PropertyChanged}" />
<Button Width="100" Height="30" VerticalAlignment="Top" Content="Click" Click="Button_Click" />
</Grid>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var testData = this.DataContext as TestViewModel;
testData.TestString = "Yay, it's change!";
if (testData.HasChanges == true)
{
MessageBox.Show("It's Change!");
}
}
}
public class TestViewModel : ModelBase
{
private string _testString = "test";
public string TestString
{
get { return _testString; }
set
{
if (_testString != value)
{
_testString = value;
RaisePropertyChanged("TestString");
}
}
}
}
public abstract class ModelBase : INotifyPropertyChanged
{
protected ModelBase()
{
}
private bool _hasChanges;
public bool HasChanges
{
get
{
return _hasChanges;
}
set
{
if (_hasChanges != value)
{
_hasChanges = value;
RaisePropertyChanged("HasChanges");
}
}
}
protected void RaisePropertyChanged(string propertyName)
{
HasChanges = true;
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}