How to bind a CustomControl to a CustomCommand? - wpf

WPF .NET 4.6
In the below code, clicking on the menu item will activate the command and correctly display:
"InkAndGesture command executed"
It is my understanding that the RoutedUICommand will travel up and down the visual tree. So how can the ProgressNoteEditor (a custom control contained within the ItemsControl) listen and act upon the custom command? (There are many instantiations of the ProgressNoteEditor) ???
Note: I need ALL instances of ProgressNoteEditor to respond, not just one, so CommandTarget is no use. Do commands only bubble up?
TIA.
I have a CustomControl (ProgressNoteEditor) which is used from the MainWindow as:
<ItemsControl x:Name="ProgressNote" Grid.Column="1" Grid.Row="1" ItemsSource="{Binding WritingLayer.ProgressNote}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<pn:ProgressNoteEditor LineCount="{Binding LineCount}"
Background="{Binding Background}"
Vocabulary="{Binding Vocabulary}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
From the Menu in the MainWindow, I have added a custom command as:
<MenuItem Header="Ink And Getsures" Command="pn:NotePadCommands.InkAndGesture"/>
The code-behind does:
private void NewProgressNoteView_Loaded(object sender, RoutedEventArgs e)
{
CommandBindings.Add(
new CommandBinding(NotePadCommands.InkAndGesture, NotePadCommands.InkAndGesture_Executed, NotePadCommands.InkAndGesture_CanExecute));
}
For the moment, the CustomCommand is defined in its own class as:
namespace NotePad
{
public static class NotePadCommands
{
// Allow InkCanvas controls to use Gestures with Ink.
private static RoutedUICommand _InkAndGesture;
static NotePadCommands()
{
_InkAndGesture = new RoutedUICommand("Allow Gestures with Ink","InkAndGesture", typeof(NotePadCommands));
}
// Command: InkAndGesture
public static RoutedUICommand InkAndGesture
{
get { return _InkAndGesture; }
}
public static void InkAndGesture_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("InkAndGesture command executed");
}
public static void InkAndGesture_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
}
}

Do commands only bubble up?
A RoutedCommand searches the visual tree from the focused element and up for an element that has a matching CommandBinding and then executes the Execute delegate for this particular CommandBinding.
So the CommandBinding of your ProgressNoteEditor element won't be found the MenuItem invokes the command, because it is not a visual ancestor of the MenuItem.

Related

Prism and Ribbon: Crossing regions with commands

I am building a composite WPF application with Prism and using the Ribbon Library. One thing I am having a very difficult time with is with region-crossing commands.
For example, I have my ribbon in my 'RibbonRegion' and a grid view in my 'MainRegion'. Lets say I want a button in my ribbon to pop up a message box with the currently selected item in the grid view, how do I do this?
The easy way is using EventAggregator but I fear that if I have a bunch of subscribers hooked up just for button clicks I am just asking for memory leak issues.
Is there a way to have a cross-region command, so that clicking a button in my 'RibbonRegion' will get the selected item in the grid view and pop up a message box with that value?
You can use a System.Windows.Input.RoutedUICommand
First you need to declare your Command as below:
public static class Commands
{
public static readonly RoutedUICommand TestCommand = new RoutedUICommand("Test Command",
"Test Command", typeof(Commands));
}
Then in your RibbonRegion xaml:
<my:RibbonButton Command="{x:Static cmd:Commands.TestCommand}" ...
Then in your MainRegion xaml:
<UserControl.CommandBindings>
<CommandBinding CanExecute="OnTestCanExecute"
Command="{x:Static cmd:Commands.TestCommand}"
Executed="OnTestExecute" />
</UserControl.CommandBindings>
Then in your xaml.cs:
public void OnTestRouteCanExecute(object sender, System.Windows.Input.CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
public void OnTestRouteExecute(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
// do some stuff here
}

Why doesn't this Binding work

I have a 3rd party SplitButton control that exposes some DropDownContent and a boolean IsOpen dp to control whether the drop down content is shown or not.
In the case the DropDownContent is a StackPanel with several Buttons, each of which is bound to a command in the view model. In addition to executing that command, clicking the button needs to close the open DropDown content, which I am doing with the AttachedBehavior below.
But my binding, which simple needs to get a reference to the ancestor SplitButton control doesn't work. In the binding, you will note I am trying to Find the first Ancestor control of type SplitButton. I do see however that the debug info says ancestor level 1, so I changed the level to as high as 4, but still with an error.
Can someone see what the fix is?
binding error
System.Windows.Data Error: 4 : Cannot find source for binding with reference
'RelativeSource FindAncestor, AncestorType='Xceed.Wpf.Toolkit.SplitButton',
AncestorLevel='1''. BindingExpression:(no path); DataItem=null; target element is
'CloseDropDownContentBehavior' (HashCode=8896066); target property is 'DropDownButtonElement' (type 'SplitButton')
xaml
<DataTemplate x:Key="AddNewPartyTemplate">
<StackPanel HorizontalAlignment="Right" Margin="10">
<toolkit:SplitButton x:Name="theSplitButton" Content="{resx:Resx Subject_AddNewWithChoices}">
<toolkit:SplitButton.DropDownContent>
<StackPanel x:Name="theStackPanel">
<Button Content="{resx:Resx Person}" Command="{Binding AddNewPersonCommand}"
>
<i:Interaction.Behaviors>
<local:CloseDropDownContentBehavior
*** DropDownButtonElement="{Binding
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type toolkit:SplitButton}}}"/>
</i:Interaction.Behaviors>
</Button>
...
</StackPanel>
</toolkit:SplitButton.DropDownContent>
</toolkit:SplitButton>
</StackPanel>
</DataTemplate>
attached behavior
public class CloseDropDownContentBehavior : Behavior<ButtonBase>
{
private ButtonBase _button;
protected override void OnAttached()
{
_button = AssociatedObject;
_button.Click += OnPartyButtonClick;
}
protected override void OnDetaching()
{
_button.Click -= OnPartyButtonClick;
}
// **** the point of it all
void OnPartyButtonClick(object sender, RoutedEventArgs e) { DropDownButtonElement.IsOpen = false; }
public static readonly DependencyProperty DropDownButtonElementProperty =
DependencyProperty.Register("DropDownButtonElement",
typeof(SplitButton), typeof(CloseDropDownContentBehavior), new UIPropertyMetadata(null, OnDropDownElementChanged));
public DropDownButton DropDownButtonElement
{
get { return (DropDownButton)GetValue(DropDownButtonElementProperty); }
set { SetValue(DropDownButtonElementProperty, value); }
}
private static void OnDropDownElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
}
}
Guessing it's because Interaction.Behaviors isn't part of the visual tree, so the binding won't find the ancestor. Have you tried simply:
DropDownElement="{Binding ElementName=theSplitButton}"
Update from comments: the solution in this case is to simply use x:Reference:
DropDownElement="{x:Reference theSplitButton}"
i dont know the SplitButton.DropDownContent but if its behave like a context menu the following answer might help: WPF context menu whose items are defined as data templates
this trick is to bind with RelativeSource Self or Type ContextMenu and then set the Path to PlacementTarget.DataContext.YourProperty

Problems using inheritance with drag and drop in WPF

I have a usercontrol that I want to implement a drag and drop interface on, here are the vital parts of the implementation, and this works fine:
XML-file to the usercontrol to be draggable:
<UserControl
...default xmlns...
MouseLeftButtonDown="Control_MouseLeftButtonDown">
...GUI-ELEMENTS in the control...
</UserControl>
Code behind:
public partial class DragableControl : UserControl
{
private void Control_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragDrop.DoDragDrop(this, this, DragDropEffects.Move);
}
}
XML-file to the usercontrol which will be able to accept a drag and drop operation:
<Usercontrol
...default xmlns...>
<Grid AllowDrop="True" Drop="Grid_Drop">
... GUI elements in the grid....
</Grid>
</Usercontrol>
Code behind:
public partial class DropClass: UserControl
{
private void Grid_Drop(object sender, DragEventArgs e)
{
var control = (DragableControl)e.Data.GetData(typeof(DragableControl));
if(control != null)
{
//do something
}
}
}
To be able to create different usercontrols which have drag and drop functionality, I creates a base class, BaseDragableUserControl, which at the moment contains nothing, but inherits from usercontrol.
Code:
public class BaseDragableUserControl: UserControl
{
}
I change my code (both xaml and code):
public partial class DragableControl : UserControl
I also changes the class for receiving to this:
public partial class DropClass: UserControl
{
private void Grid_Drop(object sender, DragEventArgs e)
{
var control =(BaseDragableUserControl)e.Data.GetData(typeof(BaseDragableUserControl));
if(control != null)
{
//do something
}
}
}
But the control variable is always Null. I guess that the getdata in the DragEventsArgs does not like inheritance. Is there a way to achieve this? To make it possible to use a base class for a drag and drop class?
Instead of passing this when you initiate the drag/drop, create one of the standard containers for that purpose. Specifically:
DragDrop.DoDragDrop(this, new DataObject("myFormat", this), DragDropEffects.Move);
and then now you know to expect a specific kind of data:
var control =(BaseDragableUserControl)e.Data.GetData("myFormat");

Is there a way to call external functions from xaml?

Is there any way to call methods of external objects (for example resource objects) directly from xaml?
I mean something like this:
<Grid xmlns:dm="clr-namespace:MyNameSpace;assembly=MyAssembly">
<Grid.Resources>
<dm:TimeSource x:Key="timesource1"/>
</Grid.Resources>
<Button Click="timesource_updade">Update time</Button>
</Grid>
The method timesource_update is of course the method of the TimeSource object.
I need to use pure XAML, not any code behind.
Check this thread, it has a similar problem. In general you can't call a method directly from xaml.
You could use Commands or you can create an object from xaml which will create a method on a thread, which will dispose itself when it needs.
But I am afraid you can't do it just in pure XAML. In C# you can do everything you can do in XAML, but not other way round. You can only do some certain things from XAML that you can do in C#.
OK, here is the final sollution.
XAML:
<Grid xmlns:dm="clr-namespace:MyNameSpace;assembly=MyAssembly">
<Grid.Resources>
<dm:TimeSource x:Key="timesource1"/>
</Grid.Resources>
<Button Command="{x:Static dm:TimeSource.Update}"
CommandParameter="any_parameter"
CommandTarget="{Binding Source={StaticResource timesource1}}">Update time</Button>
</Grid>
CODE in the TimeSource class:
public class TimeSource : System.Windows.UIElement {
public static RoutedCommand Update = new RoutedCommand();
private void UpdateExecuted(object sender, ExecutedRoutedEventArgs e)
{
// code
}
private void UpdateCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
// Constructor
public TimeSource() {
CommandBinding cb = new CommandBinding(TimeSource.Update, UpdateExecuted, UpdateCanExecute);
CommandBindings.Add(cb2);
}
}
TimeSource has to be derived from UIElement in order to have CommandBindings. But the result is calling outer assembly method directly from XAML. By clicking the button, 'UpdateExecuted' method of the object timesource1 is called and that is exactly what I was looking for.

Is there an MVVM-friendly way to use the WebBrowser control in WPF?

Thanks to this question (click me!), I have the Source property of my WebBrowser binding correctly to my ViewModel.
Now I'd like to achieve two more goals:
Get the IsEnabled property of my Back and Forward buttons to correctly bind to the CanGoBack and CanGoForward properties of the WebBrowser.
Figure out how to call the GoForward() and GoBack() methods without resorting to the code-behind and without the ViewModel having to know about the WebBrowser.
I have the following (non-working) XAML markup at the moment:
<WebBrowser
x:Name="_instructionsWebBrowser"
x:FieldModifier="private"
clwm:WebBrowserUtility.AttachedSource="{Binding InstructionsSource}" />
<Button
Style="{StaticResource Button_Style}"
Grid.Column="2"
IsEnabled="{Binding ElementName=_instructionsWebBrowser, Path=CanGoBack}"
Command="{Binding GoBackCommand}"
Content="< Back" />
<Button
Style="{StaticResource Button_Style}"
Grid.Column="4"
IsEnabled="{Binding ElementName=_instructionsWebBrowser, Path=CanGoForward}"
Command="{Binding GoForwardCommand}"
Content="Forward >" />
I'm pretty sure the problem is that CanGoBack and CanGoForward are not dependency properties (and don't implement INotifyChanged), but I'm not quite sure how to get around that.
Questions:
Is there any way to hook up attached properties (as I did with Source) or something similar to get the CanGoBack and CanGoForward bindings to work?
How do write the GoBackCommand and GoForwardCommand so they are independent of the code-behind and ViewModel and can be declared in markup?
For anyone who comes across this question and wants a complete solution, here it is. It combines all of the suggestions made in this thread and the linked threads (and others those link to).
XAML:
http://pastebin.com/aED9pvW8
C# class:
http://pastebin.com/n6cW9ZBB
Example XAML usage:
http://pastebin.com/JpuNrFq8
Note: The example assumes your view binds to a ViewModel that provides the source URL to the browser. A very rudimentary navigation bar with back, forward, and refresh buttons and address bar is provided just for demonstration.
Enjoy. I have set the expiration on those pastebin's to never, so they should be available for as long as pastebin exists.
I used this in my bindable webbrowser wrapper:
CommandBindings.Add(new CommandBinding(NavigationCommands.BrowseBack, BrowseBack, CanBrowseBack));
CommandBindings.Add(new CommandBinding(NavigationCommands.BrowseForward, BrowseForward, CanBrowseForward));
CommandBindings.Add(new CommandBinding(NavigationCommands.BrowseHome, GoHome, TrueCanExecute));
CommandBindings.Add(new CommandBinding(NavigationCommands.Refresh, Refresh, TrueCanExecute));
CommandBindings.Add(new CommandBinding(NavigationCommands.BrowseStop, Stop, TrueCanExecute));
Note that I created my bindable webbrowser as FrameworkElement that exposes DependencyProperties and calls methods on the actual browser element, so i can set CommandBindings on it.
That way, you can use the default NavigationCommands in your View.
The used handlers are:
private void CanBrowseBack(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = webBrowser.CanGoBack;
}
private void BrowseBack(object sender, ExecutedRoutedEventArgs e) {
webBrowser.GoBack();
}
private void CanBrowseForward(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = webBrowser.CanGoForward;
}
private void BrowseForward(object sender, ExecutedRoutedEventArgs e) {
webBrowser.GoForward();
}
private void TrueCanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = true; }
private void Refresh(object sender, ExecutedRoutedEventArgs e) {
try { webBrowser.Refresh(); }
catch (Exception ex) { PmsLog.LogException(ex, true); }
}
private void Stop(object sender, ExecutedRoutedEventArgs e) {
mshtml.IHTMLDocument2 doc = WebBrowser.Document as mshtml.IHTMLDocument2;
if (doc != null)
doc.execCommand("Stop", true, null);
}
private void GoHome(object sender, ExecutedRoutedEventArgs e) {
Source = new Uri(Home);
}
Your question seems to imply that in order to correctly implement an MVVM pattern you are not allowed to have any code-behind. But perhaps adding some code-behind to your view will make it much easier to hook it up with your view-model. You can add dependency properties to the view and let it listen for INotifyPropertyChanged events.

Resources