I have a ListView databinded to a ObservableCollection of a class. I'm trying to add a "Copy" menu item to the ListView like so:
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Command="{x:Static ApplicationCommands.Copy}"></MenuItem>
<MenuItem Command="{x:Static ApplicationCommands.Copy}"
CommandTarget="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"></MenuItem>
</ContextMenu>
</ListView.ContextMenu>
Now when i right click a menu item.. the menu comes up but the Copy is grayed out.. my educated guess is that it thinks there's nothing for it to copy.. but that doesn't make sense because when i right click a listbox item.. i'm technically selecting something for it to copy.. and the listbox item is selected as i'm doing this..I just want it to copy the selected text in the ListView.
What do I have to do to get this to work? Overwrite a copy class in my class that's binded to the Listview? I tried googling and not getting very far.
I've just put together an example that works for me:
<Window.CommandBindings>
<CommandBinding
Command="ApplicationCommands.Copy"
CanExecute="CommandBinding_CanExecute"
Executed="CommandBinding_Executed"/>
</Window.CommandBindings>
<Window.Resources>
<ContextMenu x:Key="MyContextMenu">
<MenuItem Header="Copy" Command="ApplicationCommands.Copy"/>
</ContextMenu>
<Style x:Key="MyItemContainerStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource MyContextMenu}" />
</Style>
</Window.Resources>
<Grid>
<ListView x:Name="MyListView" ItemContainerStyle="{StaticResource MyItemContainerStyle}"/>
</Grid>
Then in the code behind:
// Test class with a single string property
private class MyData
{
public String Name { get; set; }
public MyData(String name)
{
Name = name;
}
public override string ToString()
{
return Name;
}
}
public MainWindow()
{
InitializeComponent();
// Create some test data
ObservableCollection<MyData> names = new ObservableCollection<MyData>();
names.Add(new MyData("Name 1"));
names.Add(new MyData("Name 2"));
names.Add(new MyData("Name 3"));
MyListView.ItemsSource = names;
}
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
Clipboard.SetText(MyListView.SelectedItem.ToString());
}
This works whether you select Copy from the context menu, or use Ctrl+C
Without having you rewrite everything, the thing to note about Gary's sample is the presence of a CanExecute statement, which is what controls the enabling/disabling of commands. You should look in to the proper command structure some more, because I think you're missing the real power of the command.
https://msdn.microsoft.com/en-us/library/ms753200%28v=vs.110%29.aspx
Related
In my WPF MVVM application, when the (sub-)menu of a MenuItem opens, I want to populate the (sub-)menu.
E.g. there is a MenuItem "Logs". Only when the submenu opens, I want to search for corresponding log files on disk and display their filenames in the SubMenu afterwards.
Populating submenu dynamically
MainWindow.xaml
<Grid>
<Menu ItemsSource="{Binding MenuItems}">
<Menu.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MyMenuItemViewModel}"
ItemsSource="{Binding Children}">
<local:CustomMenuItemControl/>
</HierarchicalDataTemplate>
</Menu.Resources>
</Menu>
</Grid>
MyMenuItemViewModel.cs
public class MyMenuItemViewModel : ObservableObject
{
public string Text { get; set; }
public ObservableCollection<MyMenuItemViewModel> Children { get; set; }
public MyMenuItemViewModel(string item)
{
Text = item;
Children = new ObservableCollection<MyMenuItemViewModel>();
}
}
My application is significantly larger, for illustrative purposes I have removed most of it.
I work with a ViewModel that contains a Text and an ObservableCollection "Children" for SubMenus.
It is displayed with a CustomControl that only displays the text.
However, I am already failing to get a trigger when the SubMenu is opened.
I've already tried adding event handler to HierarchicalDataTemplate and CustomMenuItemControl and
a DependencyProperty to the control and tried binding events in XAML, but apparently not in the right place.
Where exactly do I need to define the trigger or handler that executes code when the SubMenu is opened?
I'll answer my own question. Whether it's the best way to solve my problem, I don't know, but that's how I went about it:
In the ViewModel I created a property "IsSubMenuOpen". In the setter, I query whether the text matches "Logs". If yes, I fill the list with the log files.
private bool _isSubMenuOpen;
public bool IsSubMenuOpen
{
get => _isSubMenuOpen;
set
{
if(Text == "Logs")
{
Children.Clear();
foreach (var file in Directory.GetFiles(#"C:\MyDir", "*.log"))
{
Children.Add(new MyMenuItemViewModel(Path.GetFileName(file)));
}
}
SetProperty(ref _isSubMenuOpen, value);
}
}
In the MainWindow I have created a setter for the property "IsSubMenuopen", and bound my VM property in the value.
<Grid>
<Menu ItemsSource="{Binding MenuItems}">
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="IsSubmenuOpen" Value="{Binding IsSubMenuOpen}"></Setter>
</Style>
</Menu.ItemContainerStyle>
<Menu.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MyMenuItemViewModel}"
ItemsSource="{Binding Children}">
<local:CustomMenuItemControl/>
</HierarchicalDataTemplate>
</Menu.Resources>
</Menu>
</Grid>
Here is my firt question for Stackoverflow, I hope that will be ok!
I'm working on a custom Dropdown Button in WPF, and I would like to add a click event on the buttons "Text1" and "Text2". I have to put this dropdown button in a DLL so I use the WPF CustomControl library. So in the perfect world, I would like to create several methods in the MainWindow.xaml.cs and send the name of the method in a class where the name of the button, the icon , the tooltip, ... that will be used in the generic.xaml to find the method to call.
I hope what I said is clear :3
The purpose of this is to have a reusable dropdown button where I can add some click event in the items when we click on it.
Here is the generic.xaml with my dropdown button :
<Style TargetType="{x:Type local:ButtonDropdown}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ButtonDropdown}">
<mah:DropDownButton Content="{Binding Path=Text, RelativeSource={RelativeSource TemplatedParent}}"
ToolTip="{Binding Path=ToolTip, RelativeSource={RelativeSource TemplatedParent}}"
x:Name="DropDownButton"
Orientation="Vertical"
BorderThickness="0"
ItemsSource="{Binding ItemsSource}">
<mah:DropDownButton.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0" ToolTip="{Binding Tooltip}">
<StackPanel.InputBindings>
<MouseBinding Command="{Binding Path=SomeCommand, RelativeSource={RelativeSource TemplatedParent}}" MouseAction="LeftClick" />
</StackPanel.InputBindings>
<Image Source="{Binding Icon}" Width="16"></Image>
<TextBlock Text="{Binding Text}" x:Name="PART_DropdownButton">
</TextBlock>
</StackPanel>
</DataTemplate>
</mah:DropDownButton.ItemTemplate>
<mah:DropDownButton.Icon>
<Image Source="{Binding Path=Icon, RelativeSource={RelativeSource TemplatedParent}}" Width="32"></Image>
</mah:DropDownButton.Icon>
</mah:DropDownButton>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The call of this custom dropdown in the MainWindow.xaml :
<CustomButton:ButtonDropdown Text="Dropdown"
x:Name="ButtonDropdown"
Icon="Images/Open.png"
ToolTip="TOOLTIP DROPDOWN"
ItemsSource="{Binding Items}"/>
Here is my method OnApplyTemplate I add the line 'TextBlock textblock= GetTemplateChild("PART_DropdownButton") as TextBlock;' after the first answer.
public override void OnApplyTemplate()
{
DropDownButton dropDownButton = GetTemplateChild("DropDownButton") as DropDownButton;
TextBlock textblock= GetTemplateChild("PART_DropdownButton") as TextBlock;
textblock.MouseDown += Method1;
dropDownButton.ItemsSource = DropdownItems;
dropDownButton.Click += ButtonDropdown_Click;
}
And finally the class I have created for items in the dropdown :
public class DropdownItem
{
private string text;
private string icon;
private string tooltip;
private string clickEvent;
}
For the moment I have try with command and mousedown on textblock but don't work :/
Edit : I add the name for the textBlock and I add my method OnApplyTemplate from my ButtonDropdown.cs. The dropDownButton.click is ok but when I try to get the "PART_DropdownButton" that is null. I think because of there is not only one but several textBlock so he don't know which one to take. But that is my problem how to asign a different method on all textblock.mouseDown ? How can we put a different name on all textblock ?
Assuming your Dropdown Button derives from a button control give the DropDown button a name in the xaml file e.g. "PART_DropdownButton". Then reference the name in the code behind in the OnApplyTemplate procedure. Here you can add an event handler trapping your mouse events.
private DropdownButton dropdownbutton = null;
...
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
dropdownbutton = base.GetTemplateChild("PART_DropdownButton") as ToggleButton;
if (dropdownbutton != null)
{
dropdownbutton.MouseDown += MouseDown_Click;
}
else
....;
}
Next write your event handler for MouseDown_Click.
Regards Martin
I finally find something that works like I want !
I add an Icommand in my dropdownItem. That will contain my method.
public class DropdownItem
{
private string text;
private string icon;
private string tooltip;
private string clickEvent;
public ICommand ClickCommand { get; set; }
}
In my MainWindow.xaml.cs I add the command I need.
private ICommand _command1;
private ICommand _command2;
public MainWindow()
{
InitializeComponent();
Items.Add(new DropdownItem("Text1", "Images/Open.png", "Method1", "TEST")
{
ClickCommand = Command1
});
Items.Add(new DropdownItem("Text2", "Images/Open.png", "method2", "TEST2")
{
ClickCommand = Command2
});
ButtonDropdown.DropdownItems = Items;
}
public ICommand Command1
{
get
{
return _command1 = new RelayCommand(Method1);
}
}
public ICommand Command2
{
get
{
return _command2 = new RelayCommand(Method2);
}
}
public void Method1()
{
MessageBox.Show("Method 1");
}
public void Method2()
{
MessageBox.Show("Method 2");
}
And finally I add the call to this method in my generic.xaml
<MouseBinding Command="{Binding ClickCommand}" MouseAction="LeftClick" />
Thanks for your help, that's because of your comments and answers that I understood that I was looking in the bad direction
I really hope someone with more experience can give me a few pointers.
I have the following setup for a UWP project:
A ListView declared in XAML inside my application page, Tubes.xaml:
<ListView Name="TubesGrid"
ItemsSource="{x:Bind TubeItems, Mode=TwoWay}"
ItemTemplateSelector="{StaticResource TubeTemplateSelector}"
IsItemClickEnabled="True"
ItemClick="TubesGrid_ItemClick"
SelectionChanged="TubesGrid_SelectionChanged">
A UserControl as a template for the ListViewItem (UserControl.Resources):
<local:TubeTemplateSelector x:Key="TubeTemplateSelector"
TubeTemplate="{StaticResource TubeTemplate}">
</local:TubeTemplateSelector>
<DataTemplate x:Key="TubeTemplate" x:DataType="data:Tube">
<local:TubeTemplate HorizontalAlignment="Stretch" VerticalAlignment="Stretch" FavoritesNumber="{x:Bind SpaceLength, Mode=OneWay}"></local:TubeTemplate>
</DataTemplate>
Inside the TubeTemplate I have a button, beside other views:
<Button Name="RemoveTube"
Click="RemoveTube_Click"
<Image
Source="../Assets/xIcon.png"
Stretch="None">
</Image>
</Button>
What I'm trying to achieve:
When I click the ListViewItem I want the ItemClick event to be triggered. This works.
But when I click on the Button that's inside the ListViewItem I want a different event to be triggered inside the main page.
The idea is to click on an item to select it, but when I click the button inside the item, I want that item to be removed.
What are my options?
If looks like are doing this without using viewmodels, then you could add an event to the TubeTemplate control.
public event EventHandler Closed;
When the close button is clicked, you would fire the event.
private void RemoveTube_Click(object sender, RoutedEventArgs e)
{
Closed?.Invoke(this, EventArgs.Empty); // Even better would be to give the item clicked (the data context)
}
Then, from within your MainPage you could subscribe to the event.
<local:TubeTemplate HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Closed="TubeTemplate_Closed">
</local:TubeTemplate>
In the TubeTemplate_Closed method, you could remove the item clicked.
private void TubeTemplate_Closed(object sender, EventArgs e)
{
var element = (FrameworkElement)sender;
var tube = (Tube)element.DataContext;
TubeItems.Remove(tube);
}
The idea is to click on an item to select it, but when I click the button inside the item, I want that item to be removed.
The better way is bind button command property with MainPage command method, and process the data source in the code behind. you could refer the following code.
Code Behind
public sealed partial class MainPage : Page
{
public MainPage()
{
MakeDataSource();
this.InitializeComponent();
DataContext = this;
}
public ObservableCollection<string> Items { get; set; }
private void MakeDataSource()
{
Items = new ObservableCollection<string>() { "Nico","CCor","Jack"};
}
public ICommand BtnCommand
{
get
{
return new CommadEventHandler<object>((s) => BtnClick(s));
}
}
private void BtnClick(object s)
{
Items.Remove(s as string);
}
}
public class CommadEventHandler<T> : ICommand
{
public event EventHandler CanExecuteChanged;
public Action<T> action;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this.action((T)parameter);
}
public CommadEventHandler(Action<T> action)
{
this.action = action;
}
}
Xaml code
Please note we need pass current focus listview item parameter to command method and remove it from data souce.
<Grid HorizontalAlignment="Stretch" x:Name="RootGrid">
<ListView ItemsSource="{Binding Items}" x:Name="MyListView">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<TextBlock Text="{Binding}" VerticalAlignment="Center"/>
<Button HorizontalAlignment="Right"
Margin="0,0,30,0"
Content="Favorite"
Command="{Binding ElementName=MyListView,Path=DataContext.BtnCommand}"
CommandParameter="{Binding}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
I asked a question a bit earlier about the same thing, but I have an other problem.
I need help about a dynamic menu I have. I want to get the "path" of objects in my menu.
Here is my wpf, you can see I binded Value="{Binding name}"/> because I want to display the names in my menu, not the path:
<Menu HorizontalAlignment="Left" Height="30" VerticalAlignment="Top" Width="525" IsMainMenu="True">
<MenuItem Header="Menu" x:Name="myList" Click="myList_Click">
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Header" Value="{Binding name}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
I binded a collection with "this.myList.ItemsSource = list;", it's what displays my dynamic menu,so here's my mainwindow.cs:
public MainWindow()
{
var list = new List<History>
{
new History() { name = "Guy1", path = "C:/F1/}"},
new History() { name = "Guy2", path = "C:/F2/"},
new History() { name = "Guy3", path = "C:/F3/"},
new History() { name = "Guy4", path = "C:/F4"},
};
InitializeComponent();
this.myList.ItemsSource = list;
}
private void myList_Click(object sender, RoutedEventArgs e)
{
DependencyObject obj = (DependencyObject)e.OriginalSource;
while (obj != null && obj != myList)
{
if (obj.GetType() == typeof(MenuItem))
{
MessageBox.Show((e.OriginalSource as MenuItem).Header.ToString());
break;
}
obj = VisualTreeHelper.GetParent(obj);
}
}
And here is a simple class that I named "History":
namespace MediaPlayer
{
public class History
{
// "prop" puis "tab"
public String name { get; set; }
public String path { get; set; }
public int time { get; set; }
public override string ToString()
{
return path;
}
}
}
What I want to do is when I click on, for example, Guy1, I want my software to display a message that says "C:/F1/" because it's Guy1, for GUY2 I want "C:/F2/".
Do you have any idea? Do you know how can I acess the path of my object by using something like this? : MessageBox.Show((e.OriginalSource as MenuItem).Header.ToString());
Thanks.
EDIT :
<Menu HorizontalAlignment="Left" Height="30" VerticalAlignment="Top" Width="525" IsMainMenu="True">
<MenuItem Header="Menu" x:Name="myList" Click="myList_Click">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<EventSetter Event="Click" Handler="MyItemClick"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
(easy solution)
You can use an EventSetter inside the Style for your MenuItem. Syntax as such:
<EventSetter Event="Click" Handler="MyItemClick" />
Inside the event handler code you can find out which item was clicked:
public void MyItemClick(object sender, EventArgs e)
{
var clickedMenuItem = (MenuItem)sender;
var clickedHistoryItem = (History)clickedMenuItem.DataContext;
//do stuff with whatever "guy" was clicked
}
(more advanced option)
Note that using the MVVM Pattern, you can create a Command object inside the history item, and handle whatever you need to handle from there. The syntax would be:
<Setter Property="Command" Value="{Binding ClickCommand}" />
You can look into the MVVM pattern, but the learning curve is quite steep. It will definetely pay off once your program grows through.
I have a ListView/GridView setup and I want to handle a right click on the dislayed items. Is there are Databinding-way of doing this? I have seen complicated workarounds like handling the super-elements event and poking around to find its origin, but that feels awfully bloated for such basic request.
What I'd love to see is something like binding the event to an action of the item's ViewModel - is there a way to do that? Similar to this, but I can't quite wrap my head around how to adapt that to work on a single ListView item (I am not even sure that's possible, tbh).
Rough outline:
<ListView>
<ListView.View>
<GridView />
</ListView.View>
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}">
</Style>
</ListView.Resources>
</ListView/>
There is a way using the Interactivity assembly form the Blend SDK. It will provide an EventTrigger which executes a command when an event is raised.
<!--
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
-->
<Button Content="Click me">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding ClickCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Edit:
A possible solution for your problem could look like this:
View:
<ListView x:Name="listView">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseRightButtonUp">
<i:InvokeCommandAction Command="{Binding RightClickOnItemCommand}"
CommandParameter={Binding SelectedItem, ElementName=listView} />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
ViewModel:
public ICommand RightClickOnItemCommand { get; set; }
public void RightClickOnItem(object item)
{
}
You could try to create a style template for the list view item, and add an attached behaviour to it to handle mouse clicks.
public static readonly DependencyProperty PreviewMouseLeftButtonDownCommandProperty =
DependencyProperty.RegisterAttached("PreviewMouseLeftButtonDownCommand", typeof (ICommand),
typeof (MouseBehaviour), new FrameworkPropertyMetadata(PreviewMouseLeftButtonDownCommandChanged));
private static void PreviewMouseLeftButtonDownCommandChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs args)
{
var element = (FrameworkElement) dependencyObject;
element.PreviewMouseLeftButtonDown += Element_PreviewMouseLeftButtonDown;
}
private static void Element_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs args)
{
var element = (FrameworkElement) sender;
ICommand command = GetPreviewMouseLeftButtonDownCommand(element);
if (command != null)
{
command.Execute(args);
}
}
public static void SetPreviewMouseLeftButtonDownCommand(UIElement element, ICommand value)
{
element.SetValue(PreviewMouseLeftButtonDownCommandProperty, value);
}
public static ICommand GetPreviewMouseLeftButtonDownCommand(UIElement element)
{
return (ICommand) element.GetValue(PreviewMouseLeftButtonDownCommandProperty);
}