I need to provide access key shortcut for several functions such as Save. To do this I have started by handling the KeyUp event of my root object which is a Grid called LayoutRoot (typically created as default in a Silverlight UserControl or Page).
I am using an MVVM pattern but for this I have added code in the code behind as such (this is UI interaction so it seems OK):
private void LayoutRoot_KeyUp(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.S:
if ((Keyboard.Modifiers & ModifierKeys.Windows) == ModifierKeys.Windows)
{
e.Handled = true;
// save
}
break;
case Key.C:
if ((Keyboard.Modifiers & ModifierKeys.Windows) == ModifierKeys.Windows)
{
e.Handled = true;
// clear fields
}
break;
}
}
I have used the Windows key because there are no shorcuts in the browser that use it as far as I know.
I implement the Save functionality on the Save button using a Command, therefore maintaining the MVVM pattern. E.g.
public RelayCommand CommandSavePtr { get; private set; }
CommandSavePtr = new RelayCommand(OnSavePtr);
private void OnSavePtr()
{
....
In XAML: -
<Button x:Name="SavePtrButton"
Command="{Binding CommandSavePtr}"
Style="{StaticResource StandardButtonStyle}"
IsEnabled="{Binding Ptr.HasErrors, Converter={StaticResource NotOperatorValueConverter}}">
<StackPanel Orientation="Horizontal">
<Image Source="/G4SPrisonerEscorting_ResourceDictionaries;component/images/accept.png" Style="{StaticResource SubPanelIconStyle}"/>
<TextBlock Text="Save"/>
</StackPanel>
</Button>
My problem now is that I don't know how to communicate to the ViewModel from my above KeyUp event to perform the same Save function that is perfomed when clicking the Save button.
Could anyone point me in the right direction.
BTW I am using GalaSoft's MVVM Light to do the Commanding.
This is an old question, but I thought I would answer it anyway if someone else stumbles upon the same problem.
It should be possible to fire the command for the button in the code-behind in the following manner:
SavePtrButton.Command.Execute(SavePtrButton.CommandParameter);
Related
I have a UserControl with a number of StackPanel's. I like to hide specific panels depending on the user action. A StackPanel which is visible on startup gives me a number of working buttons. The buttons have click events in the code behind file. After collapsing the panel and then making it visible again the buttons no longer work. Here is a part of my UserControl:
<StackPanel x:Name="buttonPanel" Orientation="Horizontal">
<Button x:Name="ReMindNodeNotes" Content=""
FontFamily="Segoe UI Symbol" FontSize="14" Foreground="#FF292323"
HorizontalAlignment="Left" BorderThickness="1" Padding="0"
UseLayoutRounding="True" Click="NoteClicked" />
<Button x:Name="ReMindNodeRemove" Content=""
FontFamily="Segoe UI Symbol" FontSize="14" Foreground="#FF292323"
HorizontalAlignment="Left" BorderThickness="1" Padding="0"
UseLayoutRounding="True" Click="RemoveClicked" />
</StackPanel>
And here is the code (for now just some text):
private void NoteClicked(object sender, RoutedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("NoteClicked...");
}
private void RemoveClicked(object sender, RoutedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("RemoveClicked...");
}
I have been looking for a solution the last two days. No luck so far. Who can help...?
THX Peter
Follow up 1...
Here is the code for collapsing the panel:
private void MoreClicked(object sender, RoutedEventArgs e)
{
System.Diagnostics.Debug.WriteLine(this.nodeName);
this.buttonPanel.Visibility =
this.buttonPanel.Visibility ==
Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
}
It works if the buttonPanel has focus. If the focus is on another panel it does not. Furthermore, what I probably should have mentioned... is that users can create multiple instances of the user control.
THX
Follow up 2...
I continue working on a solution of course... ;-) and I found a solution, which however is not the solution I want. Let me explain.
Users can interactively create multiple instances of the user control mentioned before. When a new instance is created, that instance gets focus. Now every instance has its own set of buttons which are on a stackpanel. When the focus goes to another instance I want the panel of the previous instance to collapse. The focus should then be set to the new (or selected existing) instance.
When I do this manually, it works! When I try to achieve this through the GotFocus and LostFocus events however, it does not. Here is the code for the manual solution (which works):
private void MoreClicked(object sender, RoutedEventArgs e)
{
this.buttonPanel.Visibility =
this.buttonPanel.Visibility ==
Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
}
Here are the LostFocus and GotFocus events:
private void NodeGotFocus(object sender, RoutedEventArgs e)
{
this.buttonPanel.Visibility = Visibility.Visible;
}
private void NodeLostFocus(object sender, RoutedEventArgs e)
{
this.buttonPanel.Visibility = Visibility.Collapsed;
}
I really appreciate your help! THX again...
Thanks for your sample morincer. The problem however is a little more complex. Let me try to explain the solution which I found after some more research. Maybe other developers can benefit from it as well.
I added the GotFocus and LostFocus events to my userconctrol. If I click somewhere inside the usercontrol the focus changes every time. Strange as these events are only defined on the usercontrol itself and not it's children. I have several buttons and a textbox inside the usercontrol and when I for example click on one of the buttons of the usercontrol that has focus the LostFocus and GotFocus events are fired for usercontrol anyway.
The most important event for me in this case is the LostFocus event. When the usercontrol looses focus - for example to another control - I want the button panel to disappear. Since the LostFocus event fires every time a object inside the usercontrol is touched, I cannot distinguish between the situation in which I want to hide and show the buttons.
I got a little closer to a solution by changing the LostFocus event as follows:
private void LostFocus(object sender, RoutedEventArgs e)
{
Object fo = FocusManager.GetFocusedElement();
if (fo.GetType().ToString().Contains("TextBox") ||
fo.GetType().ToString().Contains("ScrollViewer"))
{
this.buttonPanel.Visibility = Visibility.Collapsed;
}
}
This covers most of the situations. When the cursor is positioned in the TextBox the button panel is closed. The button panel is also closed when the user clicks on the background. This seems to be a ScrollViewer (found through debugging the code). Can anyone explain this...?
The situation which is not covered however, is when a user clicks on another usercontrol. It does of course when the user clicks on the TextBox (see the code) but not when the user clicks on a button. I tried to compare sender and FocusManager.GetFocusedElement(). Problem is that the sender returns the usercontrol (which is what I am looking for) but the FocusManager.GetFocusedElement() returns the button that was pressed. Now I could ask for it's parent which is a border then ask for the borders parent which is a stack panel and so on until I arrive at the usercontrol. A code behind file however was introduced with the idea to split design and logic while this solution would tie them together again. If I would change the XAML I would have to change the logic as well. Doesn't seem to be the right solution to me.
I found a solotion by giving every usercontrol a unique name in the constructor. I then give all the buttons unique names as well (I don't use them in my code anyway) starting with the name of the usercontrol. This then gives me the possibility to compare names at runtime and determine whether the focus has changed to another instance of the usercontrol. Here is the code:
private void NodeLostFocus(object sender, RoutedEventArgs e)
{
Object fo = FocusManager.GetFocusedElement();
if (fo.GetType().ToString().Contains("ScrollViewer"))
{
this.buttonPanel.Visibility = Visibility.Collapsed;
}
else if (fo.GetType().ToString().Contains("TextBox"))
{
if (!((TextBox)fo).Name.Contains(this.nodeName))
{
this.buttonPanel.Visibility = Visibility.Collapsed;
}
}
else if (fo.GetType().ToString().Contains("Button"))
{
if (!((Button)fo).Name.Contains(this.nodeName))
{
this.buttonPanel.Visibility = Visibility.Collapsed;
}
}
}
Now this works! But…I don't like the solution. I am depending on names instead of a good architecture. Does anyone hove an idea how to compare the actual sender with the usercontrol that is the parent of the button pressed (FocusManager.GetFocusedElement())? Or any other solution that relies on good programming?
THX again
I am attempting to have a key binding cause an event within the view model. I have been search for a while and have not come across any solutions that have worked thus far, unfortunately.
This is what I am basically attempting to implement:
<i:Interaction.Triggers>
<i:EventTrigger EventName="createNew">
<cal:ActionMessage MethodName="newCustomer" />
</i:EventTrigger>
</i:Interaction.Triggers>
I am wanting a way to provide a "hotkey" to allow the user to implement a newCustomer event within the view model. So far it won't even reach into the view model. If I attach the EventName="KeyDown" it works wonderfully if any key is pressed, but I am attempting to target a single key.
I will add that the code behind in the view model looks like this.
public void createNew(object sender, KeyEventArgs e)
{ if (e.Key == Key.F9)
{
addCustomer();
} }
Not too sure what control you are using, but for example purposes I'll use a text-box...
Not entirely sure if this will help even, but I hope it does:
The View:
<TextBox x:Name="MyTextBox" TextAlignment="Left" FontSize="10" Width="240" cal:Message.Attach="[Event KeyUp]=[Action ExecuteAction($executionContext)]"/>
The ViewModel:
* For example purposes I'll use "Enter" as the single key on which you want the actions performed.
public void ExecuteAction(ActionExecutionContext context)
{
var eventArgs = (KeyEventArgs)context.EventArgs;
if (eventArgs.Key != Key.Enter) return;
//Execute Actions...
}
This ought to fire code on each key-press on the control you want, if the key is not equal to whatever you specify, it won't do anything...
If I'm waaay off, maybe I just don't understand the issue correctly :D
I have a C# WPF .NET 4 application that has an icon in the system tray. I am currently using the well-discussed WPF NotifyIcon, but the problem I am having is not dependent on this control. The problem is that .NET 4 simply does not allow (for the most part) a WPF ContextMenu object to appear over the top of the Windows 7 taskbar. This example illustrates the problem perfectly.
XAML:
<Window x:Class="TrayIconTesting.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="100" Width="400">
<Window.Resources>
<ContextMenu x:Key="TrayContextMenu" Placement="MousePoint">
<MenuItem Header="First Menu Item" />
<MenuItem Header="Second Menu Item" />
</ContextMenu>
<Popup x:Key="TrayPopup" Placement="MousePoint">
<Border Width="100" Height="100" Background="White" BorderBrush="Orange" BorderThickness="4">
<Button Content="Close" Click="ButtonClick"></Button>
</Border>
</Popup>
</Window.Resources>
<StackPanel Orientation="Horizontal">
<Label Target="{Binding ElementName=UseWinFormsMenu}" VerticalAlignment="Center">
<AccessText>Use WinForms context menu for tray menu:</AccessText>
</Label>
<CheckBox Name="UseWinFormsMenu" IsChecked="False" Click="UseWinFormsMenuClicked" VerticalAlignment="Center" />
</StackPanel>
</Window>
Code:
using System.Drawing;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Forms;
using ContextMenu = System.Windows.Controls.ContextMenu;
namespace TrayIconTesting
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ContextMenuStrip winFormsContextMenu;
public MainWindow()
{
InitializeComponent();
this.TrayIcon = new NotifyIcon
{
Icon = new Icon("Bulb.ico"),
Visible = true
};
this.TrayIcon.MouseClick += (sender, args) =>
{
switch (args.Button)
{
case MouseButtons.Left:
this.TrayPopup.IsOpen = true;
break;
case MouseButtons.Right:
if (!this.UseWinFormsMenu.IsChecked.GetValueOrDefault())
{
this.TrayContextMenu.IsOpen = true;
}
break;
}
};
}
private void ButtonClick(object sender, RoutedEventArgs e)
{
this.TrayPopup.IsOpen = false;
}
private void UseWinFormsMenuClicked(object sender, RoutedEventArgs e)
{
this.TrayIcon.ContextMenuStrip = this.UseWinFormsMenu.IsChecked.GetValueOrDefault() ? this.WinFormsContextMenu : null;
}
private ContextMenu TrayContextMenu
{
get
{
return (ContextMenu)this.FindResource("TrayContextMenu");
}
}
private Popup TrayPopup
{
get
{
return (Popup)this.FindResource("TrayPopup");
}
}
private NotifyIcon TrayIcon
{
get;
set;
}
private ContextMenuStrip WinFormsContextMenu
{
get
{
if (this.winFormsContextMenu == null)
{
this.winFormsContextMenu = new ContextMenuStrip();
this.winFormsContextMenu.Items.AddRange(new[] { new ToolStripMenuItem("Item 1"), new ToolStripMenuItem("Item 2") });
}
return this.winFormsContextMenu;
}
}
}
}
To see the problem make sure that the tray icon is always visible and not part of that Win7 tray icon popup thing. When you right click on the tray icon the context menu opens ABOVE the taskbar. Now right click one of the standard Windows tray icons next to it and see the difference.
Now, left click on the icon and notice that it DOES allow a custom popup to open right where the mouse cursor is.
Checking the "Use WinForms..." checkbox will switch the app to use the old ContextMenuStrip context menu in the Windows.Forms assembly. This obviously opens the menu in the correct place, but its appearance doesn't match the default Windows 7 menus. Specifically, the row highlighting is different.
I have played with the Horizontal and VerticalOffset properties, and with the "right" values you can make the context menu popup all the way at the bottom right of the screen, but this is just as bad. It still never opens where your cursor is.
The real kicker is that if you build this same sample targeting .NET 3.5 it works just as expected. Unfortunately, my real application uses many .NET 4 features, so reverting back is not an option.
Anyone have any idea how to make the context menu actually open where the cursor is?
After a little more searching I stumbled across this question & answer. I never thought to try the ContextMenu property on the NotifyIcon! While not ideal it will work well enough until WPF address the fact that the system tray is a useful part of applications. It will really be a shame to lose all the binding and command routing features provided by the WPF ContextMenu though.
It feels wrong to accept my own answer though, so I'm going to leave this open for a few more days.
Well, I'm glad didn't mark this as answered because I found a slightly better option for me. I found this article that details how to add icons to the System.Windows.Forms.MenuItem object. Now with just a little code I have a menu that perfectly matches system context menus!
I have seen a few posts addressing how to remove an UserControl that has been added during runtime, but my problem is a little different. I have a UserControl that consists of an image with a small "x" button on the top right corner that is used to remove itself (the UserControl) from its parent canvas. Also to note is that the UserControl is added during runtime when the user doubleclicks on a ListboxItem. I have a Click event handler for the top right corner button but this code is not running at all. I know this because I have a breakpoint in this code which is not reached when I click the button.
So,
Why isn't the click event of the remove button being handled?
Maybe there is a better way to implement this. Please advise.
Here's the code used for adding it:
private void MyListBox_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (e.OriginalSource.ToString() == "System.Windows.Controls.Border" || e.OriginalSource.ToString() == "System.Windows.Controls.Image" || e.OriginalSource.ToString() == "System.Windows.Controls.TextBlock")
{
Expression.Blend.SampleData.MyCollection.Dataset lbi = ((sender as ListBox).SelectedItem as Expression.Blend.SampleData.MyCollection.Dataset);
var new_usercontrol = new MyUserControl();
new_usercontrol.MyImageSourceProperty = lbi.Image;
MyCanvas.Children.Add(new_usercontrol);
Canvas.SetLeft(new_usercontrol, 100);
Canvas.SetTop(new_usercontrol, 100);
Canvas.SetZIndex(new_usercontrol, 100);
}
}
The following is the cs code for the UserControl:
public partial class ModuleElement : UserControl
{
public ImageSource MyProperty
{
get { return (ImageSource)this.image.Source; }
set { this.image.Source = value; }
}
public ModuleElement()
{
this.InitializeComponent();
}
private void RemoveButton_Click(object sender, RoutedEventArgs e)
{
((Canvas)this.Parent).Children.Remove(this);
}
}
The XAML:
<Grid x:Name="LayoutRoot">
<Image x:Name="image" />
<Button x:Name="RemoveButton" Content="X" HorizontalAlignment="Right" Height="17.834" Margin="0" VerticalAlignment="Top" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Click="RemoveButton_Click">
</Button>
</Grid>
Thanks in advance,
Bryan
So I tried your code here exactly except for some name changes and could not reproduce your issue. In my personal experience your issue here has to be that for some reason the event for the click isn't subscribed to properly. For this I would go into designer for the user control, wipe out the current event for the button and double click in the designer event textbox such that VS or Blend generates all the code necessary for a proper subscription.
I have created a sample based on your code here. Feel free to pull it down and take a look to see if you can find any inconsistencies.
As far as a better way to implement this, check out the good old MVVM pattern and the MVVM Light Toolkit. With this you can have a central ViewModel class that will handle all of your button commands and binding without code behind.
The easiest way is to implement ButtonClick event handler and invoke Window.Close() method, but how doing this through a Command binding?
All it takes is a bit of XAML...
<Window x:Class="WCSamples.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Close"
Executed="CloseCommandHandler"/>
</Window.CommandBindings>
<StackPanel Name="MainStackPanel">
<Button Command="ApplicationCommands.Close"
Content="Close Window" />
</StackPanel>
</Window>
And a bit of C#...
private void CloseCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
this.Close();
}
(adapted from this MSDN article)
Actually, it is possible without C# code. The key is to use interactions:
<Button Content="Close">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction TargetObject="{Binding ElementName=window}" MethodName="Close"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
In order for this to work, just set the x:Name of your window to "window", and add these two namespaces:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
This requires that you add the Expression Blend SDK DLL to your project, specifically Microsoft.Expression.Interactions.
In case you don't have Blend, the SDK can be downloaded here.
I think that in real world scenarios a simple click handler is probably better than over-complicated command-based systems but you can do something like that:
using RelayCommand from this article http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
public class MyCommands
{
public static readonly ICommand CloseCommand =
new RelayCommand( o => ((Window)o).Close() );
}
<Button Content="Close Window"
Command="{X:Static local:MyCommands.CloseCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}"/>
If the window was shown with Window.ShowDialog():
The simplest solution that I know of is to set the IsCancel property to true of the close Button:
<Button Content="Close" IsCancel="True" />
No bindings needed, WPF will do that for you automatically!
This properties provide an easy way of saying these are the "OK" and "Cancel" buttons on a dialog. It also binds the ESC key to the button.
Reference: MSDN Button.IsCancel property.
For .NET 4.5 SystemCommands class will do the trick (.NET 4.0 users can use WPF Shell Extension google - Microsoft.Windows.Shell or Nicholas Solution).
<Window.CommandBindings>
<CommandBinding Command="{x:Static SystemCommands.CloseWindowCommand}"
CanExecute="CloseWindow_CanExec"
Executed="CloseWindow_Exec" />
</Window.CommandBindings>
<!-- Binding Close Command to the button control -->
<Button ToolTip="Close Window" Content="Close" Command="{x:Static SystemCommands.CloseWindowCommand}"/>
In the Code Behind you can implement the handlers like this:
private void CloseWindow_CanExec(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void CloseWindow_Exec(object sender, ExecutedRoutedEventArgs e)
{
SystemCommands.CloseWindow(this);
}
In the beginning I was also having a bit of trouble figuring out how this works so I wanted to post a better explanation of what is actually going on.
According to my research the best way to handle things like this is using the Command Bindings. What happens is a "Message" is broadcast to everything in the program. So what you have to do is use the CommandBinding. What this essentially does is say "When you hear this Message do this".
So in the Question the User is trying to Close the Window. The first thing we need to do is setup our Functions that will be called when the SystemCommand.CloseWindowCommand is broadcast. Optionally you can assign a Function that determines if the Command should be executed. An example would be closing a Form and checking if the User has saved.
MainWindow.xaml.cs (Or other Code-Behind)
void CloseApp( object target, ExecutedRoutedEventArgs e ) {
/*** Code to check for State before Closing ***/
this.Close();
}
void CloseAppCanExecute( object sender, CanExecuteRoutedEventArgs e ) {
/*** Logic to Determine if it is safe to Close the Window ***/
e.CanExecute = true;
}
Now we need to setup the "Connection" between the SystemCommands.CloseWindowCommand and the CloseApp and CloseAppCanExecute
MainWindow.xaml (Or anything that implements CommandBindings)
<Window.CommandBindings>
<CommandBinding Command="SystemCommands.CloseWindowCommand"
Executed="CloseApp"
CanExecute="CloseAppCanExecute"/>
</Window.CommandBindings>
You can omit the CanExecute if you know that the Command should be able to always be executed Save might be a good example depending on the Application. Here is a Example:
<Window.CommandBindings>
<CommandBinding Command="SystemCommands.CloseWindowCommand"
Executed="CloseApp"/>
</Window.CommandBindings>
Finally you need to tell the UIElement to send out the CloseWindowCommand.
<Button Command="SystemCommands.CloseWindowCommand">
Its actually a very simple thing to do, just setup the link between the Command and the actual Function to Execute then tell the Control to send out the Command to the rest of your program saying "Ok everyone run your Functions for the Command CloseWindowCommand".
This is actually a very nice way of handing this because, you can reuse the Executed Function all over without having a wrapper like you would with say WinForms (using a ClickEvent and calling a function within the Event Function) like:
protected override void OnClick(EventArgs e){
/*** Function to Execute ***/
}
In WPF you attach the Function to a Command and tell the UIElement to execute the Function attached to the Command instead.
I hope this clears things up...
One option that I've found to work is to set this function up as a Behavior.
The Behavior:
public class WindowCloseBehavior : Behavior<Window>
{
public bool Close
{
get { return (bool) GetValue(CloseTriggerProperty); }
set { SetValue(CloseTriggerProperty, value); }
}
public static readonly DependencyProperty CloseTriggerProperty =
DependencyProperty.Register("Close", typeof(bool), typeof(WindowCloseBehavior),
new PropertyMetadata(false, OnCloseTriggerChanged));
private static void OnCloseTriggerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = d as WindowCloseBehavior;
if (behavior != null)
{
behavior.OnCloseTriggerChanged();
}
}
private void OnCloseTriggerChanged()
{
// when closetrigger is true, close the window
if (this.Close)
{
this.AssociatedObject.Close();
}
}
}
On the XAML Window, you set up a reference to it and bind the Behavior's Close property to a Boolean "Close" property on your ViewModel:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<i:Interaction.Behaviors>
<behavior:WindowCloseBehavior Close="{Binding Close}" />
</i:Interaction.Behaviors>
So, from the View assign an ICommand to change the Close property on the ViewModel which is bound to the Behavior's Close property. When the PropertyChanged event is fired the Behavior fires the OnCloseTriggerChanged event and closes the AssociatedObject... which is the Window.