Set Shortcut key for ToggleButton with Image content - wpf

I've created a ToggleButton to show and hide a piece of UI and I've sent its Content to an icon.
I now want to add a shortcut key to the ToggleButton but I'm unsure how to do with without binding a command as well. Since all I am doing is binding to the IsChecked state, I don't need a command to do any other functionality and creating an empty one seems incorrect.
Here is my ToggleButton as it stands currently non-functional and not responding when I press the indicated shortcut key.
<ToggleButton ToolTip="Command History"
MinWidth="24"
IsChecked="{Binding IsShowHistoryChecked}"
Margin="7">
<ToggleButton.InputBindings>
<KeyBinding Gesture="Ctrl+H" />
</ToggleButton.InputBindings>
<Image Source="/Amuse;component/Images/ComHistory256.png"
Width="24" />
</ToggleButton>

An InputBinding on the ToggleButton itself won't do the trick, but there are two good WPF solutions for your problem:
Use a RoutedCommand that updates the model.
Register an access key.
Why your InputBinding solution won't work
The InputBinding you have defined currently won't work because it doesn't list a command. It is easy to create a command that toggles a button, as follows:
public void Execute(object parameter)
{
((ToggleButton)parameter).IsChecked = !((ToggleButton)parameter).IsChecked;
}
However this will not achieve what you are looking for. You want Ctrl-H to toggle your button even when the button is not focused. An InputBinding will not accomplish this for you, since it only works when the button has focus. I will now discuss two solutions you can use.
Option 1: Use a RoutedCommand that updates the model
The whole point of WPF's architecture is that you never will need to "toggle a button" in the first place: Conceptually all keyboard and mouse actions in WPF should serve to toggle a bound property in your model or view model. The ToggleButton then just becomes the mechanism for accepting mouse clicks, but need not be the only one.
The name you chose for your "IsShowHistoryChecked" property indicates a fundamental problem in the way you're conceptualizing your view model. Your view model should not be designed around the view - rather, it should expose logical concepts such as a "ShowHistory" property. The view may bind this to a CheckBox or ToggleButton, or it may choose some other mechanism, or it may not expose it at all. The whole point of data binding and view models is that when you create the view model you don't care what the actual view will be like. In fact, during automated unit testing there will be no checkbox so "IsShowHistoryChecked" would clearly be a real misnomer.
So let's say you've properly separated your view from your view model and you have a "ShowHistory" property. First implement a "ToggleShowHistory" command in your view model that, when executed, toggles the ShowHistory property. Now all you have to do is assign this command an InputBinding of Ctrl-H at the view level and you're done. Even if the ToggleButton is removed from the view entirely the InputBinding will still take effect and Ctrl-H will still work. Nirvanna.
Option 2: Register an access key
Windows has a standard mechanism for associating keys with arbitrary buttons and labels, which is the "access key" concept. If you register an access key of "h" on the ToggleButton, pressing Alt-H will toggle the button, and so will just plain H if you don't have a TextBox or another control accept it first.
It is very simple to register the access key in code:
AccessKeyManager.Register("h", togleButton);
This registers "h" as the access text. Now if the user presses Alt-H anywhere in scope (or plain "h" if isn't handled by a TextBox), your button will toggle.
You can also do it in XAML. If you're showing text in your button, just use an underline before the access key letter:
<Button Text="Show _History" ... />
If you're showing something other than just text, include a hidden AccessText element in your button content:
<Button ...>
<Grid>
<AccessText Text="_h" Visibility="Collapsed" />
<Image ...>
</Grid>
</Button>
In case you're wondering, WPF has no built in mechanism to request that AccessKey registrations respond to Ctrl instead of Alt, so this will not allow you to set Ctrl-H as the access key.

Related

Multiple windows in WPF and the ability to tile them

I am writing a terminal in WPF, which communicates the host with an embedded device through RS232.
I want to be able to open multiple connections that will reside on different tabs, I believe that for that purpose WPF's tabContorl is sufficient, However the customer wants to be able to tile the different tabs on the screen, and as I understand the basic tabControl doesn't allow you to do that.
Any ideas how can this be done?
any help will be appreciated,
Thanks in advance.
Maybe it's overkill, but I would give a try to Avalon Dock from the WPF Toolkit, it's free. With that, you will be able to move terminals around, dock them wherever you wish and even have only opened at a time if you unpin others.
I have made a custom WPF control called ModalContentPresenter that allows you to display modal content which I think will be suitable. The control allows you to conditionally display additional content on top of your primary content.
Your application window will comprise a single ModalContentPresenter which will contain your TabControl in it's primary content and an ItemsControl in it's modal content.
<c:ModalContentPresenter IsModal="{Binding IsTiling}">
<DockPanel>
<Button Content="Show tiled view"
Command={Binding ShowTiledViewCommand}
DockPanel.Dock="Top"/>
<TabControl ItemsSource="{Binding Connections}"> />
</DockPanel>
<c:ModalContentPresenter.ModalContent>
<DockPanel>
<Button Content="Hide tiled view"
Command={Binding HideTiledViewCommand}
DockPanel.Dock="Top"/>
<Itemscontrol ItemsSource="{Binding Connections}" />
</DockPanel>
</c:ModalContentPresenter.ModalContent>
</c:ModalContentPresenter>
By default the modal content will not be displayed so all the user will see is the TabControl.
Both the TabControl and ItemsControl are bound to the same collection of data in your viewModel ensuring that the tiled view is in sync with the items in the tab control.
Your viewModel must have a Boolean property called IsTiling which should return true when you want the 'tiled' view to be shown and false when it is hidden.
When the tiled view is displayed users will be unable to interact with the primary content.
You can change the layout panel used by the ItemsControl to change how the collection of data is 'tiled'.
See this answer for an additional example of how to use the control.
The interface of your viewModel will look something like this:
public interface ViewModel {
public ObservableCollection<Connection> Connections { get; }
public boolean IsTiling { get; }
public ICommand ShowTiledViewCommand { get; }
public ICommand HideTiledViewCommand { get; }
}
Once you have this working you can add some additional enhancements to improve the look and feel of the user interface.
I would start by assigning a custom control template to the TabControl so that a Button is displayed in the 'header' area. This Button is bound to a command in your viewModel which is responsible for changing the IsTiling property to true.
This question provides a link to an answer which explores ways of achieving this.
A second enhancement is to remove the button from the modal content and call the HideTiledViewCommand command when the user selects an item in the items control. You can then add some additional logic which selects the correct tab when the tiled view is closed. I think this can be achieved by having an additional property in your viewModel representing the selected connection.

WPF MVVM Default Focus on Textbox and selectAll

I have situation like this, I have 2 WPF forms developed using MVVM Pattern ..2nd form will be opened from first(form1 will be in backend till form2 is closed) and closing the second makes the first form active.
Now I want to make a textbox on form1 with default focus set on it. I was able to do it with FocusManager and its working fine but the same is not working fine when Im getting into form1 from form2. Also during this time I have to set the focus on the default textbox and also I need to select all the text present on it. I am unable to understand how to do this with viewmodel.
Any suggestions will be of great help for me.
Regards,
Krishna
You can focus a particular UI element using the FocusManager.FocusedElement Attached Property:
<Grid FocusManager.FocusedElement="{Binding ElementName=SomeTextBox}">
<TextBox Name="SomeTextBox" Text="{Binding SomeProperty}" />
</Grid>
This should select the TextBox each time the view/UserControl is loaded.
As for how to select text from the view model... the solution would be the same to handle any event when using MVVM. Wrap it in an Attached Property. Please beware that it is not appropriate to handle all events in the view model, as it should not really have any knowledge of purely UI events. However, the choice is yours.
To 'wrap', or handle any event in an Attached Property, you basically declare a class that extends the DependencyObject class and define one or more static properties. Rather than go over the whole story once again, I'd prefer to direct you to my answer to the What's the best way to pass event to ViewModel? question on Stack Overflow, which provides further links and a full code example.
For background information on Attached Properties, please see the Attached Properties Overview page on MSDN.

ProcessCmdKey analog in WPF

I'm new to WPF and in WinForms to add hotkeys to the form I've usually used ProcessCmdKey that made easy to override(add) key related functionality (similar to the way described here). Is there an easy way to assign hotkeys in WPF? I'm using Commands with keys, but sometimes that doesn't work (I think some other controls on window respond to that gestures and do their jobs, so that my command can't respond to predefined key gesture).
InputBindings are scoped with the control they are assigned to, if you assign a KeyBinding in the Window.InputBindings they will be fired throughout the window unless the same gesture is locally overridden by being defined somewhere down the tree.
For example this:
<UserControl.InputBindings>
<KeyBinding Gesture="CTRL+SHIFT+N"
Command="{Binding BtnNewChild_Command}"
CommandParameter="{Binding ElementName=view}" />
</UserControl.InputBindings>
doesnt work, it cant pass the view element as parameter, tho there is control named "view" in that UserControl. Does wpf have some "common" hotkey assign scenario?
The problem here would just be scope, the child controls have access to things declared higher up on the tree but not the other way around. You could possibly refactor this to create your view as a resource in the UserControl.Resources then you can reference it both in the CommandParameter and whereever you use it in the UserControl.

MVVM Command without A Button

I want to initiate a bound command, but I don't want to use a Button on my UserControl. In effect the UserControl is itself a Button. When it is clicked anywhere on its surface I want to initiate a bound command. Is there a way to give a UserControl a command?
In a side note: one command for one control and only a few certain out-of-the-box controls? This all seems a little clunky. I'm starting to think that MVVM is impractical. I can decouple my UI just fine with Interfaces and OOP. Anyway, I still have hope.
Also, I'm not willing to hack anything or use an expensive workaround. If I can't do this, I'm abandoning MVVM.
Take a look at the ICommandSource interface here: http://msdn.microsoft.com/en-us/library/system.windows.input.icommandsource.aspx. If you want a control to have a command, then your control should implement this interface. Examples of controls that implement this interface are ButtonBase and MenuItem. Hope this helps.
If your UserControl is essentially a Button, why are you writing your own UserControl instead of using the Button class?
To add more info, here's what you do:
Subclass Button, put any extra DependencyProperties that you need in there - it should be a very empty class (you could even have something like public class MyCoolButton : Button { }
Add a Style whose TargetType is MyCoolButton - don't name the style so it applies to all MyCoolButtons
Override the default Template of the style, then paste in your Xaml code. You might have to do some work here to handle the "Normal / Pushed / Disabled" states. If you're using v4.0, you can use VSM here.
I will agree with Paul Betts.
Quite often I create my own ListBoxItemContainerStyle using a button as the top container with nothing but a propertyless content presenter in it. This allows me to use the buttons functionality (like Command) without having the Windows chrome on it.
Putting it in the ListBoxItemContainerStyle also lets me make it so that when it is clicked it does not display the normal dotted border (FocusVisualStyle={x:Null}).
Are you using Visual Studio or Expression Blend to do your styling?
Additionally, some MVVM frameworks provide an interface for adding a command-ish ability to controls other than buttons. Caliburn has a pretty rich command pattern. I am not sure if it allows binding commands on non-button controls, however.
The OP asked for an example of how you could use a button control, but with the content properly filling the entire button. You can do this using the ContentAlignment properties:
<Button HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<Button.Content>
<Grid IsHitTestVisible="False">
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Row0" />
<TextBlock Grid.Row="1" Text="Row1" />
</Grid>
</Button.Content>
</Button>
This creates a button with two labels spaced using a Grid control. I mark the Grid to turn off HitTestVisible, as you have to decide which controls should interact like the button and which should interact like controls. For instance, you might have an embedded TextBox that you want to be clickable without clicking the button, in which case it should have HitTestVisible=True.
WPF supports layers and transparency :
Panel.ZIndex
You can create anything that supports commanding on a superior transparent layer, the size you want, to act as a button.

Setting a property on the ViewModel from the View in WPF

I have a dependency property on my ViewModel which is the DataContext for my View. The ViewModel has no reference to the View. The property on the ViewModel is going to reference a control on the view, but I need to be able to set this property in XAML.
How is this possible? One thought I had was to develop a custom control which has a Property property and a Value property, so you could do something like this in the View to set the property on the ViewModel:
<PropertySetter Property="{Binding MyViewModelDependencyProperty}" Value="{Binding ElementName=aControlOnMyView" />
Before I went down this route, I wanted to check if there was any other approach I could take?
Thanks for the detailed reply Ray, but if I give you a bit more detail about the problem I'm trying to solve, you might get a better idea of why I mentioned the approach I did.
Basically, what I'm trying to do is set the focus to a textbox when the user hits a button. I've written an attached property which you can attach to the Button control, specify what the trigger event is (in this case the 'Click' event), and then what control to focus on. This works really nicely, and keeps everything in XAML.
However, I now have a use case where the focus should be set to an arbitrary text box from the click event on a button which is part of a toolbar. This toolbar is itself a user control which is sitting inside another user control, which is inside another user control! This toolbar needs to be reusable across various different forms, and each time, the control to set focus on after you click the button will be different per form.
That's why I had the idea of making the focus control (i.e. a textbox) a property on the view model itself (on my ViewModel base to be precise), and have the ViewModel base code (which the toolbar is bound to), set the focus to the control when the button is clicked (and the e.g. Add/Edit method is called on the ViewModel base).
In unit test land, the control to focus on property will be null, so it's .Focus() method just won't be called. So I can't see an issue there. My problem is then how you set the focus control property from XAML, which is why I had the PropertySetter idea.
I don't like the fact that the ViewModel has any reference to controls sitting on the view, but I can't see another way to achieve what I need. What if the logic that dictates whether to set focus to the control is quite complex? This would sit in the ViewModel surely? Therefore, is there any harm in the ViewModel having this UIElement property? It still knows nothing about the specific View it is bound to, it just knows that there is a control which it needs to set focus to when some action happens on the ViewModel.
My first reaction (and it's a strong one) is so say "Don't do that!" By giving your view model a reference to a part of your UI you are breaking the encapsulation that makes view models so powerful and useful.
For example, what if you want to unit test your view model or serialize it to disk? In each case the piece of your UI will not be present, because there will be no view at all. Your tests will miss coverage and your reconstitution will be incomplete.
If your view model actually needs references to UI objects and there is no better way to architect it, the best solution is to have the view model itself construct those controls it requires a reference to. Then your view can incorporate that control as the Content of a ContentPresenter via binding and provide a Style to configure the control, including a ControlTemplate to provide its content. Thusly:
public class MyViewModel
{
public ListBox SpecialControl { get; set; }
public MyViewModel()
{
SpecialControl = new ListBox();
}
}
and
<DataTemplate TargetType="{x:Type local:MyViewModel}">
<DataTemplate.Resources>
<Style TargetType="ListBox" ... />
</DataTemplate.Resources>
...
<ContentPresenter Content="{Binding SpecialControl}" />
</DataTemplate>
Other possibilities are:
Have the view model actually derive from the Control class, then override OnApplyTemplate() and use GetTemplateChild to find a template item whose name starts with "PART_"
Implement an attached property that takes a property name, finds that property in the DataContext, and sets it to the DependencyObject to which the property is attached.
Implement your PropertySetter idea
My option #2 would look like this:
<DataTemplate TargetType="{x:Type MyViewModel}">
...
<TextBox local:PropertyHelper.SetViewModelToThis="SpecialControl" />
...
</DataTemplate>
The code in the SetViewModelToThis PropertyChangedCallback would get the view model from the DataContext, reflect on it to find the "SpecialControl" property, then set it to the TextBox. Note that the implementation of SetViewModelToThis must take into account the possiblity that DataContext is not set right away, and that it maybe changed requiring the old setting to be removed and a new one made.
First of all, the DataContext of the control should be the ViewModel object and not a property of it. Second, when you TwoWay bind a property of ViewModel to your control, changes in the control's value will update (in your case, 'set') the value of ViewModel's property.

Resources