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
}
Related
I have defined a label with name and I'm trying to access it but no luck. Let me explain my problem with my code.
<ListView Name="gridListView" ItemsSource="{Binding... }">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Focusable" Value="false"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Border>
<StackPanel Orientation="Vertical">
<Label x:Name="vLabel" Content="{Binding VCValue}"/>
<ListView Name="checkBoxListView" ItemsSource="{Binding CList}">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Margin="5" Click="CheckBox_Click" IsChecked="{Binding SelectedValue, Mode=TwoWay}" Content="{Binding Current, Mode=OneWay }"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In the above code, I have two listview, gridListView and checkBoxListView. Here, I want to access the label vLabel which is inside the datatemplate of gridListView when the one of the value in checkbox(which is inside checkBoxListView) is clicked.
I understand it can't be accessed directly as its within datatemplate so i tried below code as suggested in other forums but gridListView.SelectedIndex is always -1 so i know i'm not doing the right thing. When I just hardcoded gridListView.SelectedIndex to index 0, 1 or 2 its giving me the right value of vLabel so the below code will work if gridListView.SelectedIndex is correct.
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
CheckBox chk =(CheckBox)sender;
int index = gridListView.Items.IndexOf(chk.DataContext);
ListViewItem item = gridListView.ItemContainerGenerator.ContainerFromIndex(gridListView.SelectedIndex) as ListViewItem;
if (item!=null)
{
//get the item's template parent
ContentPresenter templateParent = GetFrameworkElementByName<ContentPresenter>(item);
DataTemplate dataTemplate = gridListView.ItemTemplate;
if (dataTemplate != null && templateParent != null)
{
var lab = dataTemplate.FindName("vLabel", templateParent) as Label;
var v = lab.Content;
}
}
private static T GetFrameworkElementByName<T>(FrameworkElement referenceElement) where T : FrameworkElement
{
//I can post this function if need be
....
}
Appreciate any help that will help me access vLabel.
Thanks in advance
you are setting focusable to false, so you cant select the item by clicking. also the checkbox-checked happens before the selection, even if you were to set focusable to true, so selected index would still be -1.
you can find your label simply like this:
public Label FindLabel(CheckBox checkBox)
{
var listView = VisualTreeHelper.GetParent(checkBox);
while (listView.GetType() != typeof(ListView))
{
listView = VisualTreeHelper.GetParent(listView);
}
return (listView as FrameworkElement).FindName("vLabel") as Label;
}
but i suggest you tell us what you want to achieve, because this doesnt seem like a clean solution.
can you perhaps do this on your StackPanel:
<StackPanel CheckBox.Checked="CheckBox_Click" Orientation="Vertical">
and then access your two desired properties as following:
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
var outerItem = (sender as FrameworkElement).DataContext;
var innerItem = (e.OriginalSource as FrameworkElement).DataContext;
}
and then do whatever you want to do?
Thank you for all your guidance. I did get hint from Milan's code to achieve solution for my issue. Here, I'm trying to get reference parent of listviewItem(i.e stackpanel) and then access its child. In my case, stack panels child at index 0 is Label and at index 1 is ListView. So then I go through visualtreehelper to get reference to its child at index 0 which is what i need access to. So here is the code snippet.
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
CheckBox checkBox = (CheckBox)sender;
//to access parent of the checkbox
ListViewItem listViewItem =
GetVisualAncestor<ListViewItem>((DependencyObject)sender);
//to access parent of the listViewItem(which is parent of checkbox)
StackPanel stackPanel =
GetVisualAncestor<StackPanel>((DependencyObject)listViewItem);
int childCount = VisualTreeHelper.GetChildrenCount(stackPanel);
//to access child of stackpanel to access the current vLabel
var vValue = VisualTreeHelper.GetChild(stackPanel, 0) as Label;
}
private static T GetVisualAncestor<T>(DependencyObject o)where T : DependecyObject
{
...
}
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 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.
How to "Set" the SelectedIndex in DevExpress ComboBoxEdit?
I tried both in XAML and in code behind, but the index was not set, it starts out with a blank item.
My XAML: [I can't see why this doesn't work, but it doesn't..]
<dxb:BarEditItem.EditSettings>
<dxe:ComboBoxEditSettings>
<dxe:ComboBoxEditSettings.Items>
<dxe:ComboBoxEditItem IsSelected="True">AAA</dxe:ComboBoxEditItem>
<dxe:ComboBoxEditItem>BBB</dxe:ComboBoxEditItem>
<dxe:ComboBoxEditItem>CCC</dxe:ComboBoxEditItem>
</dxe:ComboBoxEditSettings.Items>
</dxe:ComboBoxEditSettings>
</dxb:BarEditItem.EditSettings>
My C# code: [I'm getting the countStr correctly so I'm sure the ComboBoxEdit and the items are initialized and added ok, but SelectedIndex still don't set the index..]
* also I do not want to use EditValue to set the value, I need to use an integer (Index) to set it.
private void Foo_LinkControlLoaded(object sender,
DevExpress.Xpf.Bars.BarItemLinkControlLoadedEventArgs e)
{
BarEditItemLink link = (BarEditItemLink)sender;
countStr = ((ComboBoxEdit)link.Editor).Items.Count.ToString();
((ComboBoxEdit)link.Editor).SelectedIndex = 2;
}
There are no SelectedIndex or SelectedItem property within the editor settings (e.g. ComboBoxEditSettings).
But you can set the SelectedIndex, SelectedItem or EditValue properties of ComboBoxEdit via the editor style:
<dxb:BarEditItem x:Name="beiComboBox">
<dxb:BarEditItem.EditStyle>
<Style TargetType="dxe:ComboBoxEdit">
<Setter Property="SelectedIndex" Value="1"/>
</Style>
</dxb:BarEditItem.EditStyle>
<dxb:BarEditItem.EditSettings>
<dxe:ComboBoxEditSettings>
<dxe:ComboBoxEditSettings.Items>
<dxe:ComboBoxEditItem>AAA</dxe:ComboBoxEditItem>
<dxe:ComboBoxEditItem>BBB</dxe:ComboBoxEditItem>
<dxe:ComboBoxEditItem>CCC</dxe:ComboBoxEditItem>
</dxe:ComboBoxEditSettings.Items>
</dxe:ComboBoxEditSettings>
</dxb:BarEditItem.EditSettings>
</dxb:BarEditItem>
You can also set a ComboBoxEdit.SelectedIndex property from codebehind if you catch the Loaded event:
<dxb:BarEditItem.EditStyle>
<Style TargetType="dxe:ComboBoxEdit">
<EventSetter Event="Loaded" Handler="ComboBoxEdit_Loaded"/>
</Style>
</dxb:BarEditItem.EditStyle>
//...
void ComboBoxEdit_Loaded(object sender, RoutedEventArgs e) {
((ComboBoxEdit)sender).SelectedIndex = 1;
}
I have implemented a selection pattern similar to the one described in this post using a ViewModel to store the IsSelected value, and by binding the ListViewItem.IsSelected to the ViewModel IsSelected:
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListView.ItemContainerStyle>
It works in general, but I encounter a severe problem. By using the a VirtualizingStackPanel as the panel in the list view, only the visible ListViewItem are getting created. If I use "Ctrl+A" to select all items, or by using shortcut combination such "Shift+Ctrl+End" on the first item, all items get selected, but for the non visible items, the ViewModel does not get its IsSelected set to true. That is logical, because if the ListViewItem are not created, the binding can't work.
Anybody experienced the same issue, and found a solution (apart from not using a VirtualizingStackPanel)?
I found another way of handling selection in the MVVM pattern, which solved my issue. Instead of maintaining the selection in the viewmodel, the selection is retrieved from the ListView/ListBox, and passed as a parameter to the Command. All done in XAML:
<ListView
x:Name="_items"
ItemsSource="{Binding Items}" ... />
<Button
Content="Remove Selected"
Command="{Binding RemoveSelectedItemsCommand}"
CommandParameter="{Binding ElementName=_items, Path=SelectedItems}"/>
in my ViewModel:
private void RemoveSelection(object parameter)
{
IList selection = (IList)parameter;
...
}
In my case, I ended up solving this by deriving a ListBoxEx class from ListBox, and adding code to respond to selection changes, enforcing the selection state on the item view models:
private readonly List<IListItemViewModelBase> selectedItems = new List<IListItemViewModelBase>();
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
bool isVirtualizing = VirtualizingStackPanel.GetIsVirtualizing(this);
bool isMultiSelect = (SelectionMode != SelectionMode.Single);
if (isVirtualizing && isMultiSelect)
{
var newSelectedItems = SelectedItems.Cast<IListItemViewModelBase>();
foreach (var deselectedItem in this.selectedItems.Except(newSelectedItems))
{
deselectedItem.IsSelected = false;
}
this.selectedItems.Clear();
this.selectedItems.AddRange(newSelectedItems);
foreach (var newlySelectedItem in this.selectedItems)
{
newlySelectedItem.IsSelected = true;
}
}
}
Apart from not using VirtualizingStackPanel, the only thing I can think of is to capture those keyboard shortcuts and have methods for modifying a certain range of your ViewModel items so that their IsSelected property is set to True (e.g., SelectAll(), SelectFromCurrentToEnd()). Basically bypassing the Binding on ListViewItem for controlling the selection for such cases.