I found one bug, and I don't understand, why it's happening.
I have DataGrid. On left mouse button click I want to open ContextMenu.
I made it. It works fine until I start clicking on DataGrid's Cells on random (every time ContextMenu is closing and reappearing at new place). But at one moment something is happening and ContextMenu newer showing (and Window don't response to any Mouse events like clicking on buttons and so on)... until I move cursor out of window and return it (or clicking Alt or F10).
Here some code:
ContextMenu (inside <DataGrid.Resources>)
<ContextMenu Style="{StaticResource DefaultContextMenuStyle}" x:Key="cm"
DataContext="{Binding Data, Source={StaticResource WindowViewModel}}">
</ContextMenu>
DataGrid column:
<DataGridTemplateColumn Header="TestHeader" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DockPanel ContextMenu="{StaticResource cm}" VerticalAlignment="Stretch" Background="Transparent">
<i:Interaction.Behaviors>
<local:OpenContextMenuLeftBehavior />
</i:Interaction.Behaviors>
<TextBlock Style="{StaticResource TextVCenteredCellStyle}" Text="{Binding LPU}" />
</DockPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
And behavior to open ContextMenu:
public class OpenContextMenuLeftBehavior : Behavior<FrameworkElement>
{
protected override void OnAttached()
{
AssociatedObject.PreviewMouseUp += OpenContextMenu;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewMouseUp -= OpenContextMenu;
}
void OpenContextMenu(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left) {
FrameworkElement Sender = sender as FrameworkElement;
Sender.ContextMenu.PlacementTarget = Sender;
Sender.ContextMenu.IsOpen = true;
}
}
}
When I found that bug, I tried to find some info with Snoop WPF.
And here some info from it:
Before bad thing happened one click on cell was like:
before bad thing
After bad thing:
after bad thing
First event happened at Popup (ContextMenu part?), it doesn't belong to window VisualTree. This Popup seems to stretch all over the main Window and closing, when i move mouse out of it.
So, I lost 2 days on that bug and this is all, that I can find.
Please help me.
EDIT:
I created minimal example:
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="dg1" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DockPanel>
<i:Interaction.Behaviors>
<local:OpenContextMenuLeftBehavior />
</i:Interaction.Behaviors>
<DockPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="123456"></MenuItem>
</ContextMenu>
</DockPanel.ContextMenu>
<TextBlock Text="123" />
</DockPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
dg1.ItemsSource = new List<int>()
{
1,2,3,4,5,6,7,8,9,0
};
}
}
public class OpenContextMenuLeftBehavior : Behavior<FrameworkElement>
{
protected override void OnAttached()
{
AssociatedObject.PreviewMouseUp += OpenContextMenu;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewMouseUp -= OpenContextMenu;
}
void OpenContextMenu(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left) {
FrameworkElement Sender = sender as FrameworkElement;
if (Sender != null) {
Sender.ContextMenu.PlacementTarget = Sender;
Sender.ContextMenu.IsOpen = true;
Sender.ContextMenu.UpdateLayout();
}
}
}
}
To reproduce this bug just click rapidly on different cells
Revised Answer
After you published a full, simple example, I'd suggest the following:
after you create the ContextMenu
Sender.ContextMenu.IsOpen = true;
Sender.ContextMenu.PreviewMouseUp += ContextMenu_PreviewMouseUp;
Having defined
private void ContextMenu_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
var Sender = (sender as ContextMenu);
if (Sender != null)
{
Sender.IsOpen = true;
e.Handled = true;
}
}
(you still have to manage the grid line selection, but it's like off topic here)
Related
I'm trying to create a ContextMenu on a UserControl derived object. Note: This is NOT a WinForms App, it is pure WPF.
So I create the ContextMenu thus:
<UserControl.Resources>
<ContextMenu x:Key="cmLCD_CopyCutPaste">
<MenuItem Name="CutOption" Header="{x:Static p:Resources.Popup_Cut}" Command="Cut" Click="MenuItem_Cut" IsEnabled="True"/>
<MenuItem Name="CopyOption" Header="{x:Static p:Resources.Popup_Copy}" Command="Copy" Click="MenuItem_Copy" IsEnabled="True"/>
<MenuItem Name="PasteOption" Header="{x:Static p:Resources.Popup_Paste}" Command="Paste" Click="MenuItem_Paste" IsEnabled="True"/>
</ContextMenu>
</UserControl.Resources>
The mousebutton event is setup thus:
d:DesignHeight="66" d:DesignWidth="340" Focusable="True" KeyDown="Grid_KeyDown" MouseRightButtonDown="EMS_UI_LCDscreen_MouseRightButtonDown" >
In the code behind, the constructor starts like this:
public EMS_UI_LCDscreen()
{
InitializeComponent();
// Find the ContextMenu created on this object - it is called cmLCD_CopyCutPaste
ContextMenu cm = FindResource("cmLCD_CopyCutPaste") as ContextMenu;
// If we found the contextMenu, assign it the ContextMenu placeholder for this instance.
if (cm != null)
{
ContextMenu = cm;
}
...
The mouse click(right button) is handled like this:
private void EMS_UI_LCDscreen_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
if (this.ContextMenu is ContextMenu cm)
{
if (cm != null)
{
if (DataContext is DeviceEditorData ded)
{
cm.DataContext = ded.Device.GetStructuredLocation(0);
cm.IsEnabled = true;
cm.PlacementTarget = sender as Button;
cm.IsOpen = true;
}
}
}
}
There are the three event handlers to actually respond to the user selection on the ContextMenu:
private void MenuItem_Cut(object sender, RoutedEventArgs e)
{
}
private void MenuItem_Copy(object sender, RoutedEventArgs e)
{
}
private void MenuItem_Paste(object sender, RoutedEventArgs e)
{
}
View of the ContextMenu being shown on the LCD Image component
The component I have created appears in a few different contexts, but the one I'm mainly interested in is inside of a DataGrid that shows various items of data, each with one of these components. when right clicked, the component faithfully displays the popup context menu as expected..... BUT all the items on the menu are grayed out and basically not enabled.
So my question is, what is the missing piece of glue that effectively enables the menu items so that they can be clicked to do the required actions. Most of the answers already seen on the net go into detail of how to do it in a WinForms app, but despite hours of searching, I can find no clear solution to what should be a very simple task of enabling the menu items.
Can some kind soul please put me out of my anguish and in a few lines of code show me how to do it! Thank you
UPDATE: This is the XAML implementation for the LCD Component:
<UserControl x:Class="EMS_Config_Tool.UIComponents.WPF.EMS_UI_LCDscreen"
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"
xmlns:p="clr-namespace:EMS_Config_Tool.Properties"
mc:Ignorable="d"
d:DesignHeight="66" d:DesignWidth="340" Focusable="True" KeyDown="Grid_KeyDown" MouseRightButtonDown="EMS_UI_LCDscreen_MouseRightButtonDown" >
<UserControl.Resources>
<ContextMenu x:Key="cmLCD_CopyCutPaste">
<MenuItem Name="CutOption" Header="{x:Static p:Resources.Popup_Cut}" Command="{Binding Cut}"/>
<MenuItem Name="CopyOption" Header="{x:Static p:Resources.Popup_Copy}" Command="{Binding Copy}"/>
<MenuItem Name="PasteOption" Header="{x:Static p:Resources.Popup_Paste}" Command="{Binding Paste}"/>
</ContextMenu>
</UserControl.Resources>
<UserControl.CommandBindings>
<CommandBinding Command="Cut"
CanExecute="CutCommand_CanExecute"
Executed="CutCommand_Executed" />
<CommandBinding Command="Copy"
CanExecute="CopyCommand_CanExecute"
Executed="CopyCommand_Executed" />
<CommandBinding Command="Paste"
CanExecute="PasteCommand_CanExecute"
Executed="PasteCommand_Executed" />
</UserControl.CommandBindings>
</UserControl>
As can be seen, it is very simple - there are no items added to the control - it only serves as a Canvas upon which the code behind draws all the needed items which are purely graphic "draw" items.
This is the implementations of the 6 methods referenced, in the corresponding .cs file for the xaml above:
private void CutCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// Verify relevant conditions and set CanExecuteRoutedEventArgs.CanExecute accordingly
e.CanExecute = true;
}
private void CutCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
// Execute the command action
if (sender is EMS_UI_LCDscreen lcd)
{
if (TheDevice != null)
{
Clipboard.SetDataObject(TheDevice.GetStructuredLocation(0).ToString());
TheDevice.SetLocation(0, "");
lcd.StructuredTextToShow = TheDevice.GetStructuredLocation(0);
}
else
{
Clipboard.SetDataObject(lcd.StructuredTextToShow.ToString());
lcd.StructuredTextToShow.SetString("");
}
}
}
private void CopyCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// Verify relevant conditions and set CanExecuteRoutedEventArgs.CanExecute accordingly
e.CanExecute = true;
}
private void CopyCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
// Execute the command action
if (sender is EMS_UI_LCDscreen lcd)
{
if (TheDevice!=null)
Clipboard.SetDataObject(TheDevice.GetStructuredLocation(0).ToString());
else
Clipboard.SetDataObject(lcd.StructuredTextToShow.ToString());
}
}
private void PasteCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// Verify relevant conditions and set CanExecuteRoutedEventArgs.CanExecute accordingly
IDataObject iData = Clipboard.GetDataObject();
// Is the Data Text?
if (iData.GetDataPresent(DataFormats.Text))
e.CanExecute = true;
else
e.CanExecute = false;
}
private void PasteCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
// Execute the command action
if (sender is EMS_UI_LCDscreen lcd)
{
// Retrieves data
IDataObject iData = Clipboard.GetDataObject();
// Is the Data Text?
if (iData.GetDataPresent(DataFormats.Text))
{
if (TheDevice != null)
{
TheDevice.SetLocation(0, (string)iData.GetData(DataFormats.Text));
lcd.StructuredTextToShow = TheDevice.GetStructuredLocation(0);
}
else
{
lcd.StructuredTextToShow.SetString((string)iData.GetData(DataFormats.Text));
}
}
}
}
Finally, the ContextMenu is assigned to the object in the constructor:
public EMS_UI_LCDscreen()
{
InitializeComponent();
// Find the ContextMenu created on this object - it is called cmLCD_CopyCutPaste
ContextMenu cm = FindResource("cmLCD_CopyCutPaste") as ContextMenu;
// If we found the contextMenu, assign it the ContextMenu placeholder for this instance.
if (cm != null)
{
ContextMenu = cm;
}
...
The menu is shown on the component in response to right-click, the cut/copy/paste methods respond to the key ops, but clicking on the menu items does not fire the corresponding methods.
I can't see why that should be so, it all looks correct to me, but perhaps there is something missing that is preventing it working. One suggestion was to use a "relay command" type of thingy, but what and how that may be, is unclear.
You should remove all code that sets IsEnabled to true. It's redundant as the value is true by default.
The buttons are disabled because you have (accidentally?) attached a command to the MenuItem.Command property but no corresponding command handler.
The framework will try to invoke a CanExecute handler. Since there is no one defined, a default handler is returned that sets CanExecuteRoutedEventArgs.CanExecute to false, which will disable the button (ICommandSource).
Not sure if your intention was to use a command here or you wrongfully guessed the Command property is like a name property (since you have also registered Click event handlers). See Commanding Overview to learn more.
Anyway, this is how you can register a command handler:
You have assigned predefined application commands (cut, copy and paste - the MenuItem.Command string values in your XAML implicitly references the static ApplicationCommands commands). These commands are routed commands (behavior is identical to routed events - in fact routed commands are routed events). Therefore, you must define the command bindings on a parent element of the command source (the element that invokes the command) as the command will bubble up the tree.
A UIElement.CommandBinding consists of the specified CommandBinding.Executed handler and the optional CommandBinding.CanExecute handler.
Use the CanExecute handler to control the disabled states of the command source e.g., a Button. If the command source should be always enabled, simply omit the CanExecute handler.
XAML
<Window>
<Window.CommandBindings>
<CommandBinding Command="Cut"
CanExecute="CutCommand_CanExecute"
Executed="CutCommand_Executed" />
</Window.CommandBindings>
</Window>
C#
partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var cutCommandBinding =
new CommandBinding(ApplicationCommands.Cut, CutCommand_Executed, CutCommand_CanExecute)
this.CommandBindings.Add(cutCommandBinding);
}
}
Then create the corresponding command handlers in the code-behind.
private void CutCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
// Verify relevant conditions and set CanExecuteRoutedEventArgs.CanExecute accordingly
e.CanExecute = true;
}
private void CutCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
// Execute the command action
}
Update
To force command handling on the UserControl, you can explicitly set the MenuItem.CommandTarget property to reference the UserControl.
You can also assign the UserControl.Contextmenu from XAML:
<UserControl>
<UserControl.Resources>
<Style TargetType="MenuItem">
<Setter Property="CommandTarget"
Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=PlacementTarget}" />
</Style>
</UserControl.Resources>
<UserControl.Contextmenu>
<ContextMenu>
<MenuItem Name="CutOption" Header="{x:Static p:Resources.Popup_Cut}" Command="{Binding Cut}"/>
<MenuItem Name="CopyOption" Header="{x:Static p:Resources.Popup_Copy}" Command="{Binding Copy}"/>
<MenuItem Name="PasteOption" Header="{x:Static p:Resources.Popup_Paste}" Command="{Binding Paste}"/>
</ContextMenu>
</UserControl.Contextmenu>
<UserControl.CommandBindings>
<CommandBinding Command="Cut"
CanExecute="CutCommand_CanExecute"
Executed="CutCommand_Executed" />
<CommandBinding Command="Copy"
CanExecute="CopyCommand_CanExecute"
Executed="CopyCommand_Executed" />
<CommandBinding Command="Paste"
CanExecute="PasteCommand_CanExecute"
Executed="PasteCommand_Executed" />
</UserControl.CommandBindings>
</UserControl>
This was the solution that finally worked for me. The elixir was assigning the commands, not with the "Binding" term, but directly:
<UserControl x:Class="EMS_Config_Tool.UIComponents.WPF.EMS_UI_LCDscreen"
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"
xmlns:p="clr-namespace:EMS_Config_Tool.Properties"
mc:Ignorable="d"
d:DesignHeight="66" d:DesignWidth="340" Focusable="True" KeyDown="Grid_KeyDown" MouseRightButtonDown="EMS_UI_LCDscreen_MouseRightButtonDown" >
<UserControl.Resources>
<Style TargetType="MenuItem">
<Setter Property="CommandTarget"
Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=PlacementTarget}" />
</Style>
</UserControl.Resources>
<UserControl.ContextMenu>
<ContextMenu>
<MenuItem Name="CutOption" Header="{x:Static p:Resources.Popup_Cut}" Command="Cut" />
<MenuItem Name="CopyOption" Header="{x:Static p:Resources.Popup_Copy}" Command="Copy" />
<MenuItem Name="PasteOption" Header="{x:Static p:Resources.Popup_Paste}" Command="Paste" />
</ContextMenu>
</UserControl.ContextMenu>
<UserControl.CommandBindings>
<CommandBinding Command="Cut"
CanExecute="CutCommand_CanExecute"
Executed="CutCommand_Executed" />
<CommandBinding Command="Copy"
CanExecute="CopyCommand_CanExecute"
Executed="CopyCommand_Executed" />
<CommandBinding Command="Paste"
CanExecute="PasteCommand_CanExecute"
Executed="PasteCommand_Executed" />
</UserControl.CommandBindings>
</UserControl>
The constructor and assignment of images looks like this:
public EMS_UI_LCDscreen()
{
InitializeComponent();
Image ObjImage1 = new Image();
Image ObjImage2 = new Image();
Image ObjImage3 = new Image();
ObjImage1.Source = new BitmapImage(new Uri(#"pack://application:,,,/Graphics\Misc Icons\Cut.png"));
CutOption.Icon = ObjImage1;
ObjImage2.Source = new BitmapImage(new Uri(#"pack://application:,,,/Graphics\Misc Icons\Copy.png"));
CopyOption.Icon = ObjImage2;
ObjImage3.Source = new BitmapImage(new Uri(#"pack://application:,,,/Graphics\Misc Icons\Paste.png"));
PasteOption.Icon = ObjImage3;
...
... and displaying the context menu on the component in response to the mouse right click:
private void EMS_UI_LCDscreen_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
if (ContextMenu is ContextMenu cm)
{
if (DataContext is DeviceEditorData ded)
{
cm.DataContext = ded.Device.GetStructuredLocation((uint)LocationIndex);
cm.IsEnabled = true;
cm.PlacementTarget = sender as EMS_UI_LCDscreen;
cm.IsOpen = true;
}
if (DataContext is EMSBasicDevice dev)
{
cm.DataContext = dev.GetStructuredLocation((uint)LocationIndex);
cm.IsEnabled = true;
cm.PlacementTarget = sender as EMS_UI_LCDscreen;
cm.IsOpen = true;
}
}
}
For brevity, I have not shown the ..._CanExecute / ...Executed methods, they are as shown previously in the question.
Thanks to all who have helped in resolving this issue.
View of the finished context Menu in situ
So, if you are using DataContext and Binding's, you can do the following
remove the direct control with event handlers, i.e. Click, and direct setting of Enabled.
use Binding to bind menu action to the handler inside the view model: Command={Binding CutCommand} (or any other handler).
use ICommand.CanExecute of the command handler (CutCommand in the sample above). to manage Enabled state of menu item.
So, let's put all together.
In the XAML
<MenuItem Name="CutOption" Header="{x:Static p:Resources.Popup_Cut}"
Command="{Binding CutCommand}"/>
In the ViewModel
class ViewModel: INotifyPropertyChanged
{
...
public ICommand CutCommand { get; }
public ViewModel()
{
//you can just omit second arg if you always can call Cut.
CutCommand = new RelayCommand(CutHandler, ()=>CanCut()));
}
...
}
Here RelayCommand is a https://learn.microsoft.com/en-us/windows/communitytoolkit/mvvm/relaycommand or any other implementation of simple command.
If you need to update the Enabled state on-the-fly, you can call CommandManager.InvalidateRequerySuggested(); to reevaluate CanExecute of all commands.
From time to time I'd like to show a context menu when clicking on a Cell in a DataGrid.
I create the ContextMenu programmatically and then display it with ContextMenu.IsOpen=true. In the example below, it works when clicking inside the Grid panel, but it doesn't, wenn clicking on a cell(UIElement inside a cell) of the DataGrid.
That is the difference? What do I need to do to make it work on a DataGridCell as well?
Here comes a demo version, first XAML and below the code behind.
<Window x:Class="WpfApplication7_delete_me.MainWindow"
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"
xmlns:local="clr-namespace:WpfApplication7_delete_me"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid MouseDown="Grid_MouseDown" Background="Beige">
<DataGrid x:Name="dataGrid" HorizontalAlignment="Left" VerticalAlignment="Top" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" MouseDown="TextBlock_MouseDown" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Here comes the code behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApplication7_delete_me {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
Person p1 = new Person(); p1.Name = "abc";
Person p2 = new Person(); p2.Name = "1q23";
List<Person> l = new List<Person>() { p1, p2 };
dataGrid.ItemsSource = l;
}
private void Grid_MouseDown(object sender, MouseButtonEventArgs e) {
ContextMenu cm = new ContextMenu();
MenuItem mi = new MenuItem();
mi.Header = "hallo";
cm.Items.Add(mi);
cm.IsOpen = true;
}
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e) {
ContextMenu cm = new ContextMenu();
MenuItem mi = new MenuItem();
mi.Header = "hallo";
cm.Items.Add(mi);
cm.IsOpen = true;
}
}
class Person {
public string Name { get; set; }
}
}
After some time I found two solutions:
1) It's working when using PreviewMouseDown instead of MouseDown.
2) Using: Dispatcher.BeginInvoke(new Action(() => { c.IsOpen = true; }), null);
But why is setting IsOpen inside MouseDown event not working?
About the first solution.
there are some issues while using the MouseDown event because the event might be marked as handled by other controls.
the PreviewMouseDown is a preview event and it is not marked, hence when you use it , it comes all the way down from the root element and controls to your implementation.
for more information you can read here:MSDN UIElement.MouseDown Event
Define an empty ContextMenu for the DataGrid.
<DataGrid.ContextMenu>
<ContextMenu x:Name="CtxMenu">
</ContextMenu>
</DataGrid.ContextMenu>
And handle ContextMenuOpening event :
private void DataGrid_ContextMenuOpening_1(object sender, ContextMenuEventArgs e)
{
ContextMenu ctxmenu = (sender as DataGrid).ContextMenu;
// suppress ContextMenu if empty
e.Handled = ctxmenu.Items.Count == 0;
}
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
ContextMenu ctxmenu = Dgrd.ContextMenu;
MenuItem mi = new MenuItem();
mi.Header = "hallo";
ctxmenu.Items.Add(mi);
}
Better would be to handle PreviewMouseDown event at DataGrid level like this <DataGrid ... DataGridCell.PreviewMouseDown="DataGridCell_MouseDown" ... /> .
That way you get ContextMenu as ContextMenu ctxmenu = (sender as DataGrid).ContextMenu;. Also its good to use preview events if you want to do some initial preparation.
With the following XAML:
<Window x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="600" Width="640">
<ScrollViewer PanningMode="Both">
<StackPanel>
<TextBlock TextWrapping="Wrap">LOTS OF TEXT...</TextBlock>
<DataGrid MinHeight="200">
<DataGrid.Columns>
<DataGridTextColumn Width="100"></DataGridTextColumn>
<DataGridTextColumn Width="100"></DataGridTextColumn>
<DataGridTextColumn Width="100"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<TextBlock TextWrapping="Wrap">LOTS OF TEXT...</TextBlock>
</StackPanel>
</ScrollViewer>
</Window>
You can scroll by touching on the TextBlocks. However, if you touch the DataGrid and attempt to scroll, it does nothing.
I'm guessing it has something to do with the fact that the content in the DataGrid is potentially scrollable itself so WPF is getting confused with the potentially nested scrollbars.
The desired behaviour is that touching in the DataGrid will scroll the content inside the DataGrid first (if necessary). Then, when content in the DataGrid has been fully scrolled, the main window will scroll.
Similar to PreviewMouseWheel (Bubbling scroll events from a ListView to its parent), you can do same with Touch controls:
C#:
public sealed class SubControlsTouchScrollEvent : Behavior<UIElement>
{
double originalDistance;
double actualDistance;
int delta;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewTouchDown += AssociatedObject_PreviewTouchDown;
AssociatedObject.PreviewTouchMove += AssociatedObject_PreviewTouchMove;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewTouchUp -= AssociatedObject_PreviewTouchDown;
AssociatedObject.PreviewTouchMove -= AssociatedObject_PreviewTouchMove;
base.OnDetaching();
}
void AssociatedObject_PreviewTouchDown(object sender, TouchEventArgs e)
{
System.Windows.IInputElement s = sender as System.Windows.IInputElement;
originalDistance = e.GetTouchPoint(s).Position.Y;
}
void AssociatedObject_PreviewTouchMove(object sender, TouchEventArgs e)
{
ScrollViewer s = sender as ScrollViewer;
actualDistance = e.GetTouchPoint(s).Position.Y;
delta = Convert.ToInt16(actualDistance - originalDistance);
s.ScrollToVerticalOffset(s.VerticalOffset - (delta * 0.1));
e.Handled = true;
}
}
XAML:
<ScrollViewer PanningMode="Both" Background="Transparent">
<interactivity:Interaction.Behaviors>
<local:SubControlsTouchScrollEvent />
</interactivity:Interaction.Behaviors>
</ScrollViewer>
NOTE: This is only for vertical scrolling, but you can add horizontal scrolling in the same way.
The solution from #neo_st worked for me. Three important sidenotes to make this work:
The Behavior needs to be attached to the outer ScrollViewer.
This solution can't handle MultiTouch. If you put two fingers on the touch screen the Events get fired by both fingers and the vertical scroll doesn't work correctly.
To get the typical vertical scroll behaviour on the outer ScrollViewer you should change the actualDistance to the original distance as follows:
void AssociatedObject_PreviewTouchMove(object sender, TouchEventArgs e)
{
ScrollViewer s = sender as ScrollViewer;
actualDistance = e.GetTouchPoint(s).Position.Y;
delta = Convert.ToInt16(actualDistance - originalDistance);
s.ScrollToVerticalOffset(s.VerticalOffset - delta);
originalDistance = actualDistance;
e.Handled = true;
}
I have ICollectionView looks like
public ICollectionView UsersCollectionView
{
get
{
var view = CollectionViewSource.GetDefaultView(this);
view.GroupDescriptions.Add(new PropertyGroupDescription("SeriesName"));
view.SortDescriptions.Add(new SortDescription("CreationDate", ListSortDirection.Ascending));
view.SortDescriptions.Add(new SortDescription("DocumentTypeId", ListSortDirection.Ascending));
return view;
}
}
I want to use drag & drop to change the item Series Name , and location on the list view any idea how to do that for example
--- ScienceFiction
------------> Book1
------------> Book2
--- History
------------> Book3
------------> Book4
if Idraged and droped book3 in ScienceFiction the output should be
--- ScienceFiction
------------> Book1
------------> Book2
------------> Book3
--- History
------------> Book4
I use xaml code like this :
<UserControl.Resources>
<Style x:Key="ContainerStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander Header="{Binding Name}" IsExpanded="True">
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<ListBox x:Name="lbPersonList" Margin="19,17,162,25" AlternationCount="2" ItemsSource="{Binding}">
<ListBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource ContainerStyle}"/>
</ListBox.GroupStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
AMH,
First modify the ListviewItem Style. It is the container that contains every line (datatemplate instanciation) of the listbox. It is a good place to manage Drag and Drop at the line level ( not a control of a line, there could be many in the DataTemplate).
In Visual Studio, select the listbox, right click, Edit additional templates/Edit Generated Item Container(ItemContainerStyle)/Edit a copy
In the ListBoxItemStyle created, add those three declarations amongst the setters :
<EventSetter Event="ListBoxItem.DragOver" Handler="ListBoxItemDragOver"/>
<EventSetter Event="ListBoxItem.Drop" Handler="ListBoxItemDrop"/>
<EventSetter Event="ListBoxItem.PreviewMouseMove" Handler="ListBoxItemPreviewMouseMove"/>
Set the AllowDrop property to true on the ListBox :
<ListBox x:Name="listboxBooks" AllowDrop="True">
Then implement the handlers in the .xaml.cs code :
#region DnD management
private Book sourceBook;
private void ListBoxItemPreviewMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed)
return;
var listboxItem = sender as ListBoxItem;
if (listboxItem == null)
return;
sourceBook = listboxItem.DataContext as Book;
if (sourceBook == null)
return;
var data = new DataObject();
data.SetData(sourceBook);
// provide some data for DnD in other applications (Word, ...)
data.SetData(DataFormats.StringFormat, sourceBook.ToString());
DragDropEffects effect = DragDrop.DoDragDrop(listboxItem, data, DragDropEffects.Move | DragDropEffects.Copy);
}
private void ListBoxItemDrop(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(typeof(Book)))
return;
var listBoxItem = sender as ListBoxItem;
if (listBoxItem == null)
return;
var targetBook = listBoxItem.DataContext as Book;
if (targetBook != null)
{
viewModel.RecategorizeBook(sourceBook, targetBook.Category);
}
e.Handled = true;
}
private void ListBoxItemDragOver(object sender, DragEventArgs e)
{
Debug.WriteLine(e.Effects);
if (!e.Data.GetDataPresent(typeof(Book)))
{
e.Effects = DragDropEffects.None;
e.Handled = true;
}
}
private void GroupItemDrop(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(typeof(Book)))
return;
var groupItem = sender as GroupItem;
if (groupItem == null)
return;
dynamic targetGroup = groupItem.DataContext;
if (targetGroup != null)
{
// here I change the category of the book
// and refresh the view of the collectionViewSource ( see link to project zipped further)
viewModel.RecategorizeBook(sourceBook, targetGroup.Name as String);
}
e.Handled = true;
}
#endregion
Note that I also implemented Drop management on the group header in the handlers.
So the handler needs to be declared in the XAML groupstyle :
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<EventSetter Event="GroupItem.Drop" Handler="GroupItemDrop"/>
<EventSetter Event="GroupItem.PreviewMouseMove" Handler="ListBoxItemPreviewMouseMove"/>
It works, here is full working code : http://1drv.ms/1FhBZwr
Wish you the best possible code
Unfortunately .net does not yet provide an "easy to use" implementation of drag'n'drop. You have to build a bunch of things on your own. The starting point will be a Behavior starting the drag and a ContentControl being the area onto which the user can drop things. Having those defined you can easily reuse this concept. In the following example the StackPanel can be "dragged" onto an invisible "area" surrounding the TextBlock. This way you are able to implement manually sorting your books (drop before/behind the book under the mouse pointer).
If you want to drop books onto your headers surround them with a DropArea. You could also implement both ways.
Your xaml will look like:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
[...]
<ListBox x:Name="lbPersonList" Margin="19,17,162,25" AlternationCount="2" ItemsSource="{Binding}">
<ListBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource ContainerStyle}"/>
</ListBox.GroupStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<myOwn:DropArea>
<TextBlock Text="{Binding Name}"/>
</myOwn:DropArea>
<i:Interaction.Behaviors>
<myOwn:DragBehavior/>
</i:Interaction.Behaviors>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The DragBehavior will look like:
public class DragBehavior : Behavior<FrameworkElement>
[...]
protected override void OnAttached()
{
AssociatedObject.MouseMove += AssociatedObject_MouseMove;
AssociatedObject.MouseDown += AssociatedObject_MouseLeftButtonDown;
AssociatedObject.MouseLeave += AssociatedObject_MouseLeave;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
AssociatedObject.MouseDown -= AssociatedObject_MouseLeftButtonDown;
AssociatedObject.MouseLeave -= AssociatedObject_MouseLeave;
base.OnDetaching();
}
protected virtual void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
{
if (some condition of mouse button states or mouse moves)
{
DataObject data = new DataObject();
data.SetData(typeof(anyKeyType), anyData);
data.SetData(typeof(anyOtherKeyType), anyOtherData);
DragDrop.DoDragDrop(fe, data, DragDropEffects.Move);
}
}
The DropArea will look like:
public class DropArea : ContentControl
[...]
public DropArea()
{
DragEnter += AssociatedObjectDragEnter;
DragLeave += AssociatedObjectDragLeave;
DragOver += AssociatedObjectDragOver;
IsTabStop = false;
AllowDrop = true;
}
protected override void AssociatedObjectDrop(object sender, DragEventArgs e)
{
object o = e.Data.GetData(typeof(anyKeyType));
//handle dropped data
}
Hope that may help you on your way. There may be any frameworks or libraries to adress that issue, but this way you can adress your own needs.
Related: "Cleanest single click + double click handling in Silverlight?"
What is the simplest way to make a double-click trigger some action in XAML?
I'm trying to do something like this, closing a popup when the user double-clicks a ListBox item:
<Popup x:Name="popup">
<ListBox ItemsSource="{Binding Colors}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Color="{Binding Color}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="DoubleClick">
<ei:ChangePropertyAction TargetObject="{Binding ElementName=popup}"
PropertyName="IsOpen" Value="False" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
</Popup>
The problem with this above is that, of course, there is no "DoubleClick" event. So I need to replace the EventTrigger with a double-click trigger.
Create a custom double-click trigger by inheriting TriggerBase<T> and attaching a listener to the "MouseLeftButtonDown" event. Check the "MouseButtonEventArgs.ClickCount" property to check for the double-click:
public class DoubleClickTrigger : TriggerBase<FrameworkElement>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.AddHandler(FrameworkElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(OnClick), true);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.RemoveHandler(FrameworkElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(OnClick));
}
private void OnClick(object sender, MouseButtonEventArgs args)
{
if (args.ClickCount == 1)
return;
if (args.ClickCount == 2)
InvokeActions(null);
}
}
With this, the XAML in the OP should work by replacing the EventTrigger with the above DoubleClickTrigger.