wpf xaml resource dictionary add custom property - wpf

I want to use the following xaml code for navigation in some pages:
<Button Content="Go to page2">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction PropertyName="Source" TargetObject="{Binding NavigationService, RelativeSource={RelativeSource AncestorType={x:Type Page}, Mode=FindAncestor}}">
<ei:ChangePropertyAction.Value>
<System:Uri>Page2.xaml</System:Uri>
</ei:ChangePropertyAction.Value>
</ei:ChangePropertyAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
So I´m wondering if there is a possibility to outsource the interaction part into a style (in a resource dictionary) and add a custom property like "NavigationUri" where you can directly declare the page to navigate to.
Another idea (which would probably be the better approach) is to create a custom control and inherit from button class.
Anywhere I would prefer a more compact and lean way without code behind.
Please let me know, which is the more suitable solution and how to implement it.

Although there are various simple techniques to make our Behavior Xaml as static resource. But, we need a custom behavior, as we are using a parameter in the form of Page name to navigate to. This variable demands programming.
So, I came up with
a. Custom behavior(NavigationBehavior), and
b. Button subclassing(NavigationButton)
NavigationBehavior
using System;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace WpfApplication1.Navigation
{
public class NavigationBehavior:Behavior<NavigationButton>
{
protected override void OnAttached()
{
AssociatedObject.Click += AssociatedObject_Click;
base.OnAttached();
}
void AssociatedObject_Click(object sender, System.Windows.RoutedEventArgs e)
{
((Page)AssociatedObject.DataContext).NavigationService.Source = new Uri(AssociatedObject.DestinationUri, UriKind.Relative);
}
}
}
NavigationButton
namespace WpfApplication1.Navigation
{
public class NavigationButton : Button
{
NavigationBehavior behavior = new NavigationBehavior();
public NavigationButton()
{
behavior.Attach(this);
}
public string DestinationUri { get; set; }
}
}
Usage :
<nav:NavigationButton Content="Navigate to Page2" DestinationUri="/Navigation/Page2.xaml" />
Important Note
We are using DataContext property in our behavior to get access to the containing page. So, set this.DataContext = this; in the constructor of your all pages.
One can try using a common base class / interface to avoid this.

you cannot have interaction triggers extracted but there is a workaround to get this done
you can create a Button in resources with interaction logic in it, and then where needed you can have a content control with content set to your resource.
something like this --
<Window.Resources>
<Button x:Key="MyButton"
Content="Go to page2">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction PropertyName="Source" TargetObject="{Binding NavigationService, RelativeSource={RelativeSource AncestorType={x:Type Page}, Mode=FindAncestor}}">
<ei:ChangePropertyAction.Value>
<System:Uri>Page2.xaml</System:Uri>
</ei:ChangePropertyAction.Value>
</ei:ChangePropertyAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Window.Resources>
<Grid>
<ContentControl Name="MyLocalButton" Content="{StaticResource MyButton}" />
</Grid>

You should put the navigation logic in a command in each page's view model and bind the buttons' Command properties to those commands, or else give the view models each a NextPage property and bind to that.
And create separate buttons in the views. Define a Style in the resource dictionary to make them all look the same.
Defining a Button as a resource is a bad idea. Among other things, there's only one instance of it and it can have only one visual parent, so when you add it to one view it'll vanish from the last. And you run into ugly problems like this one. You're working against XAML, and XAML is already hard enough when you're working with it.

Related

shifting code of code behind to viewmodel in wpf

Here is my code-behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
foreach (FrameworkElement fe in canvas.Children)
{
double top = (double)fe.GetValue(Canvas.TopProperty);
double left = (double)fe.GetValue(Canvas.LeftProperty);
}
}
A 'canvas' is the name of my Canvas control in XAML. The current code provide me with ability to get the location of images inside the canvas. This is implemented in a code-behind. The question is, how we get the location of images in MVVM manner(using the command to start the process)?
how we get the location of images in MVVM manner(using the command to start the process)?
Commanding is not needed, simply bind each of the needed properties of each of the canvas(es) to the VM. Then when needed extract the target canvas properties needed to acquire the image.
A good solution would be to use XAML behaviors:
<Canvas x:Name="Canvas">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding YourCommand}"
CommandParameter="{Binding ElementName=canvas}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Canvas>
In this manner you will have access to your Canvas in your ViewModel and you can execute the code behind inside the command.

Bind to Datacontext property in button trigger?

I understand my problem however I'm looking for advice on a solution:
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource ButtonStyle}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="MouseOverControl" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
I'm trying to bind to a property inside my datacontext, basically I want to tell the DC when a control in my UI has the mouse over it. I think I'll only need this for two buttons and it doesn't matter which one it's over, therefore I don't need to bother with a complicated solution (I hope).
Problem is at the moment it's looking for Button.MouseOverControl which obviously doesn't exist, I'd like to understand how you might go about accessing the DC instead.
Thanks!
EDIT: So I've attempted to go down the attached property/behavior route, here is what I have so far:
public static class MouseBehaviour
{
public static readonly DependencyProperty MouseOverProperty
= DependencyProperty.RegisterAttached(
"MouseOver",
typeof(bool),
typeof(MouseBehaviour),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
MouseOverBindingPropertyChanged));
public static bool GetMouseOver(DependencyObject obj)
{
return (bool)obj.GetValue(MouseOverProperty);
}
public static void SetMouseOver(DependencyObject obj, bool value)
{
obj.SetValue(MouseOverProperty, value);
}
private static void MouseOverBindingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as FrameworkElement;
if (element != null)
{
// Unsure about this method..
}
}
}
Also I've added this to my button to try and link them, it appears to work:
ex:MouseBehaviour.MouseOver="{Binding MouseOverControl}"
However nothing happens, this is because I think it's working the wrong way around at the moment, so it is expecting my DC property to change but I want it so the MouseOverControl in my DC reflects the value of the IsMouseOver property of my button. Would it be as simple as:
SetMouseOver(element, element.IsMouseOver);
Or something similar?
First thing come to mind is binding IsMouseOver property to MouseOverControl property in viewmodel directly without trigger. Unfortunately that scenario is not supported.
One possible workaround to address that limitation is using event that is raised whenever IsMouseOver property changed to trigger method/command in viewmodel. We can do that using interaction triggers. Since IsMouseOverChanged event doesn't exist, we can use 2 events (MouseEnter and MouseLeave) as alternative.
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<ei:CallMethodAction MethodName="MouseEnter" TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeave">
<ei:CallMethodAction MethodName="MouseLeave" TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Then have corresponding methods in viewmodel :
public void MouseEnter()
{
MouseOverControl = true;
}
public void MouseLeave()
{
MouseOverControl = false;
}
Another possible way is by creating attached behavior for MouseOver so that you can bind it to viewmodel's property as demonstrated in this blog post.
So I ended up solving this by adding my own action to update a property because CallMethodAction is only available in Blend 4 which at the time I'm unable to use.
This question helped me considerably: Setting a property with an EventTrigger
In particular I'd like to direct you to user Neutrino's answer on that page (Here), the only part I needed to change was the XAML implementation:
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<ex:SetPropertyAction PropertyName="MouseOverControl" TargetObject="{Binding}"
PropertyValue="true" />
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<ex:SetPropertyAction PropertyName="MouseOverControl" TargetObject="{Binding}"
PropertyValue="false"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Quick explanation is whenever the mouse enters a button I've added these triggers to, it sets a property in my viewmodel/datacontext to mirror this, perfect! Credit to har07 for providing several alternate solutions which also would have worked in different situations (and if I could figure out attached behaviors!!)

Flexible Close Command

I am searching for a way how to realize a window close command, which is usable in the window's CommandBindings and by other sources, for example a button. I am working with MVVM. So far, I only found solutions that were either meant for use by CommandBindings or with a simple Command featured inside the MVVM. I need to have one central handler for this.
Thanks in advance!
Simply use EventToCommand.
ViewModel:
public ICommand WindowClosing
{
get
{
return new RelayCommand<CancelEventArgs>(
(args) =>{
});
}
}
and in XAML:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<command:EventToCommand Command="{Binding WindowClosing}" />
</i:EventTrigger>
</i:Interaction.Triggers>

How to trigger ViewModel command for a specific button events

How can a command on a ViewModel be invoked by a specific event of a button, such as MouseDoubleClick?
You can use the EventTrigger in the System.Windows.Interactivity namespace, which is part of the so-called Prism framework. If you're just getting started with MVVM, don't care too much for Prism by now, but keep it in mind for later. Anyway, you can steel the EventTrigger
It works like this:
Reference the assembly System.Windows.Interactivity.dll
In XAML, reference the namespace:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Then in your Button or any other control, add a EventTrigger like this:
<Button Content="Button">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding CommandToBindTo}"
CommandParameter="{Binding CommandParameterToBindTo}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
This way, you bind your event to a Command on your DataContext.
Remark
To clarify the usage, here's a kind of real life example including the ViewModel. The fictional requirement is to allow the user to select an item in a list and then perform a command which takes the selected item as a parameter:
<ListBox x:Name="ItemsList" ItemsSource="{Binding Items}" />
<Button Content="Do something with selected item">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding DoSomethingCommand}"
CommandParameter="{Binding SelectedItem,
ElementName=ItemsList}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
And that would be the ViewModel. Note how the parameter to the command is used, in the example with a generic version of a DelegateCommand object as you get it in every MVVM framework (sometimes RelayCommand). This class takes the type of the required parameter as a generic parameter (here ItemViewModel) and requires a method which takes an according parameter (here ExecuteDoSomethingWithItem(ItemViewModel ...)). The rest is WPF magic: The oject to which the CommandParameter property is bound in your XAML will be passed through as the parameter in your Execute(...) function.
public class ViewModel
{
ObservableCollection<ItemViewModel> Items { get; set; }
public ICommand DoSomethingCommand
{
get
{
return _doSomethingCommand ??
(_doSomethingCommand = new DelegateCommand<ItemViewModel>(ExecuteDoSomethingWithItem));
}
}
private DelegateCommand<ItemViewModel> _doSomethingCommand;
private void ExecuteDoSomethingWithItem(ItemViewModel itemToDoSomethingWith)
{
// Do something
}
public ViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
// Fill the collection
}
}
Have fun with learning MVVM, it's worth it.
you can use attached command behaviors
=> http://geekswithblogs.net/HouseOfBilz/archive/2009/08/21/adventures-in-mvvm-ndash-generalized-command-behavior-attachments.aspx
You need to do a lot of pluming yourself if you going to use Command and Event Binding from out of the box WPF. You can gain a lot of just using existing framework such as MVVM Light Toolkit, or Cliburn Micro that already provide command and even binding.

how to use EventToCommand in a ItemContainerStyle?

<ListBox Grid.Row="1" ItemsSource="{Binding Source}" SelectedItem="{Binding SelectedItem,Mode=TwoWay}" DisplayMemberPath="Name">
<ListBox.ItemContainerStyle>
<Style>
<EventSetter Event="ListBoxItem.MouseDoubleClick" Handler="DoubleClick" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
This is how it works now.
What should I do if I want to Bind every ListBoxItem's DoubleClick event to a RelayCommand?
This is the way I am using the MVVMLight EventToCommand feature.
If you have a doubleclick event hook to that. If that is not available take the (preview)mousedown and check the clickCount in the command args. A ClickCount of 2 corresponds to a double click.
Please note: I have my own RelayCommand Implementation. The one from the MVMMLight toolkit might look different.
XAML:
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="MouseDown">
<mvvmLight:EventToCommand PassEventArgsToCommand="True" Command="{Binding MouseDownCommand}"></mvvmLight:EventToCommand>
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
ViewModel:
public ICommand MouseDownCommand
{
get
{
if (_mouseDownCommand == null)
{
_mouseDownCommand = new RelayCommand(x => MouseDown(x as MouseButtonEventArgs));
}
return _mouseDownCommand;
}
}
private void MouseDown(MouseButtonEventArgs e)
{
if (e.ClickCount == 2)
{
// do stuff
}
}
The best way to do this is to just use a normal event handler written in code-behind. If needed this can relay to a method or command on your model or view-model.
Tricks like using an EventToCommand behavior just cost you in terms of more complex XAML and a pretty high risk that you will leak memory. (This happens because EventToCommand listens to the CanExecuteChanged event even when it shouldn't.)

Resources