Retrieving CommandTarget during the Executed phase - wpf

How can I retrieve the CommandTarget in the Executed callback of a RoutedCommand?
Thanks.
Edit: adding a verbose sample
Commands class:
static class Commands
{
public static readonly RoutedCommand MyCommand = new RoutedCommand();
}
XAML code
<Window.CommandBindings>
<CommandBinding Command="{x:Static BasicWpfCommanding:Commands.MyCommand}"
CanExecute="MyCommandCanExecute"
Executed="MyCommandExecuted"/>
</Window.CommandBindings>
<StackPanel>
<Button Command="{x:Static BasicWpfCommanding:Commands.MyCommand}"
CommandParameter="#FF303030"
CommandTarget="{Binding ElementName=aButton}"
Name="aButton">A Command</Button>
</StackPanel>
Commands callbacks
private void MyCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void MyCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
//var target = (Button)sender fires an ecception: in effect "sender" is the main window...
}

var target = e.Source;

Related

User usercontrol buttons in multiple views

`I am working on a WPF application (MVVM)
I have a user control(uc1) that has four buttons. cancel,accept,exit
I am going to use this control in multiple views.
I need to cancel button to revert the changes what user will make in propertygrig
user control:
<UserControl x:Class="WPF.CustomControl.RadPropertyWindowButtons"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="45" d:DesignWidth="700">
<Grid>
<Grid Uid="radpropertybuttons" Height="39" VerticalAlignment="Bottom" Margin="74,0,-108,0">
<Button x:Name="Cancel"
Command="{Binding radpropertyCancel}" >
</Button>
<Button x:Name="Accept"
Command="{Binding radpropertyAccept}">
</Button>
<Button x:Name="Exit"
Command="{Binding radpropertyExit}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
</Button>
</Grid>
</Grid>
</UserControl>
view:
<Grid Height="564" VerticalAlignment="Top" >
<Grid HorizontalAlignment="Center">
<telerik:RadLayoutControl
Name="PropertyGridContainer"
Orientation="Vertical">
</telerik:RadLayoutControl>
</Grid>
<Grid VerticalAlignment="Bottom">
<customcontrol:RadPropertyWindowButtons x:Name="ucPropertyButtons" Height="44" VerticalAlignment="Top" HorizontalAlignment="Center" Loaded="RadPropertyWindowButtons_Loaded" />
</Grid>
</Grid>
in view model
public ICommand radpropertyCancel { get; set; }
radpropertyCancel = new ViewModelCommand(execradpropertyCancel);
private void execradpropertyCancel(object obj)
{
this.RevertToOriginalData();
}
how to clear the PropertyGridContainer and bind with the data that we get from RevertToOriginalData`
I do it like this if i do from code behind if i use click event but how to do it with command.
this._viewModel.RevertToOriginalData();
this.PropertyGridContainer.Items.Clear();
this.PropertyGridContainer.Items.Add(this._viewModel.myGrid);
this.ViewModel.IsDirty = false;
this._viewModel.myGrid is wrong design if myGrid is really a Grid ( a UI element). Your view model classes must never handle UI elements or participate in/implement UI logic.
Data changes are always handled outside the view (where the data lives). Layout on the other hand is always the domain of the view.
If you want to revert the layout changes made by the user, you must do this completely in the view (code-behind).
To accomplish this, a parent control (e.g., Window) that hosts both, the RadPropertyWindowButtons and the RadLayoutControl, should expose the related commands as routed commands.
Then in the command handlers you save (serialize) the layout before edit (or alternatively on accept/after edit) and restore (deserialize) it in case the edit procedure was cancelled. The RadLayoutControl exposes a related API to help with the serialization.
Now, that the implementation of the custom control no longer depends on the explicit view model class type, the RadPropertyWindowButtons has become fully reusable in any context.
In general, to enable reusability of controls they must express their (data) dependencies as dependency properties, that are later bound to the current DataContext. The internals of the reusable control simply bind to these dependency properties (instead of binding to an explicit DataContext type). Otherwise they are only "reusable" with a particular DataContext.
MainWindow.xaml.cs
partial class MainWindow : Window
{
public static RoutedUICommand CancelEditLayoutCommand { get; } = new RoutedUICommand(
"Cancel layout edit and revert to previous state",
nameof(MainWindow.CancelEditLayoutCommand),
typeof(MainWindow));
public static RoutedUICommand AcceptLayoutCommand { get; } = new RoutedUICommand(
"Accept the current layout",
nameof(MainWindow.AcceptLayoutCommand),
typeof(MainWindow));
private Dictionary<RadLayoutControl, bool> IsInEditModeTable { get; }
private string SerializedLayout { get; set; }
public MainWindow()
{
InitializeComponent();
this.IsInEditModeTable = new Dictionary<RadLayoutControl, bool>();
var cancelEditLayoutCommandBinding = new CommandBinding(MainWindow.CancelEditLayoutCommand, ExecuteCancelEditLayoutCommand, CanExecuteCancelEditLayoutCommand);
_ = this.CommandBindings.Add(cancelEditLayoutCommandBinding);
var acceptLayoutCommandBinding = new CommandBinding(MainWindow.AcceptLayoutCommand, ExecuteAcceptLayoutCommand, CanExecuteAcceptLayoutCommand);
_ = this.CommandBindings.Add(acceptLayoutCommandBinding);
}
private void CanExecuteCancelEditLayoutCommand(object sender, CanExecuteRoutedEventArgs e)
=> e.CanExecute = e.Parameter is RadLayoutControl targetControl
&& this.IsInEditModeTable.TryGetValue(targetControl, out bool isTargetControlInEditMode)
&& isTargetControlInEditMode;
private void ExecuteCancelEditLayoutCommand(object sender, ExecutedRoutedEventArgs e)
{
var targetControl = (RadLayoutControl)e.Parameter;
RestoreLayout(targetControl);
this.IsInEditModeTable[targetControl] = false;
}
private void CanExecuteAcceptLayoutCommand(object sender, CanExecuteRoutedEventArgs e)
=> e.CanExecute = e.Parameter is RadLayoutControl targetControl
&& this.IsInEditModeTable.TryGetValue(targetControl, out bool isTargetControlInEditMode)
&& isTargetControlInEditMode;
private void ExecuteAcceptLayoutCommand(object sender, ExecutedRoutedEventArgs e)
{
var targetControl = (RadLayoutControl)e.Parameter;
SaveLayout(targetControl);
this.IsInEditModeTable[targetControl] = false;
}
// Instead of handling the SelectionChanged event I recommend
// to introduce another routed command that allows the user to put the RadLayoutControl into edit mode (by setting the RadLayoutControl.IsInEditMode accordingly).
// Aside from an improved UX this would provide a better flow or trigger to kickoff the serialization
private void OnLayoutControlSelectionChanged(object sender, LayoutControlSelectionChangedEventArgs e)
{
var targetControl = sender as RadLayoutControl;
if (this.IsInEditModeTable.TryGetValue(targetControl, out bool isTargetControlInEditMode)
&& isTargetControlInEditMode)
{
return;
}
isTargetControlInEditMode = e.NewItem is not null;
if (isTargetControlInEditMode)
{
SaveLayout(targetControl);
}
this.IsInEditModeTable[targetControl] = isTargetControlInEditMode;
}
private void SaveLayout(RadLayoutControl targetControl)
=> this.SerializedLayout = targetControl.SaveToXmlString();
private void RestoreLayout(RadLayoutControl targetControl)
=> targetControl.LoadFromXmlString(this.SerializedLayout);
}
MainWindow.xaml
<Window>
<StackPanel>
<telerik:RadLayoutControl Name="PropertyGridContainer"
IsInEditMode="True"
telerik:RadLayoutControl.SerializationId="PropertyGridContainerID"
SelectionChanged="OnLayoutControlSelectionChanged" />
<customcontrol:RadPropertyWindowButtons TargetControl="{Binding ElementName=PropertyGridContainer}" />
</StackPanel>
</Window>
RadPropertyWindowButtons.xaml.cs
class RadPropertyWindowButtons
{
public RadLayoutControl TargetControl
{
get => (RadLayoutControl)GetValue(TargetControlProperty);
set => SetValue(TargetControlProperty, value);
}
public static readonly DependencyProperty TargetControlProperty = DependencyProperty.Register(
"TargetControl",
typeof(RadLayoutControl),
typeof(RadPropertyWindowButtons),
new PropertyMetadata(default));
}
RadPropertyWindowButtons.xaml
<UserControl>
<StackPanel>
<Button x:Name="Cancel"
Command="{x:Static local:MainWindow.CancelEditLayoutCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=TargetControl}" />
<Button x:Name="Accept"
Command="{x:Static local:MainWindow.AcceptLayoutCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=TargetControl}" />
</StackPanel>
</UserControl>
See Save/Load Layout for more advanced scenarios.

How can i trigger a event when a keyboard shortcut ( Ctrl+F7 ) is fired?

I want to fetch the event when someone in my Application uses the Shortcut Ctrl+F7. Because I have settings in my application, i dont want anyone to be able to change, but if he has to change those, I can provide him the keyboard shortcut.
How do my Application get to know, when CTRL+F7 is fired ( Application has Focus when the Shortcut is fired.)
You could override the OnPreviewKeyDown method of your main window:
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (e.Key == Key.F7 && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.LeftCtrl)))
{
//...
}
}
If your application has several top-level windows, you could create a common base class where you override the method and then inherit all your windows from this one:
public abstract class AppWindow : Window
{
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (e.Key == Key.F7 && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.LeftCtrl)))
{
//...
}
}
}
public partial class MainWindow : AppWindow
{
public MainWindow()
{
InitializeComponent();
}
}
XAML:
<local:AppWindow x:Class="WpfApp1.MainWindow">...</local:AppWindow>
You can use Command binding with Hot Key like this:
<Window.CommandBindings>
<CommandBinding Command="{x:Static local:CustomCommands.ShortCutCommand}" Executed="CommandBinding_Executed_1" CanExecute="CommandBinding_CanExecute" />
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Command="{x:Static local:CustomCommands.ShortCutCommand}" Key="F7" Modifiers="Ctrl" />
</Window.InputBindings>
MainWindow.cs
public static RoutedCommand ShortCutCommand= new RoutedCommand();
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void CommandBinding_Executed_1(object sender, ExecutedRoutedEventArgs e)
{
//do something here
}

Double-click on ListBox within DataGridTemplateColumn to open window - opens underneath

I have ListBoxes defined in a DataTemplateColumn:
<Window x:Class="DoubleclickTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataGrid Name="dg" IsReadOnly="True" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ListBox Loaded="ListBox_Loaded" MouseDoubleClick="ListBox_MouseDoubleClick" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Window>
and I want to open a new window on double-clicking the listbox:
public MainWindow() {
InitializeComponent();
dg.ItemsSource = new[] { 1, 2, 3, 4, 5 };
}
private void ListBox_Loaded(object sender, RoutedEventArgs e) {
((ListBox)sender).ItemsSource=Enumerable.Range(1,5);
}
private void ListBox_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
e.Handled = true;
var win = new MainWindow();
win.Show();
//Neither of these help:
//win.Activate();
//win.Focus();
}
The new window opens underneath the current window.
How can I have the new window open over the current window (without using ShowDialog)?
Update
Using an ItemsControl instead of a ListBox doesn't help.
That was a weird behavior but the following method will help:
<Window ... Loaded="Window_Loaded">
Use a timer to activate the window:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Timer t = new Timer(10);
t.Elapsed += t_Elapsed;
t.Start();
}
void t_Elapsed(object sender, ElapsedEventArgs e)
{
(sender as Timer).Stop();
Dispatcher.Invoke(() =>
{
this.Activate();
this.Focus();
});
}
The used timer is system timer:
using System.Timers;
Using .NET 4.5:
private async void ListBox_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
e.Handled = true;
await Task.Delay(20);
var win = new MainWindow();
win.Show();
}
This is still a bit of a hack to work around the issue, but if there are no better solutions I will use this.

Attached Behavior handling an Attached Event in WPF

I googled regarding this question but couldn't gather any information and I was wondering if it is possible for an attached behavior to handle an attached event??
I've an event declared in a class and a behavior that I am attaching to a TextBox control, the event will be raised when a button is clicked. I added the handler for this event in my behavior and wrote the logic in the event handler, but it is not executed. So, I was wondering if it is possible for an attached behavior to handle an attached event or not?
class ResetInputEventClass
{
public static readonly RoutedEvent ResetInputEvent = EventManager.RegisterRoutedEvent("ResetInput",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(ResetInputEventClass));
public static void AddResetInputEventHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie == null)
{
return;
}
uie.AddHandler(ResetInputEventClass.ResetInputEvent, handler);
}
public static void RemoveResetInputEventHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie == null)
{
return;
}
uie.RemoveHandler(ResetInputEventClass.ResetInputEvent, handler);
}
}
That is my Event class and this is how I am handling it in the behavior
public class MyBehavior : Behavior<TextBoxBase>
{
public MyBehavior()
{
// Insert code required on object creation below this point.
}
protected override void OnAttached()
{
base.OnAttached();
// Insert code that you would want run when the Behavior is attached to an object.
ResetInputEventClass.AddResetInputEventHandler(AssociatedObject, OnResetInputEvent);
}
protected override void OnDetaching()
{
base.OnDetaching();
// Insert code that you would want run when the Behavior is removed from an object.
ResetInputEventClass.RemoveResetInputEventHandler(AssociatedObject, OnResetInputEvent);
}
private void OnResetInputEvent(Object o, RoutedEventArgs e)
{
//Logic
}
}
Here is my XAML Code:
<Grid x:Name="LayoutRoot">
<StackPanel>
<TextBox Margin="5" Text="Bye" TextWrapping="Wrap" Width="150">
<i:Interaction.Behaviors>
<local:MyBehavior/>
</i:Interaction.Behaviors>
</TextBox>
<TextBox Margin="5" Text="Bye" TextWrapping="Wrap" Width="150">
<i:Interaction.Behaviors>
<local:MyBehavior/>
</i:Interaction.Behaviors>
</TextBox>
<Button Name="MyButton" Content="Save" Width="50" Height="25" Click="MyButton_Click"/>
</StackPanel>
</Grid>
and I am raising the event in the click event of my button
private void MyButton_Click(object sender, RoutedEventArgs e)
{
RoutedEventArgs eventArgs = new RoutedEventArgs(ResetInputEventClass.ResetInputEvent,e.OriginalSource);
RaiseEvent(eventArgs);
}
Your problem is simple. The textbox is registered for the event, but the parent of the textbox is raising it. Thus the handler is never called. You can change the event to make it a Tunneling event instead of Bubbling. Or you can get a handle on your textbox (give it a name and reference in code behind). And have it raise the event.
<Grid x:Name="LayoutRoot">
<StackPanel>
<TextBox Margin="5" x:Name="byeTextBox" Text="Bye" TextWrapping="Wrap" Width="150">
<i:Interaction.Behaviors>
<local:MyBehavior/>
</i:Interaction.Behaviors>
</TextBox>
<Button Name="MyButton" Content="Save" Width="50" Height="25" Click="MyButton_Click"/>
</StackPanel>
</Grid>
Your code-behind should then look like this
private void MyButton_Click(object sender, RoutedEventArgs e)
{
RoutedEventArgs eventArgs = new RoutedEventArgs(ResetInputEventClass.ResetInputEvent,e.OriginalSource);
byeTextBox.RaiseEvent(eventArgs);
}
and that should fix your problem.
Of course it is possible. Show me your XAML and I ll tel you how an attached event triggers an attached behavior.
Edited:
I dont see the need why you using attached behavior and attached events because you could do everything in code behind.
Here is how to do everything in code behind:
Here is XAML without attached properties:
<Grid>
<StackPanel>
<TextBox x:Name="txtBox" Margin="5" Text="Bye" TextWrapping="Wrap" Width="150"/>
<Button Name="MyButton" Content="Save" Width="50" Height="25" Click="MyButton_Click"/>
</StackPanel>
</Grid>
This is code behind.
public MainWindow()
{
InitializeComponent();
}
private void MyButton_Click(object sender, RoutedEventArgs e)
{
this.txtBox.Text = "hello";
}
Because you have set Name property on TextBox and Button you can access them from code behind in your Window.cs and you can write your handler easly.
Here is how you can do everything with attached properties:
This is the new XAML for the solution with attached properties. I had to create my custom Interaction because the one you are using is Expression Blend or silverlight and not pure WPF.
<Grid x:Name="LayoutRoot">
<StackPanel i:Interaction.Behaviour="True">
<TextBox x:Name="txtBox" Margin="5" Text="Bye" TextWrapping="Wrap" Width="150"/>
<Button Name="MyButton" Content="Save" Width="50" Height="25" Click="MyButton_Click"/>
</StackPanel>
</Grid>
I had to set Behavior on True because the default value is false and when value is not equal to the old then the propery changed event will be called with my custom logic like this:
private void MyButton_Click(object sender, RoutedEventArgs e)
{
RoutedEventArgs eventArgs = new RoutedEventArgs(ResetInputEventClass.ResetInputEvent,e.OriginalSource);
RaiseEvent(eventArgs);
}
public class Interaction : DependencyObject
{
// Using a DependencyProperty as the backing store for Behaviour. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BehaviourProperty =
DependencyProperty.RegisterAttached("Behaviour", typeof(bool), typeof(Interaction), new PropertyMetadata(false, new PropertyChangedCallback(OnBehaviourChanged)));
private static void OnBehaviourChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
StackPanel sp = (StackPanel)d;
sp.Dispatcher.BeginInvoke(new Action(() =>
{
TextBox tb = VisualTreeHelper.GetChild(sp, 0) as TextBox;
ResetInputEventClass.AddResetInputHandler(sp, new RoutedEventHandler((o, a) =>
{
// Do here whatever you want, call your custom expressions.
tb.Text = "hello";
}));
}), System.Windows.Threading.DispatcherPriority.Background);
}
}
Inside property changed event which will be called as I already mentioned when I change false to true. I wait till everything is intialized by telling the dispatcher to execute my code when application is in background. Then I find the TextBox and inject the handler which will be called when you trigger ResetInput event.
This is very complicated solution but it will work with attached events and attached properties.
I highly recommend you to use the code behind for this scenario.
Also you made a mistake inside your ResetInputEventClass class. Add and Remove methods are not correctly spelled.
This is how you should have written them:
public static void AddResetInputHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie == null)
{
return;
}
uie.AddHandler(ResetInputEventClass.ResetInputEvent, handler);
}
public static void RemoveResetInputHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie == null)
{
return;
}
uie.RemoveHandler(ResetInputEventClass.ResetInputEvent, handler);
}
Have fun, I hope I helped you out.
You could also have achieved this with Commands

WPF ContextMenu CommandBinding disabled

I have a button and a context menu bound to the same Command, when I start the app, the button is enabled, but the context menu item isn't (as I said, they're bound to the same command). The context menu item becomes enabled only after I click the button. Does anybody know why?
This is the behavior.
And this is the XAML:
<Window.CommandBindings>
<CommandBinding Command="local:LocalCommandManager.ShowDialogCommand" CanExecute="CanExecuteShowDialogCommand" Executed="ShowDialogCommandExecuted" />
<CommandBinding Command="local:LocalCommandManager.DontShowDialogCommand" CanExecute="CanExecuteDontShowDialogCommand" Executed="DontShowDialogCommandExecuted" />
</Window.CommandBindings>
<Window.ContextMenu>
<ContextMenu>
<MenuItem Command="local:LocalCommandManager.ShowDialogCommand" />
<MenuItem Command="local:LocalCommandManager.DontShowDialogCommand" />
</ContextMenu>
</Window.ContextMenu>
<Grid Background="Red">
<Button Command="local:LocalCommandManager.ShowDialogCommand" Content="Show Dialog" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="6" />
</Grid>
Thanks!
EDIT:
Command code:
public static class LocalCommandManager
{
private static object syncRoot = new object();
private static RoutedUICommand _showDialogCommand;
public static RoutedUICommand ShowDialogCommand
{
get
{
lock (syncRoot)
{
if (_showDialogCommand == null)
_showDialogCommand = new RoutedUICommand("Show Dialog", "ShowDialogCommand", typeof(LocalCommandManager));
return _showDialogCommand;
}
}
}
}
Command event handlers (in MainWindow.xaml.cs):
private void CanExecuteShowDialogCommand(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void ShowDialogCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Dialog");
}

Resources