I have a WPF TreeView with XAML shown below:
<TreeView x:Name="twElements">
<TreeView.Resources>
<v8r:IconTypeConverter x:Key="IconConverter"/>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="twElements_MouseDoubleClick" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildItems}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Icon, Converter={StaticResource IconConverter}}"/>
<TextBlock Text="{Binding Text}" Margin="3,0,0,0"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
The task is to open some form after double click on a child item.
code-behind for DoubleClick event:
private void twElements_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.Source is TreeViewItem && ((TreeViewItem)e.Source).IsSelected)
{
e.Handled = true;
var twi = (TreeViewItem)e.Source;
var Editable = twi.Header as IEditable;
if (Editable != null)
{
Window Editor = Editable.GetEditor();
Editor.Show(); // Editor is a WPF.Window
}
}
}
The problem: After desired window is opened, form with a treeview activates itself, making new window to go background.
How to make new window to remain active?
You probably need to let WPF finish the job of handling the current mouse click event(s) before you open the new Window. Let the new window be the next UI job by adding it to the current Dispatcher's queue like this:
(...)
//Editor.Show();
Action showAction = () => Editor.Show();
this.Dispatcher.BeginInvoke(showAction);
in constructor of new form set
this.Focus();
Also, does your new form should be Modal window? if yes use Editor.ShowDialog() instead of Editor.Show(); It will automatically solve issue with focus
Related
I am displaying a modal dialog when the user double taps inside a TextBlock. Its a backdoor to a hidden settings panel. The TextBlock is contained in a Popup. I am finding that it is always necessary to tap/touch once before any of the buttons in the dialog do anything. Kind of annoying. Initially was using MouseLeftButtonDown and ClickCount==2 on the TextBlock but have also tried the DoubleClickEvent of a Label and their Preview... versions. Nothing I have tried fixes this problem.
<TextBlock Foreground="Black" FontSize="16" Grid.ColumnSpan="4" HorizontalAlignment="Center" MouseLeftButtonDown="TextBlock_MouseLeftButtonDown" VerticalAlignment="Center" Padding="0,10,0,10">
<Run Text="{x:Static languages:Strings.SoftwareVersion}"/>
<Run Text="{Binding SoftwareBuild}"/>
<Run Text=" "/>
<Run Text="{x:Static languages:Strings.FirmwareVersion}"/>
<Run Text="{Binding FirmwareBuild}"/>
</TextBlock>
private void TextBlock_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
try
{
if (e.ClickCount == 2)
{
var sw = new SettingsWindow();
sw.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
sw.DataContext = this.DataContext;
sw.ShowDialog();
}
}
catch (Exception ex)
{
ex.HandleException();
}
}
I've made a correction to the title as the problem is only with touch/tap behaviour; the first mouse click does get through only the first touch fails.
Added more code for the SettingsWindow as requested:-
<Window x:Class="XXXXXXX.UserInterface.Views.Miscellaneous.SettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="SettingsWindow" Height="150" Width="200">
<Grid>
<Button Content="Click Me" Click="Button_Click" VerticalAlignment="Center" HorizontalAlignment="Center" Height="48" Margin="50,30,37,41" Width="105" />
</Grid>
</Window>
public partial class SettingsWindow : Window
{
public SettingsWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
(sender as Button).Background = new SolidColorBrush(Colors.Red);
(sender as Button).Content = "WAS CLICKED";
}
}
Ive tried focus setting in the load and activate event of the dialog to no avail. Even tried raising a MouseClick event using RaiseEvent.
Same problem if I only count 1 click too but with 1 click the problem goes away if I count MouseButtonUp events not MouseButtonDown, but then I lose the ClickCount==2 option which doesnt work at all. Somewhere a MouseButtonUp event is getting lost.
So I'm about to manage my own double clicking to get this to work ...
but suggestions welcome as to why this breaks.
Jerry
I use this article to create multi-select ComboBox.
I want to select/unselect the CheckBox inside each item when pressing the Space button.
I tried to add PreviewKeyDown for the CheckBox but the event doesn't get raised.
I also tried adding PreviewKeyDown in the StackPanel but then I can't get the selected item that it's CheckBox is currently checked.
You need to handle KeyUp of the combobox and make sure that the dropdown is open.
Update:
<Grid>
<ComboBox x:Name="cbo" KeyUp="ComboBox_KeyUp" Height="30" Width="200">
<CheckBox Content="checkbox1"/>
<CheckBox Content="checkbox2"/>
<CheckBox Content="checkbox3"/>
<CheckBox Content="checkbox4"/>
<CheckBox Content="checkbox5"/>
</ComboBox>
</Grid>
///////////////////////////
private void ComboBox_KeyUp(object sender, KeyEventArgs e)
{
if (cbo.IsDropDownOpen)
{
// select first and second
(cbo.Items[0] as CheckBox).IsChecked = true;
(cbo.Items[1] as CheckBox).IsChecked = true;
}
}
I use EventSetter.
<ComboBox.Resources>
<Style TargetType="{x:Type ComboBoxItem}">
<EventSetter Event="PreviewKeyDown" Handler="EventSetter_OnHandler" />
</Style>
</ComboBox.Resources>
and in code behind.
private void EventSetter_OnHandler(object sender,KeyEventArgs e)
{
var item=((ComboBoxItem)sender).DataContext as Node;
item.IsSelected=!item.IsSelected;
}
I want to delete my listbox item on right click. But, right click event not worked in my case.
Below is the code which I tried.
In constructor:
listBox1.MouseDown += new MouseButtonEventHandler(listBox1_MouseRightClick);
Right Click:
private void listBox1_MouseRightClick(object sender, MouseButtonEventArgs e)
{
if (sender is ListBoxItem)
{
ListBoxItem item = (ListBoxItem)sender;
Harvest_TimeSheetEntry entryToDelete = (Harvest_TimeSheetEntry)item.DataContext;
MessageBoxResult Result = System.Windows.MessageBox.Show("Are you sure?", "Delete Confirmation", System.Windows.MessageBoxButton.YesNo);
if (Result == MessageBoxResult.Yes)
{
Globals._globalController.harvestManager.deleteHarvestEntry(entryToDelete);
}
else
{
System.Windows.MessageBox.Show("Delete operation Terminated");
}
}
}
In xaml:
<ListBox x:Name="listBox1" ItemsSource="{Binding}" Margin="0,131,0,59" ItemTemplateSelector="{StaticResource templateSelector}" SelectionMode="Single" MouseRightButtonDown="listBox1_MouseRightClick">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="MouseDown" Handler="listBox1_MouseRightClick"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Please suggest, how should I use right click event so it could work?
Your original code seems redundant and verbose. MouseRightButtonDown isn't working because there is already an event handling listbox item selection and the ListBoxItem datacontext is simply the SelectedItem of listBox1.
Get rid of overriding the style and just declare the listbox with the preview event. This will tunnel MouseRightButtonDown instead of bubble it.
<ListBox x:Name="listBox1"
ItemsSource="{Binding}"
ItemTemplateSelector="{StaticResource templateSelector}"
Margin="0,131,0,59"
SelectionMode="Single"
PreviewMouseRightButtonDown="listBox1_MouseRightClick" />
In the constructor, get rid of this
listBox1.MouseDown += new MouseButtonEventHandler(listBox1_MouseRightClick);
Now in the event handler, sender is your listbox1 but if you're not tying this event to other listboxes, simply get the selectedItem from listbox1 and cast it to the appropriate object. Otherwise if you decide you want the functionality on multiple listboxes cast sender to ListBox
private void listBox1_MouseRightClick(object sender, MouseButtonEventArgs e)
{
Harvest_TimeSheetEntry entryToDelete = (Harvest_TimeSheetEntry)listBox1.SelectedItem;
if(entryToDelete != null)
{
//Do work
}
}
Deleting records on right click is not a good design and it leads users make more confuse the functionality. Still if you want to do something, then you can go for the PreviewMouseRightButtonDown event. Please see the below snippet
ListBox1.PreviewMouseRightButtonDown += new MouseButtonEventHandler(ListBox1_MouseRightButtonDown);
Change your XAML as follows
<ListBox x:Name="listBox1"
ItemsSource="{Binding}"
Margin="0,131,0,59"
ItemTemplateSelector="{StaticResource templateSelector}"
SelectionMode="Single">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="ListBox1_PreviewMouseRightButtonDown" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
I am trying to do something that seems relatively simple and logic from a user interface level but I have one bug that is very annoying. I have a ToggleButton and I am trying to show a Popup when the button is toggled in and hide the Popup when the button is toggled out. The Popup also hides when the user clicks away from it.
Everything is working as expected with the following XAML except when I click the toggle button after the Popup is shown, the Popup disappears for a split second then reappears.
I suspect what's going on here is that clicking away from the Popup is causing it to toggle the button off then immediately after the button is toggled back on as the mouse clicks it. I just don't know how to go about fixing it.
Any help is appreciated. Thanks.
<ToggleButton x:Name="TogglePopupButton" Content="My Popup Toggle Button" Width="100" />
<Popup StaysOpen="False" IsOpen="{Binding IsChecked, ElementName=TogglePopupButton, Mode=TwoWay}">
<Border Width="100" Height="200" Background="White" BorderThickness="1" BorderBrush="Black">
<TextBlock>This is a test</TextBlock>
</Border>
</Popup>
Stephans answers has the disadvantage, that the desired behaviour of closing the popup whenever it loses focus also disappears.
I solved it by disabling the toggle-button when the popup is open. An alternative would be to use the IsHitTestVisible Property instead of is enabled:
<ToggleButton x:Name="TogglePopupButton" Content="My Popup Toggle Button" Width="100" IsEnabled="{Binding ElementName=ToggledPopup, Path=IsOpen, Converter={StaticResource BoolToInvertedBoolConverter}}"/>
<Popup x:Name="ToggledPopup" StaysOpen="False" IsOpen="{Binding IsChecked, ElementName=TogglePopupButton, Mode=TwoWay}">
<Border Width="100" Height="200" Background="White" BorderThickness="1" BorderBrush="Black">
<TextBlock>This is a test</TextBlock>
</Border>
</Popup>
The converter looks like this:
public class BoolToInvertedBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool)
{
bool boolValue = (bool)value;
return !boolValue;
}
else
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException("ConvertBack() of BoolToInvertedBoolConverter is not implemented");
}
}
Solution without IValueConverter:
<Grid>
<ToggleButton x:Name="TogglePopupButton" Content="My Popup Toggle Button" Width="100" >
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="IsHitTestVisible" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Popup, Path=IsOpen}" Value="True">
<Setter Property="IsHitTestVisible" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
<Popup StaysOpen="false" IsOpen="{Binding IsChecked, ElementName=TogglePopupButton, Mode=TwoWay}"
PlacementTarget="{Binding ElementName=TogglePopupButton}" PopupAnimation="Slide"
x:Name="Popup">
<Border Width="100" Height="200" Background="White" BorderThickness="1" BorderBrush="Black">
<TextBlock>This is a test</TextBlock>
</Border>
</Popup>
</Grid>
I faced the same problem. None of the answers offered here worked correctly.
After a little research, I can say that the suspicions of the author of the question are correct. During a mouse click, the first click (down) closes the popup and set togglebutton as unchecked, the second click (up) causes the observed action when the popup appears again.
The first way to avoid this problem is to discard the second click by delay:
<ToggleButton x:Name="UserPhotoToggleButton"/>
<Popup x:Name="UserInfoPopup"
IsOpen="{Binding IsChecked, ElementName=UserPhotoToggleButton, Delay=200, Mode=TwoWay}"
StaysOpen="False">
It looks simple enough to fix problem. Although it is not an ideal solution. The best way would be to extend the functionality of the popup by Behavior:
Add these namespaces
xmlns:behaviors="clr-namespace:WpfClient.Resources.Behaviors;assembly=WpfClient.Resources"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
then extend your popup by i:Interaction.Behaviors
<Popup x:Name="UserInfoPopup"
StaysOpen="False">
<i:Interaction.Behaviors>
<behaviors:BindToggleButtonToPopupBehavior
DesiredToggleButton="{Binding ElementName=UserPhotoToggleButton}"/>
</i:Interaction.Behaviors>
<Border>
<!--Your template-->
</Border>
</Popup>
Finally add the behavior. In a minimal form, it may look like this:
using Microsoft.Xaml.Behaviors;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace WpfClient.Resources.Behaviors
{
public class BindToggleButtonToPopupBehavior : Behavior<Popup>
{
public ToggleButton DesiredToggleButton
{
get { return (ToggleButton)GetValue(DesiredToggleButtonProperty); }
set { SetValue(DesiredToggleButtonProperty, value); }
}
public static readonly DependencyProperty DesiredToggleButtonProperty =
DependencyProperty.Register(nameof(DesiredToggleButton), typeof(ToggleButton), typeof(BindIconToggleButtonToPopupBehavior), new PropertyMetadata(null));
protected override void OnAttached()
{
base.OnAttached();
DesiredToggleButton.Checked += DesiredToggleButton_Checked;
DesiredToggleButton.Unchecked += DesiredToggleButton_Unchecked;
AssociatedObject.Closed += AssociatedObject_Closed;
AssociatedObject.PreviewMouseUp += AssociatedObject_PreviewMouseUp;
}
private void DesiredToggleButton_Unchecked(object sender, RoutedEventArgs e) => AssociatedObject.IsOpen = false;
private void DesiredToggleButton_Checked(object sender, RoutedEventArgs e) => AssociatedObject.IsOpen = true;
private void AssociatedObject_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (e.Source is Button)
AssociatedObject.IsOpen = false;
}
private void AssociatedObject_Closed(object sender, EventArgs e)
{
if (DesiredToggleButton != Mouse.DirectlyOver)
DesiredToggleButton.IsChecked = false;
}
protected override void OnDetaching()
{
base.OnDetaching();
DesiredToggleButton.Checked -= DesiredToggleButton_Checked;
DesiredToggleButton.Unchecked -= DesiredToggleButton_Unchecked;
if (AssociatedObject != null)
{
AssociatedObject.Closed -= AssociatedObject_Closed;
AssociatedObject.PreviewMouseUp -= AssociatedObject_PreviewMouseUp;
}
}
}
}
On the ToggleButton set the Property ClickMode="Press"apixeltoofar
Set StaysOpen="True" for your Popup
From MSDN:
Gets or sets a value that indicates whether the Popup control closes
when the control is no longer in focus.
[...]
true if the Popup control closes when IsOpen property is set to false;
false if the Popup control closes when a mouse or keyboard event occurs outside the Popup control.
I'm trying to use the context menu in a listview to run some code that requires data from which item it originated from.
I initially just did this:
XAML:
<ListView x:Name="lvResources" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListView.Resources>
<ContextMenu x:Key="resourceContextMenu">
<MenuItem Header="Get Metadata" Name="cmMetadata" Click="cmMetadata_Click" />
</ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource resourceContextMenu}" />
</Style>
</ListView.ItemContainerStyle>
...
C#:
private void cmMetadata_Click(object sender, RoutedEventArgs e)
{
// code that needs item data here
}
But I found that the originating listview item was not accessible that way.
I've read some tactics about how to get around this, like intercepting the MouseDown event and setting a private field to the listviewitem that was clicked, but that doesn't sit well with me as it seems a bit hacky to pass data around that way. And WPF is supposed to be easy, right? :) I've read this SO question and this MSDN forum question, but I'm still not sure how to really do this, as neither of those articles seem to work in my case. Is there a better way to pass the item that was clicked on through to the context menu?
Thanks!
Similar to Charlie's answer, but shouldn't require XAML changes.
private void cmMetadata_Click(object sender, RoutedEventArgs e)
{
MenuItem menu = sender as MenuItem;
ListViewItem lvi = lvResources.ItemContainerGenerator.ContainerFromItem(menu.DataContext) as ListViewItem;
}
Well in the cmMetadata_Click handler, you can just query the lvResources.SelectedItem property, since lvResources will be accessible from the code-behind file that the click handler is located in. It's not elegant, but it will work.
If you want to be a little more elegant, you could change where you set up your ContextMenu. For example, you could try something like this:
<ListView x:Name="lvResources" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListView.Style>
<Style TargetType="ListView">
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<TextBlock Text="{TemplateBinding Content}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Get Metadata" Name="cmMetadata" Click="cmMetadata_Click"
DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
</ListView.Style>
<ListViewItem>One Item</ListViewItem>
<ListViewItem>Another item</ListViewItem>
</ListView>
What this does is plug in a template for your ListViewItem, and then you can use the handy TemplatedParent shortcut to assign the ListViewItem to the DataContext of your menu item.
Now your code-behind looks like this:
private void cmMetadata_Click(object sender, RoutedEventArgs e)
{
MenuItem menu = sender as MenuItem;
ListViewItem item = menu.DataContext as ListViewItem;
}
Obviously the downside is you will now need to complete the template for a ListViewItem, but I'm sure you can find one that will suit your needs pretty quickly.
So I decided to try and implement a command solution. I'm pretty pleased with how it's working now.
First, created my command:
public static class CustomCommands
{
public static RoutedCommand DisplayMetadata = new RoutedCommand();
}
Next in my custom listview control, I added a new command binding to the constructor:
public SortableListView()
{
CommandBindings.Add(new CommandBinding(CustomCommands.DisplayMetadata, DisplayMetadataExecuted, DisplayMetadataCanExecute));
}
And also there, added the event handlers:
public void DisplayMetadataExecuted(object sender, ExecutedRoutedEventArgs e)
{
var nbSelectedItem = (MyItem)e.Parameter;
// do stuff with selected item
}
public void DisplayMetadataCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
I was already using a style selector to dynamically assign styles to the listview items, so instead of doing this in the xaml, I have to set the binding in the codebehind. You could do it in the xaml as well though:
public override Style SelectStyle(object item, DependencyObject container)
{
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(container);
MyItem selectedItem = (MyItem)item;
Style s = new Style();
var listMenuItems = new List<MenuItem>();
var mi = new MenuItem();
mi.Header= "Get Metadata";
mi.Name= "cmMetadata";
mi.Command = CustomCommands.DisplayMetadata;
mi.CommandParameter = selectedItem;
listMenuItems.Add(mi);
ContextMenu cm = new ContextMenu();
cm.ItemsSource = listMenuItems;
// Global styles
s.Setters.Add(new Setter(Control.ContextMenuProperty, cm));
// other style selection code
return s;
}
I like the feel of this solution much better than attempting to set a field on mouse click and try to access what was clicked that way.