MVVM handling the Drag events of MouseDragElementBehavior - c#

This question tells me what to do in words, but I can't figure out how to write the code. :)
I want to do this:
<SomeUIElement>
<i:Interaction.Behaviors>
<ei:MouseDragElementBehavior ConstrainToParentBounds="True">
<i:Interaction.Triggers>
<i:EventTrigger EventName="DragFinished">
<i:InvokeCommandAction Command="{Binding SomeCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ei:MouseDragElementBehavior>
</i:Interaction.Behaviors>
</SomeUIElement>
But as the other question outlines, the EventTrigger doesn't work... I think it's because it wants to find the DragFinished event on the SomeUIElement instead of on the MouseDragElementBehavior. Is that correct?
So I think what I want to do is:
Write a behavior that inherits from MouseDragElementBehavior
Override the OnAttached method
Subscribe to the DragFinished event... but I can't figure out the code to do this bit.
Help please! :)

Here is what I implemented to solve your problem :
public class MouseDragCustomBehavior : MouseDragElementBehavior
{
public static DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(MouseDragCustomBehavior));
public static DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(MouseDragCustomBehavior));
protected override void OnAttached()
{
base.OnAttached();
if (!DesignerProperties.GetIsInDesignMode(this))
{
base.DragFinished += MouseDragCustomBehavior_DragFinished;
}
}
private void MouseDragCustomBehavior_DragFinished(object sender, MouseEventArgs e)
{
var command = this.Command;
var param = this.CommandParameter;
if (command != null && command.CanExecute(param))
{
command.Execute(param);
}
}
protected override void OnDetaching()
{
base.OnDetaching();
base.DragFinished -= MouseDragCustomBehavior_DragFinished;
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
}
And then the XAML to call it like this....
<Interactivity:Interaction.Behaviors>
<Controls:MouseDragCustomBehavior Command="{Binding DoCommand}" />
</Interactivity:Interaction.Behaviors>

Related

Pass event argument to command with Microsoft.Toolkit.Mvvm and Microsoft.Xaml.Behaviors.Wpf

I try to implement the MVVM pattern for this sample (just simple paging part) with Microsoft.Toolkit.Mvvm unfortunately, I failed :( because I'm so noob in WPF also MVVM :))
the primary problem is how can I pass an argument of an event to command with InvokeCommandAction (Microsoft.Xaml.Behaviors.Wpf)? there is limited documentation and wiki I think...
in this scenario, I change this code in MainWindow.xaml :
...
<ui:Frame x:Name="ContentFrame" Navigated="ContentFrame_Navigated" />
</ui:NavigationView>
to :
...
<ui:Frame x:Name="ContentFrame" DataContext="{Binding ContentFrameVM}">
<i:Interaction.Triggers>
<!-- Events -->
<i:EventTrigger EventName="Navigated">
<i:InvokeCommandAction Command="{Binding ContentFrame_NavigatedCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ui:Frame>
and move the code related to this event from MainWindow.xaml.cs :
private void ContentFrame_Navigated(object sender, NavigationEventArgs e)
{
if (e.SourcePageType() == typeof(SettingsPage))
{
NavView.SelectedItem = NavView.SettingsItem;
}
else
{
NavView.SelectedItem = NavView.MenuItems.OfType<NavigationViewItem>().FirstOrDefault(x => GetPageType(x) == e.SourcePageType());
}
}
to MainWindowViewModel.cs and change it to :
class MainWindowViewModel : ObservableObject
{
public IRelayCommand ContentFrame_NavigatedCommand { get; }
private NavigationView _navigationViewVM;
public NavigationView NavigationViewVM
{
get => _navigationViewVM;
set => SetProperty(ref _navigationViewVM, value);
}
private ModernWpf.Controls.Frame _contentFrameVM;
public ModernWpf.Controls.Frame ContentFrameVM
{
get => _contentFrameVM;
set => SetProperty(ref _contentFrameVM, value);
}
public MainWindowViewModel()
{
ContentFrame_NavigatedCommand = new RelayCommand<object>(ContentFrame_Navigated, (o) => { return true; });
}
...
private void ContentFrame_Navigated(object o)
{
NavigationEventArgs e = o as NavigationEventArgs;
if (e.SourcePageType() == typeof(Views.Pages.SettingsPage))
{
NavigationViewVM.SelectedItem = NavigationViewVM.SettingsItem;
}
else
{
NavigationViewVM.SelectedItem = NavigationViewVM.MenuItems.OfType<NavigationViewItem>().FirstOrDefault(x => GetPageType(x) == e.SourcePageType());
}
}
or try this :
public MainWindowViewModel()
{
ContentFrame_NavigatedCommand = new RelayCommand<NavigationEventArgs>(ContentFrame_Navigated);
}
...
private void ContentFrame_Navigated(NavigationEventArgs e)
{
...
}
in debug mode "ContentFrame_Navigated" doesn't fire at all and "ContentFrame_NavigatedCommand" just triggered once at startup (in that time "NavigationViewVM" is null!)
I'm ignoring an obvious issue Isn't that so?
also, it's a Possible duplicate sorry about that but I tried to read all similar questions and ref for days!
Thanks to Jesse's comment... I edit that part of the code but the main problem was ItemInvoked event was not implemented at all! however, I decide to implement SelectionChanged event instead:
MainWindow.xaml :
<ui:NavigationView>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged" SourceObject="{Binding ElementName=NavView}">
<i:InvokeCommandAction Command="{Binding NavVM_SelectionChangedCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
...
in MainWindowViewModel.cs:
private void NavVM_SelectionChanged(NavigationViewSelectionChangedEventArgs args)
{
if (args.IsSettingsSelected)
{
Navigate(typeof(Views.Pages.SettingsPage));
}
else
{
var selectedItem = (NavigationViewItem)args.SelectedItem;
Navigate(selectedItem);
}
}

Binding not working with MVVM in xamarin form. No property, bindable property, or event found for 'ItemSelected',

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}">

How to Bind Drop and PreviewMouseLeftButtonDown Events in ViewModel in WPF

I have to bind Grid Drop Event and PreviewMouseLeftButtonDown event in ViewModel. I have a RelayCommand. But it is done only for passing the object, I have to pass the routed event by using the command and also for MouseButtonEventArgs. my sample code is as below, please give any suggestion for using the routed event args and MouseButtonEventArgs in viewmodel.
<Grid
x:Name="mainGrid"
AllowDrop="True"
Background="#F0F0F0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Drop">
<cmd:EventCommandExecuter Command="{Binding GridDrop}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
<Grid Background="LightBlue" PreviewMouseLeftButtonDown="Grid_PreviewMouseLeftButtonDown">
EventCommandExecuter
public class EventCommandExecuter : TriggerAction<DependencyObject>
{
#region Constructors
public EventCommandExecuter()
: this(CultureInfo.CurrentCulture)
{
}
public EventCommandExecuter(CultureInfo culture)
{
Culture = culture;
}
#endregion
#region Properties
#region Command
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(EventCommandExecuter), new PropertyMetadata(null));
#endregion
#region EventArgsConverterParameter
public object EventArgsConverterParameter
{
get { return (object)GetValue(EventArgsConverterParameterProperty); }
set { SetValue(EventArgsConverterParameterProperty, value); }
}
public static readonly DependencyProperty EventArgsConverterParameterProperty =
DependencyProperty.Register("EventArgsConverterParameter", typeof(object), typeof(EventCommandExecuter), new PropertyMetadata(null));
#endregion
public IValueConverter EventArgsConverter { get; set; }
public CultureInfo Culture { get; set; }
#endregion
protected override void Invoke(object parameter)
{
var cmd = Command;
if (cmd != null)
{
var param = parameter;
if (EventArgsConverter != null)
{
param = EventArgsConverter.Convert(parameter, typeof(object), EventArgsConverterParameter, CultureInfo.InvariantCulture);
}
if (cmd.CanExecute(param))
{
cmd.Execute(param);
}
}
}
}
I want to pass object and RoutedEventArgs like below in viewmodel. Please help
public void Grid_Drop(object sender, RoutedEventArgs e)
{
}
I feel like commands are often overkill for such simple tasks.
You can simply declare your ViewModel in the code behind of your view like so:
public partial class MainWindow : Window
{
private ViewModel _vm;
public ViewModel Vm
{
get { return _vm;}
set
{
_vm = value ;
}
}
//....Constructor here....
}
Then create a public event :
public event RoutedEventHandler OnGridDrop;
and call it in :
public void Grid_Drop(object sender, RoutedEventArgs e)
{
OnGridDrop?.Invoke(sender,e)
}
Now you only need to initialize your ViewModel:
public MainWindow()
{
InitializeComponent();
Vm = new ViewModel();
OnGridDrop += Vm.OnGridDrop;
}
and subscribe a corrsponding handler that you declared in your ViewModel.

WPF Dependency property not firing with binding

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!");
}
}
}

Event trigger issue when parsing xaml using MVVMLight in my WP7 app

I am a bit of a novice when it comes to MVVM and C# in general, but I do not understand why I am getting the following xaml parse exception: AG_E_PARSER_BAD_TYPE
The exception occurs when attempting to parse my event trigger:
<applicationspace:AnViewBase
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:c="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP7">
...and inside my grid:
<Button Name="LoginButton"
Content="Login"
Height="72"
HorizontalAlignment="Left"
Margin="150,229,0,0"
VerticalAlignment="Top"
Width="160">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<c:EventToCommand Command="{Binding LoginCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
The exception occurs at the i:EventTrigger EventName="Click" line.
Does anyone have any insight as to why this is happening? I have seen this used before, and am simply too inexperienced to discern why it isn't working for me.
I am obliged for any help, and thank you for your time.
I did not resolve this issue, but created a work around... I thought it might be helpful to some, so here it is:
I extended the button class by adding a command property to my new "BindableButton"
public class BindableButton : Button
{
public BindableButton()
{
Click += (sender, e) =>
{
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
};
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(BindableButton), new PropertyMetadata(null, CommandChanged));
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(BindableButton), new PropertyMetadata(null));
private static void CommandChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
BindableButton button = source as BindableButton;
button.RegisterCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
}
private void RegisterCommand(ICommand oldCommand, ICommand newCommand)
{
if (oldCommand != null)
oldCommand.CanExecuteChanged -= HandleCanExecuteChanged;
if (newCommand != null)
newCommand.CanExecuteChanged += HandleCanExecuteChanged;
HandleCanExecuteChanged(newCommand, EventArgs.Empty);
}
// Disable button if the command cannot execute
private void HandleCanExecuteChanged(object sender, EventArgs e)
{
if (Command != null)
IsEnabled = Command.CanExecute(CommandParameter);
}
}
Following this, I just bind a command in my xaml:
<b:BindableButton x:Name="LoginButton" Command="{Binding LoginCommand}"></b:BindableButton>

Categories