I can's set PlacementTarget for ContextMenu. It is always opened (via Shift+F10) in the center of listbox.
I tried:
private void listBox_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.KeyboardDevice.Modifiers == ModifierKeys.Shift &&
(e.Key == Key.F10 || e.SystemKey == Key.F10)){
var listBox = sender as System.Windows.Controls.ListBox;
listBox.ContextMenu.PlacementTarget = listBox.ItemContainerGenerator.ContainerFromItem(listBox.SelectedItem) as ListBoxItem;
}
}
and
private void listBox_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
var listBox = sender as System.Windows.Controls.ListBox;
listBox.ContextMenu.PlacementTarget = listBox.ItemContainerGenerator.ContainerFromItem(listBox.SelectedItem) as ListBoxItem;
}
But it still doesn't work as expected. (I expect it is shown in the center of selected itemlistbox)
Any suggestions?
I've just tried your code. The problem is you cannot change the PlacementTarget of the the ContextMenu once it is set to the ListBox. That means the ListBox is always set as PlacementTarget of the ContextMenu. I understand that that ContextMenu is in fact used for the selected item. So why not set it for each item? Then it works expectedly. Try this:
<ListBox ItemsSource="some_source_here"/>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContextMenu">
<Setter.Value>
<!-- your ContextMenu here -->
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
There is not any code behind involved here. Just change your XAML like above.
Related
How can I change the label text based on where I hover my mouse on combobox items?
for example pic 1
when my mouse is over a item, but WITHOUT selecting it, I want the label to change to same text as the item where my mouse is over..
to change the text I do this
flatLabel2.Text = flatComboBox1.SelectedItem.ToString().ToUpper();
but cant find how to do it when mouse is hover a item, maybe I am googling in a wrong way.
anyone has any solution?
If it's WPF project, as I found before, you have to trigger MouseOver event on items by using DependencyPropertyDescriptor like this :
DependencyPropertyDescriptor dpd;
private void ComboBox_Loaded(object sender, RoutedEventArgs e)
{
ComboBoxItem cmb = sender as ComboBoxItem;
dpd = DependencyPropertyDescriptor
.FromProperty(IsMouseOverProperty, typeof(ComboBoxItem));
if (dpd != null)
dpd.AddValueChanged(cmb, OnIsMouseOver);
}
private void ComboBox_Unloaded(object sender, RoutedEventArgs e)
{
if (dpd != null)
dpd.RemoveValueChanged(cmb, OnIsMouseOver);
}
private void OnIsMouseOver(object sender, EventArgs e)
{
ComboBoxItem cmb = sender as ComboBoxItem;
if (cmb.IsMouseOver)
{
//do something...
}
}
and on view :
<ComboBox x:Name="cmb">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<EventSetter Event="Loaded" Handler="ComboBox_Loaded" />
<EventSetter Event="Unloaded" Handler="ComboBox_Unloaded" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
I'm writing a Windows desktop app using C# and WPF. I've a combobox that is used to either type in a file path or to select from previously used file paths. The files in the list can become invalid if the file is deleted or the user entered a valid file but not the desired file. They've asked for a way to remove bad entries from the combobox drop-down list. They want to right-click on an item and select Remove from the context menu.
<ComboBox x:Name="cbDocket" IsEditable="True">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove" Click="cbDocket_MenuItemRemove">/>
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBo>
That's the easy part. What I cannot figure out is how to determine which item they selected. Searching hasn't found any suggestions that work. Any help would be appreciated. Is there another way to do this that would be easier? Oh yes, I fairly new to both C# and WPF.
Thanks, Brian
Context menus in WPF are pretty badly broken by design, so about half of all normal cases, anything you do that actually works is a kludge. I've come up with a few; here's another that's not really all that kludgey.
XAML:
<ComboBox x:Name="cbDocket" IsEditable="True">
<ComboBox.Resources>
<ContextMenu x:Key="ItemMenu">
<MenuItem Header="Remove" Click="MenuItem_Click" />
</ContextMenu>
</ComboBox.Resources>
<!-- Some arbitrary random junk to display in the ComboBox -->
<TextBlock Text="Foo" />
<TextBlock Text="Bar" />
<!-- End of arbitrary random junk -->
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter
Property="ContextMenu"
Value="{StaticResource ItemMenu}"
/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
Code behind:
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
// This needs some null checking and a try catch, but this is the guts of it.
// sender should be the MenuItem as well
var menuItem = e.OriginalSource as MenuItem;
// Since we used ItemContainerStyle to give each ComboBoxItem its own
// personal ContextMenu, each ContextMenu will have its PlacementTarget
// set to the ComboBoxItem that owns it.
var cbItem = (menuItem.Parent as ContextMenu).PlacementTarget as ComboBoxItem;
// ???
//var dataItem = cbItem.DataContext;
}
You didn't mention how you're populating the combo box, so I don't know whether you've got DataContext set on the items or what. But once you have the ComboBoxItem, you can get there.
Add an event to track the mouse movement while the dropdown is open and use that when the remove is clicked.
<ComboBox x:Name="cbDocket" HorizontalAlignment="Left" Margin="33,30,0,269" Width="124" IsEditable="True">
<ComboBox.ContextMenu>
<ContextMenu Name="Menu">
<MenuItem Header="Remove" Click="cbDocket_MenuItemRemove"/>
</ContextMenu>
</ComboBox.ContextMenu>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<EventSetter Event="MouseMove" Handler="cbDocket_OnMouseMove" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
Code behind:
ComboBoxItem cbiSelected = null;
private void cbDocket_MenuItemRemove(object sender, RoutedEventArgs e)
{
cbDocket.Items.Remove(cbiSelected);
}
private void cbDocket_OnMouseMove(object sender, MouseEventArgs e)
{
ComboBoxItem cbiHover = sender as ComboBoxItem;
if (cbiHover.IsHighlighted)
{//Verify the item is highlighted
cbiSelected = cbiHover;
}
}
There's a couple ways of doing this. Since you are using the Click event handler, then the source of the event is always the item that was clicked. For example:
Xaml:
<ComboBox x:Name="cbDocket" IsEditable="True">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove" Click="cbDocket_MenuItemRemove">/>
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBo>
Xaml.cs:
private void cbDocket_MenuItemRemove(object source, EventArgs args)
{
// Source is your item:
cbDocekt.Items.Remove(cbDocket.SelectedItem);
}
Of course you can always obtain the selected item, or even change the selected item in the combo box using the SelectedItem property.
I'm assuming you are using code behind to do this.
With the following program code you can add a context menu to a combo box. In this context menu a single item or all items of the combo box can be removed.
private ComboBox FolderComboBox;
private ContextMenu FolderHistoryContextMenu;
...
private void CreateFolderHistoryContextMenu()
{
MenuItem RemoveFolderMenuItem, RemoveAllFoldersMenuItem;
FolderHistoryContextMenu = new ContextMenu();
RemoveFolderMenuItem = new MenuItem();
RemoveFolderMenuItem.Header = "Remove folder";
RemoveFolderMenuItem.Click += new RoutedEventHandler(RemoveFolderMenuItem_Click);
FolderHistoryContextMenu.Items.Add(RemoveFolderMenuItem);
RemoveAllFoldersMenuItem = new MenuItem();
RemoveAllFoldersMenuItem.Header = "Remove all folders";
RemoveAllFoldersMenuItem.Click += new RoutedEventHandler(RemoveAllFoldersMenuItem_Click);
FolderHistoryContextMenu.Items.Add(RemoveAllFoldersMenuItem);
// The context menu is assigned to each item in the list of the combo box and not to the combo box itself.
if (FolderComboBox.ItemContainerStyle == null)
{
FolderComboBox.ItemContainerStyle = new Style(typeof(ComboBoxItem));
}
FolderComboBox.ItemContainerStyle.Setters.Add(new Setter(ContextMenuProperty, FolderHistoryContextMenu));
}
private void RemoveFolderMenuItem_Click(object sender, RoutedEventArgs e)
{
ComboBoxItem ClickedComboBoxItem = FolderHistoryContextMenu.PlacementTarget as ComboBoxItem;
if (ClickedComboBoxItem != null)
{
object ComboBoxDataItem = ClickedComboBoxItem.DataContext;
FolderComboBox.Items.Remove(ComboBoxDataItem);
}
}
private void RemoveAllFoldersMenuItem_Click(object sender, RoutedEventArgs e)
{
FolderComboBox.Items.Clear();
}
I need something that arranges/sorts items like a wrappanel but allows you to rearrange the items via drag & drop. I'm trying to prototype this out as quick as possible, so I'm just looking for something stupidly simple or hacky for now. I've been looking all over the place, but the only answers I could find required a relatively large amount of work to implement.
I would prefer not using an existing library if it's possible.
EDIT
To clarify: I need drag & drop as well as auto-rearrange mechanics within a control that arranges items like a wrappanel.
Since posting this question, I've found two posts/articles that seemed to be a good fit but only if combined.
Wrappanel: http://www.codeproject.com/Articles/18561/Custom-ListBox-Layout-in-WPF
Drag-drop & arrangement: WPF C#: Rearrange items in listbox via drag and drop
I'll post my code in an answer when I get it all working.
It took some time, but I was able to figure it out after Micky's suggestion. I'm posting an answer here for future reference and if anyone else is looking for this mechanic. The following code snippets should work by just pasting them into their appropriate files.
Here's what worked:
Make the ListBox arrange items like a WrapPanel via a custom/default style (mine is called Default.xaml).
<Style TargetType="{x:Type ListBox}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<!--'WrapPanel' can be replaced with other controls if you want it to display differently-->
<WrapPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<!--Controls in each item-->
<local:DocPage />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Set up the ListBox control (i.e. MainWindow.xaml):
<ListBox x:Name="lbx_Pages" AllowDrop="True" DragEnter="lbx_Pages_DragEnter" PreviewMouseLeftButtonDown="lbx_Pages_PreviewMouseLeftButtonDown" PreviewMouseMove="lbx_Pages_PreviewMouseMove" Drop="lbx_Pages_PagesDrop"/>
Impliment the controls in the .cs file (i.e. MainWindow.xaml.cs):
private Point dragStartPoint;
// Bindable list of pages (binding logic omitted-out of scope of this post)
private static ObservableCollection<DocPage> pages = new ObservableCollection<DocPage>();
// Find parent of 'child' of type 'T'
public static T FindParent<T>(DependencyObject child) where T : DependencyObject
{
do
{
if (child is T)
return (T)child;
child = VisualTreeHelper.GetParent(child);
} while (child != null);
return null;
}
private void lbx_Pages_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
dragStartPoint = e.GetPosition(null);
}
private void lbx_Pages_PreviewMouseMove(object sender, MouseEventArgs e)
{
ListBoxItem item = null;
DataObject dragData;
ListBox listBox;
DocPage page;
// Is LMB down and did the mouse move far enough to register a drag?
if (e.LeftButton == MouseButtonState.Pressed &&
(Math.Abs(dragStartPoint.X - e.GetPosition(null).X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(dragStartPoint.Y - e.GetPosition(null).Y) > SystemParameters.MinimumVerticalDragDistance))
{
// Get the ListBoxItem object from the object being dragged
item = FindParent<ListBoxItem>((DependencyObject)e.OriginalSource);
if (null != item)
{
listBox = sender as ListBox;
page = (DocPage)listBox.ItemContainerGenerator.ItemFromContainer(item);
dragData = new DataObject("pages", page);
DragDrop.DoDragDrop(item, dragData, DragDropEffects.Move);
}
}
}
private void lbx_Pages_PagesDrop(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent("pages"))
return;
DocPage draggedItem = e.Data.GetData("pages") as DocPage;
// Hit-test needed for rearranging items in the same ListBox
HitTestResult hit = VisualTreeHelper.HitTest((ListBox)sender, e.GetPosition((ListBox)sender));
DocPage target = (DocPage)FindParent<ListBoxItem>(hit.VisualHit).DataContext;
int removeIdx = lbx_Pages.Items.IndexOf(draggedItem);
int targetIdx = lbx_Pages.Items.IndexOf(target);
if(removeIdx < targetIdx)
{
pages.Insert(targetIdx + 1, draggedItem);
pages.RemoveAt(removeIdx);
}
else
{
removeIdx++;
if(pages.Count+1 > removeIdx)
{
pages.Insert(targetIdx, draggedItem);
pages.RemoveAt(removeIdx);
}
}
}
private void lbx_Pages_DragEnter(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent("pages") || sender == e.Source)
e.Effects = DragDropEffects.None;
}
I'm trying to get the value of the highlighted item of the dropdown list in a ComboBox while the dropdown list is still open.
This is because I want to show a different ToolTip for all the element in the dropdown list based on the highlighted item.
I have found some information here:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/822f85e7-524a-4af2-b09a-c88c94971ac0/identifying-the-highlighted-item-in-a-combobox
but seems to be difficult and with a lot of code behind...
I have also try to use the IsHighlighted property of ComboBoxItem on SelectionChanged... But I give the item selected and not the highlighted one.
I also try to cycling the elements in the ComboBox in the get of property that I bind (with Databinding) to the ToolTip property of ComboBoxItems, using a function like:
foreach (ComboBoxItem comboBoxItem in comboBox.Items)
{
if (comboBoxItem.IsHighlighted == true)
{
//Do something
break;
}
}
But I probably do something wrong... Because comboBoxItem.IsHighlighted it is always false...
Thanks to this resources:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/ce14fc29-d320-4557-abc5-81b042730c48/how-to-get-current-combobox-item-on-which-mouse-overs-in-wpf
I found this solution:
In the WPF:
<ComboBox
Name="ComboBox1">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<EventSetter Event="MouseMove" Handler="OnMouseMove" />
</Style>
</ComboBox.ItemContainerStyle>
<ComboBoxItem
Content="Test1"></ComboBoxItem>
<ComboBoxItem
Content="Test2"></ComboBoxItem>
</ComboBox>
In the code behind:
private void OnMouseMove(object sender, MouseEventArgs e)
{
ComboBoxItem highlightedComboBoxItem = sender as ComboBoxItem;
// highlightedComboBoxItem is true
}
I want to make it so a double click is required to select an item in a ListBox. This selected item should always be bold. I know the SelectedItem property will no longer reflect the item I am treating as the selected item, so the XAML below that I was previously using to make the selected item bold will no longer work.
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
I have looked into how to handle a double click with MVVM and have concluded that it is ok to use code behind and the MouseDoubleClick event.
private void lbProfiles_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
_viewModel.SelectedProfile = ((ListBox)sender.)SelectedItem as MyProfile;
//What should go here?
}
My view model will have a SelectedProfile property that I think will be set in the method above. Is there anyway to bind SelectedProfile in XAML or will it have to be managed in the code behind? Also, what is the best way to make this item bold?
Edit 1:
I ended up tweaking Rachel's answer a little so that on a single click the item is highlighted but not selected. That way the view model can have a SelectedItem property and a HighlightedItem property.
private void ListBoxItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount < 2)
e.Handled = true;
var clickedItem = ((ContentPresenter)e.Source).Content as MyProfile;
if (clickedItem != null)
{
//Let view model know a new item was clicked but not selected.
_modelView.HighlightedProfile = clickedItem;
foreach (var item in lbProfiles.Items)
{
ListBoxItem lbi =
lbProfiles.ItemContainerGenerator.ContainerFromItem(item) as ListBoxItem;
//If item is not displayed on screen it may not have been created yet.
if (lbi != null)
{
if (item == clickedItem)
{
lbi.Background = SystemColors.ControlLightBrush;
}
else
{
lbi.Background = lbProfiles.Background;
}
}
}
}
}
The easiest way to select an item on DoubleClick only is to mark the click event as Handled if the ClickCount is less than 2
This would also allow you to keep your Trigger that sets the text as Bold when it's selected
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="PreviewMouseDown" Handler="ListBoxItem_PreviewMouseDown" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
private void ListBoxItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount < 2)
e.Handled = true;
}
Just keep in mind that this disables all single click events on the ListBoxItem. If you want to allow some single-click events, you'll have to adjust the PreviewMouseDown event to not mark specific clicks as Handled.