Global event handler - wpf

How can I define one global GridViewColumnHeader.Click handler for any ListView.GridViewColumnHeader in my project?
I mean is it possible to set a Style or a Template in app.xaml for TargetType=GridViewColumnHeader so any columnheader in any listview in the project would response to the method in app.xaml.cs?

Though it isn't a global event handler, I would simply create a new control that inherits from ListView and implement the Click handler there.

Yes, with one caveat: You can create a Style that applies to all GridViewColumnHeaders, but you cannot set the Click event in it. However you can set the Command property which has almost exactly the same result:
<Application.Resources>
<Style TargetType="GridViewColumnHeader">
<Setter Property="Command"
Value="{x:Static local:GridViewClickHandler.ClickCommand}" />
</Style>
...
Now it is only required to create the command, register a class handler, and write the code to handle it:
public GridViewClickHandler
{
public RoutedCommand ClickCommand;
static GridViewClickHandler()
{
ClickCommand = new RoutedCommand("ClickCommand", typeof(GridViewClickHandler));
CommandManager.RegisterClassCommandBinding(
typeof(GridViewColumnHeader),
new CommandBinding(ClickCommand, OnColumnHeaderClick));
}
static void OnColumnHeaderClick(object sender, ExecutedRoutedEventArgs e)
{
// your code here
}
}
Note that if you manually set the GridViewColumnHeader's Command property anywhere else in your application it will take precedence over the style. If this is a concern, you may want to instead catch tunneling PreviewMouseDown events at your Window and check each to see if it's original source is a GridViewColumnHeader.

Related

How to translate CodeBehind WPF Events; Event, Handler, EventSetter to MVVM pattern?

I am trying to translate WPF CodeBehid events like Event, Handler, EventSetter to MVVM pattern. I am not allowed to use System.Windows.Controls since I am using MVVM. And I am also avoiding 3rd party library to solve this issue.
Can somebody explain how to convert the following CodeBehind Event Handler to MVVM Event-Handler? Please explain as much as you can while writing answer.
XAML Code
<DataGridCheckBoxColumn Header="Select" Binding="{Binding Path=IsSelected}">
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<EventSetter Event="MouseLeftButtonUp" Handler="ApprovedMouseUp"></EventSetter>
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
Code Behind
private void ApprovedMouseUp(object sender, MouseButtonEventArgs e)
{
if(sender is DataGridCell)
{
var temp = (sender as DataGridCell).Content;
if(temp is CheckBox) (temp as CheckBox).IsChecked = !(temp as CheckBox).IsChecked;
}
}
There are few thumb rules regarding MVVM....
Your Models and ViewModles should not refer System.Windows.Controls namespace.
Your Models and ViewModles should not handle events. Use ICommand interface for that.
RoutedCommand is not valid in Models / ViewModels (due to point 2). Hence use DelegateCommand / RelayCommand
Having said that, all the above points are perfectly allowed if you have written an Attached Behavior in MVVM.
You have a couple of choices:
Attach the event handler in XAML but the only thing the event handler does is call into the view model passing in the appropriate arguments (it's important not to pass any GUI level items to the view model -- just the data necessary to perform the action)
Use the EventToCommand behavior (showcased here) to attach an instance of an ICommand (from your view model) to an event in your view
As long as you're not trying to set these event handlers up in styles or templates I would recommend pursuing option #1 -- there is no iron law prohibiting you from using event handlers when convenient, as long as the view model is what actually performs all the work
Edit: Option #1
private void ApprovedMouseUp(object sender, MouseButtonEventArgs e)
{
if(sender is DataGridCell)
{
var checkBox= (sender as DataGridCell).Content as CheckBox;
if(checkBox != null)
{
var viewModel = (MyViewModel)checkBox.DataContext;
viewModel.ToggleApprovedStatus();
}
}
}
You can also use Caliburn Micro libraries to be able to attach a handler in ViewModel to an event in View.
Sample code:
... xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"....
<Button Content="Edit" DataContext="{Binding Path=VmInstance}"
cal:Message.Attach="[Event Click] = [Action EditFilter]" />

How to access command from MainWindow level in another Window?

I am trying to access commands that are defined in MainWindow.xaml in another window. I am only able to get grayed out titles of these commands. I am wondering what should be should be done in order to get a full access.
Sample of the command:
public partial class MainWindow : Window
{
public static RoutedUICommand AddCommand1 = new RoutedUICommand("Command ", "command1", typeof(MainWindow));
public MainWindow()
{
InitializeComponent();
this.CommandBindings.Add(new CommandBinding(AddCommand1, AddCommand1Executed));
}
private void AddCommand1Executed(object sender, ExecutedRoutedEventArgs e)
{
AddNewItem picker = new AddNewItem();
picker.ShowDialog();
}
I access these command in style through databinding:
<Menu x:Name="TaskMenuContainer"><MenuItem x:Name="menuItem" Header="TASKS" ItemsSource="{Binding}" Template="{DynamicResource TaskMenuTopLevelHeaderTemplateKey}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding}" />
<Setter Property="Header" Value="{Binding Text, RelativeSource={RelativeSource Self}}" />
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource Self}}"/>
</Style>
</MenuItem.ItemContainerStyle>
These commands work in pages that is loaded inside MainWindow.xaml through frame. However, if I have pop up window that is not part of MainWindow.xaml these commands are only grayed out and not functional anymore (cannot be executed). Any advice is highly appreciated!
The way you define the command, you define it for a particular window. If you want to handle the command globally, at the application level, you can use CommandManager.RegisterClassCommandBinding:
First, define you command in a separate static class:
public static class GlobalCommands
{
public static RoutedUICommand AddCommand1 = new RoutedUICommand("Command ", "command1", typeof(MainWindow));
}
Then, in you window or whatever place you want to put the command logic, register the command handlers:
public partial class MainWindow : Window
{
static MainWindow()
{
CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(GlobalCommands.AddCommand1, AddCommand1Executed, CanAddExecute));
}
private static void AddCommand1Executed(object sender, ExecutedRoutedEventArgs e)
{
AddNewItem picker = new AddNewItem();
picker.ShowDialog();
}
}
And in your menu style you should change the binding to x:Static:
<Setter Property="Command" Value="{x:Static my:GlobalCommands.AddCommand1}" />
When the command is routed, when checking for command bindings in each active element in the UI, the bindings registered for each element's class will also be checked. By registering the binding here, you can cause every instance of a class to be able to handle the specific command.
So, in the above example, the type Window is used and this will cause the routing to find the command binding in any instance of Window, once the routing reaches that instance in its search for a command binding.
You could instead, for example, restrict the handling to a specific subclass of Window, so that the command will only be bound in an instance of that subclass. Or you can use some other UI element type, so that that the presence of that specific type of element will cause the event to be handled. Just set the owning type for the registered command binding appropriately for your specific needs.

Mouse interaction in ListBoxItem children (WPF)

I have a ListBox with an ItemTemplate that contains a control that interacts with the mouse. This interfers with the selection functionality of the ListBox, i.e. clicking a control does not select the item. This is because ListBoxItem sets the Handled property of the mouse event to true in OnMouseLeftButtonDown. I tried the following
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
base.OnMouseLeftButtonDown(e);
e.Handled = false;
}
but the ListBoxItem “takes over” the mouse and prevents the control from doing its own interaction. Then I had another idea
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
base.OnMouseLeftButtonDown(e);
((ListBoxItem)VisualTreeHelper.GetParent(VisualTreeHelper.GetParent(VisualTreeHelper.GetParent(this)))).IsSelected = true;
}
which actually works, but feels more like an ugly kludge than an elegant solution. Are there any better solutions that don't rely on the exact contents of the visual tree?
I've found a way that is less of a kludge:
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
base.OnMouseLeftButtonDown(e);
Selector.SetIsSelected(this, true);
}
For this to have any effect, the control in the ListBox' ItemTemplate needs the following XAML attribute:
Selector.IsSelected="{Binding IsSelected, Mode=OneWayToSource, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}"
It raises two new questions:
Would it be better to define my own dependency property rather than finding an attached one that isn't currently in use?
Is there a way to achieve something similar in markup only?
I believe the MouseLeftButtonDown is a tunnelling event: you could try using PreviewMouseLeftButtonDown, doing your processing there, then ensuring e.Handled = false; as you tried already - that should do the trick!
Hope that helps.
Here is one simple solution, but unfortunately handler can be attached only in code, not in markup.
Event handler can be added by using handledEventsToo signature of AddHandler method:
myListBox.AddHandler(UIElement.MouseDownEvent,
new MouseButtonEventHandler(ListBox_MouseDown), true);
Third parameter above is handledEventsToo which ensures that this handler will be invoked no matter if it is already marked as Handled (which ListBoxItem does in ListBox).
See Marking Routed Events as Handled, and Class Handling for explanation.
See How to Attach to MouseDown Event on ListBox for example.

How to bind a ComboBoxItem's IsEnabled property to the result of a Command's CanExecute method

I've a custom SplitButton implementation in which contains a ComboBox with several ComboBoxItems bound to commands. I can bind to the Name, and Text properties of the command just fine but have no way of binding the ComboBoxItem's IsEnabled property to the result of a Command's CanExecute method because it is a method. Is there some syntax that I'm unaware of for binding to methods or is there some trickery that will help me to bind to CanExecute.
By the way, I've thought about using a custom ValueConverter except for that I realized that I probably wouldn't receive any updates when CanExecute is re-evaluated since it is not a property and since my commands are not business objects. It's looking to me that I might have to create a ViewModel for a command at this point to use only within my custom SplitButton control but that seems a little overboard to me.
You can put a button(if you dont have one in the controltemplate bound to the ICommand) inside ItemContainerStyle(ComboBoxItem style) and Bind the command to it
And add a Trigger to check the Button.IsEnabled and set that value to the ComboBoxItem. So here we used Button as a CommandSource just to get the IsEnabled from CanExeute. You can set the button's height and width to zero
<ControlTemplate....>
<Grid ...
<Button x:Name="dummyButton" Command="{Binding YourCommand}" ....
......
</Grid>
<ControlTemplate.Triggers>
<Trigger SourceName="dummyButton" Property="IsEnabled" Value="False">
<Setter Property="IsEnabled" Value="False"/>
</Trigger>
</ControlTemplate.Triggers>
Another solution by ViewModel. Below is how I used a ViewModel to solve my problem. And please note that the nifty NotifyPropertyChanged method is part of my base ViewModel class.
public class RoutedUICommandViewModel : ViewModel
{
private RoutedUICommand _command;
private IInputElement _target;
public string Name { get { return _command.Name; } }
public string Text { get { return _command.Text; } }
public bool CanExecute
{
get
{
return _command.CanExecute(null, _target);
}
}
public RoutedUICommand Command { get { return _command; } }
public RoutedUICommandViewModel(ReportCommand command, IInputElement target)
{
_command = command;
_target = target;
_command.CanExecuteChanged += _command_CanExecuteChanged;
}
private void _command_CanExecuteChanged(object sender, EventArgs e)
{
base.NotifyPropertyChanged(() => this.CanExecute);
}
}
I found this discussion on MSDN forums where Dr. WPF had recommended the use of an attached behavior to solve this exact problem. He gave the example below of how it would be used.
<Grid behaviors:CommandBehaviors.EnablingCommand="{x:Static commands:testcommand.test}">
. . .
</Grid>
Although this solution seems pretty nice I haven't been able to devote the time to understand exactly how this type of behavior would be implemented and what is involved. If anybody would like to elaborate please do otherwise I'll amend this answer with more details if I get the chance to explore this option.
The way I solved this problem in my code was to add an event handler on the ComboBox for the PreviewMouseDown event. Here's the handler:
private void comboBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
ViewModel vm = this.DataContext as ViewModel;
if (vm != null)
{
if (!vm.CanChangeSelection())
{
e.Handled = true;
vm.RespondToFailedAttemptChangeUnits();
}
}
}
This works great for me in the case that I only need to do this in one location. It might get a little tedius if I had many pages like this.
Also, though I follow the MVVM pattern, I'm not a purist - I consider this to be a good practical solution that follows the spirit of MVVM, if not the letter.

How to capture a mouse click on an Item in a ListBox in WPF?

I want to get notified when an item in a ListBox gets clicked by the mouse, whether it is already selected or not.
I searched and found this: (http://kevin-berridge.blogspot.com/2008/06/wpf-listboxitem-double-click.html see the comments)
private void AddDoubleClickEventStyle(ListBox listBox, MouseButtonEventHandler mouseButtonEventHandler)
{
if (listBox.ItemContainerStyle == null)
listBox.ItemContainerStyle = new Style(typeof(ListBoxItem));
listBox.ItemContainerStyle.Setters.Add(new EventSetter()
{
Event = MouseDoubleClickEvent,
Handler = mouseButtonEventHandler
});
}
//Usage:
AddDoubleClickEventStyle(listView1, new MouseButtonEventHandler(listView1_MouseDoubleClick));
This works, but it does it for a DoubleClick. I can't get it working for a single click though. I tried MouseLeftButtonDownEvent - as there doesn't seem to be a MouseClick event, but it's not being called.
A bit more general side question: How can I see what events do exist and which handlers correspond to them and when they actually do something? For example, what tells me that for a MouseDoubleClickEvent I need a MouseButtonEventHandler? Maybe for a MouseLeftButtonDownEvent I need some other handler and that's why it's not working?
I also tried subclassing ListBoxItem and override OnMouseLeftButtonDown - but it doesn't get called either.
Marc
I believe that your MouseLeftButtonDown handler is not called because the ListBox uses this event internally to fire its SelectionChanged event (with the thought being that in the vast majority of cases, SelectionChanged is all you need). That said, you have a couple of options.
First, you could subscribe to the PreviewLeftButtonDown event instead. Most routed events have a routing strategy of Bubbling, which means that the control that generated the event gets it first, and if not handled, the event works its way up the visual tree giving each control a chance at handling the event. The Preview events, on the other hand, are Tunneling. This means that they start at the root of the visual tree (generally Window), and work their way down to the control that generated the event. Since your code would get the chance to handle the event prior to the ListBoxItem, this will get fired (and not be handled) so your event handler will be called. You can implement this option by replacing MouseDoubleClickEvent in your sample with PreviewMouseLeftButtonDown.
The other option is to register a class handler that will be notified whenever a ListBoxItem fires the MouseLeftButtonDown event. That is done like this:
EventManager.RegisterClassHandler(typeof(ListBoxItem),
ListBoxItem.MouseLeftButtonDownEvent,
new RoutedEventHandler(this.MouseLeftButtonDownClassHandler));
private void OnMouseLeftButtonDown(object sender, RoutedEventArgs e)
{
}
Class Handlers are called before any other event handlers, but they're called for all controls of the specified type in your entire application. So if you have two ListBoxes, then whenever any ListBoxItem is clicked in either of them, this event handler will be called.
As for your second question, the best way to know what type of event handler you need for a given event, and to see the list of events available to a given control, is to use the MSDN documentation. For example, the list of all events handled by ListBoxItem is at http://msdn.microsoft.com/en-us/library/system.windows.controls.listboxitem_events.aspx. If you click on the link for an event, it includes the type of the event handler for that event.
There is also another way - to handle PreviewMouseDown event and check if it was triggered by the list item:
In XAML:
<ListBox PreviewMouseDown="PlaceholdersListBox_OnPreviewMouseDown"/>
In codebehind:
private void PlaceholdersListBox_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var item = ItemsControl.ContainerFromElement(sender as ListBox, e.OriginalSource as DependencyObject) as ListBoxItem;
if (item != null)
{
// ListBox item clicked - do some cool things here
}
}
Was inspired by this answer, but it uses listbox by name, I propose to use sender argument to avoid unnecessary dependencies.
I think the first option in Andy's answer, of using PreviewMouseLeftButtonDown, is the way to go about this. In XAML it would look like this:
<ListBox Name="testListBox">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter
Event="PreviewMouseLeftButtonDown"
Handler="ListBox_MouseLeftButtonDown" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
There is another way to get MouseDown event in ListBox. You can add event handler for events that are marked as handled by using handledEventsToo signature of AddHandler method:
myListBox.AddHandler(UIElement.MouseDownEvent,
new MouseButtonEventHandler(ListBox_MouseDown), true);
Third parameter above is handledEventsToo which ensures that this handler will be invoked no matter if it is already marked as Handled (which ListBoxItem does in ListBox).
See Marking Routed Events as Handled, and Class Handling for explanation.
See How to Attach to MouseDown Event on ListBox for example.
You can use Event="MouseLeftButtonUp"
Unlike "PreviewLeftButtonDown" it will get the ListBoxItem handled too.
You can use the SelectionChangedEventArgs argument of the SelectionChanged event to find what item is add or removed through AddedItems and RemovedItems, usually only have the latest clicked on, or if not, then look at the last item which is the count-1.

Resources