How to add an option to selectively remove items from ComboBox? - c#

I have a ComboBox that displays strings. How can I add an option to remove some items from the ComboBox list? I tried:
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove" Click="MenuItem_OnClick"></MenuItem>
</ContextMenu>
</ComboBox.ContextMenu>
But I don't know how to locate the item the user chose:
private void MenuItem_OnClick(object sender, RoutedEventArgs e) {
/* ... ??? ... */
}
I don't mind putting some icon next to each item, that removes its related item when clicked, but don't know how to do it..
Summary:
This is how I solved it, finally (The credit belongs to Nawed Nabi Zada, who provided the main idea of "climbing" using the VisualTreeHelper.GetParent(...) to get the ComboBoxItem, in the accepted answer, below)
<ComboBox IsEditable="True" Name="RemotePathComboBox" VerticalAlignment="Center"
SelectionChanged="RemotePathComboBoxOnSelectionChanged"
Grid.Column="1" Margin="0,6" KeyUp="HostNameOrIPAddress_OnKeyUp">
<ComboBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<Button Click="RemoveRemotePathItem_Click" Margin="5" DockPanel.Dock="Left">
<Image Source="{Binding Converter={StaticResource iconExtractor}, ConverterParameter=%WinDir%\\System32\\shell32.dll|131}"/>
</Button>
<TextBlock Name="ItemTextBlock" VerticalAlignment="Center" Text="{Binding Path=Path}"></TextBlock>
</DockPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Code-behind:
private void RemoveRemotePathItem_Click(object sender, RoutedEventArgs e) {
var depObj = sender as DependencyObject;
while (!(depObj is ComboBoxItem)) {
if (depObj == null) return;
depObj = VisualTreeHelper.GetParent(depObj);
}
var comboBoxItem = depObj as ComboBoxItem;
var item = comboBoxItem.Content as RemotePathItem;
_remotePathsList.Remove(item);
RemotePathComboBox_SelectIndexWithoutChangingList(0);
}
(The "Icon Extractor" that fetches the icon from the system's DLL is from an old post of mine)

You can also do it this way:
<Window x:Class="RemoveItemsFromComboBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ComboBox x:Name="CbxItems" VerticalAlignment="Top" HorizontalAlignment="Left" Width="250">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem x:Name="MenuItem" Header="Delete" Click="MenuItem_OnClick"></MenuItem>
</ContextMenu>
</ComboBox.ContextMenu>
<TextBlock Text="Item 1"/>
<TextBlock Text="Item 2"/>
<TextBlock Text="Item 3"/>
<TextBlock Text="Item 4"/>
</ComboBox>
</Grid>
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
CbxItems.PreviewMouseRightButtonDown += OnPreviewMouseRightButtonDown;
}
private void OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var comboBoxItem = VisualUpwardSearch(e.OriginalSource as DependencyObject);
if (comboBoxItem == null) return;
comboBoxItem.IsSelected = true;
e.Handled = true;
}
private ComboBoxItem VisualUpwardSearch(DependencyObject source)
{
while (source != null && !(source is ComboBoxItem))
source = VisualTreeHelper.GetParent(source);
return source as ComboBoxItem;
}
private void MenuItem_OnClick(object sender, RoutedEventArgs e)
{
CbxItems.Items.Remove(CbxItems.SelectedItem);
}
}

Put that ContextMenu for each ComboBoxItem instead of the ComboBox itself :
<ComboBoxItem.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove" Click="MenuItem_OnClick"></MenuItem>
</ContextMenu>
</ComboBoxItem.ContextMenu>
You can also put that in DataTemplate or generate it from code behind, depending on how you populate the ComboBox. Then in menu item's click event handler you can do as follow to get user chosen ComboBoxItem :
private void MenuItem_OnClick(object sender, RoutedEventArgs e)
{
var menuItem = (MenuItem)sender;
var ctxMenu = (ContextMenu)menuItem.Parent;
var comboBoxItem = (ComboBoxItem) ctxMenu.PlacementTarget;
}

For locating the combobox items, you can use checkbox in the item template of the combobox so that user can check the items which he/she wants to delete.
If your combobox is data bound, then you will have to filter the datasource of your combobox i.e. on context menu click you will have to delete the items checked by user from the datasource of your combobox and then re-bind the combobox with datasource.
If you don't have a data bound combobox, then on context menu click simply loop through the combobox items and delete the items which are checked by user.

Related

UWP C# SQL Webservice GridView triggers

i have class created for data retrieval
I have XAML code with gridview and cs code which connects to SQL webservice and i can get data from SQL :-)
my gridview has
textblock - data from sql table
checkbox
textbox
I would like to have some actions on the checkbox and textboxes. How do I get my textboxes to become visible upon checkbox click?
I have got this code working in other apps without gridviews, but I can't get it to work here. how do I reference the event_handler inside the gridview
XAML example
<GridView x:Name="GreenQuestionGridView" ItemsSource="{Binding}" Background="Green" Margin="0,40,0,0">
<GridView.ItemTemplate>
<DataTemplate>
<Grid Height="40" Width="600" >
<StackPanel Orientation="Horizontal">
<TextBlock Width="200" VerticalAlignment="Bottom" TextWrapping="Wrap" Text="{Binding question_green}" />
<CheckBox x:Name="chkBox" Checked="chkBox_Checked" Unchecked="chkBox_Unchecked" Indeterminate="chkBox_Indeterminate" VerticalAlignment="Bottom" IsThreeState="True" />
<TextBox x:Name="txtBox" Visibility="Collapsed" Width="200" VerticalAlignment="Bottom" />
</StackPanel>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
here is code that works in other app, but this needs to reference the gridview
private void chkBox_Checked(object sender, RoutedEventArgs e)
{
if (chkbox.IsChecked == null)
{
txtbox.Visibility = Visibility.Visible;
}
else
{
txtbox.Visibility = Visibility.Collapsed;
}
}
For the CheckBox you need also the event CheckBox_Unchecked to hide it again.
<CheckBox Unchecked="CheckBox_Unchecked" Checked="CheckBox_Checked" ... />
IsChecked is a nullable type you didnt even checked if it's true. Your code will hide the txtbox so long as the IsChecked is not null.
private static void ToggleTextBoxVisibility(object sender) {
if(!(sender is CheckBox)) {
return;
}
CheckBox checkBox = sender as CheckBox;
foreach(var child in ((checkBox.Parent as StackPanel).Children)) {
if(!(child is TextBox)) {
continue;
}
TextBox textBox = child as TextBox;
if(checkBox.IsChecked.HasValue && checkBox.IsChecked.Value) {
textBox.Visibility = Visibility.Visible;
} else {
textBox.Visibility = Visibility.Collapsed;
}
}
}
private void CheckBox_Checked(object sender, RoutedEventArgs e) {
ToggleTextBoxVisibility(sender);
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e) {
ToggleTextBoxVisibility(sender);
}
A clean solution would be to control it with a binding to a property in your viewmodel.

Pass image in a new window in WPF/C#

I've got some images in a ListBox. When the user clicks one image, I'd like to open a new window (ImageWindow) and show the clicked image in the new window. I've added already a new XAML-file and a eventhandler. This is what I got:
MainWindow:
<ListBox Name="MainListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel HorizontalAlignment="Center">
<Image Source="{Binding}" MouseDown="Image_MouseDown"></Image>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
/*========================================================================*/
private void Image_MouseDown(object sender, MouseButtonEventArgs e)
{
ImageWindow imageWindow = new ImageWindow();
//Pass image
imageWindow.Show();
}
ImageWindow:
<ListBox Name="ImageListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel HorizontalAlignment="Center">
<Image Source="{Binding}"></Image>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
How do I pass the clicked image?
See example (click on the image)
Just copy, past and tune this code so it fits your varnames:
private void ListBox_MouseDoubleClick(object sender, MouseButtonEventArgs e) //Varname
{
ImageWindow imageWindow = new ImageWindow { Owner = this };
foreach (var item in ListBox.Items) //Varname
{
imageWindow.ListBox.Items.Add(item);//Varname
}
imageWindow.SetSelectedImageIndex = ListBox.SelectedIndex; //Varname + save the index of the selected item and pass it to ImageWindow
imageWindow.Show();
}
ImageWindow:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Application.Current.MainWindow.WindowState = WindowState.Normal;
ListBoxItem lbi = (ListBoxItem)ImageListBox.ItemContainerGenerator.ContainerFromIndex(SetSelectedImageIndex); //Get with the index the befor selected item
lbi.Focus(); //Set the focus on it
}
You can start with something like this:
<Grid
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">
<Popup x:Name="popup" PlacementTarget="{Binding ElementName=imageList}">
<Image Source="{Binding PlacementTarget.SelectedItem , ElementName=popup}"/>
</Popup>
<ListView x:Name="imageList" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<ei:ChangePropertyAction PropertyName="IsOpen"
TargetName="{Binding ElementName=popup}" Value="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
</Grid>
Add references to Microsoft.Expression.Interactions and to System.Windows.Interactivity to get it work.

How to select an item in a ListBox and pick the item's name from a DataTemplate in WPF

I am implementing a Download UI in WPF, where every file that is being downloaded will be shown inside a list box in a DataTemplate
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="FileName" text={Binding FileName}" />
<ProgressBar ... />
<Button Content="Cancel" click="ButtonCancel_Click" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox>
Now this List is getting populated with all the download information perfectly. Only problem I am having is that when user clicks on Cancel button, to cancel the download, I have to remove an entry from the ObservableCollections. But I don't have the File Name in the click event( I know click event is not MVVM, still I want to do it in click event handler).
Can anyone suggest how do I get the FileName of that particular file when the selectedItem gets cancelled. in The
private void ButtonCancel_Click(...) {}
Although I would still encourage you to use MVVM way of dealing with UI events, here's how you can achieve what you want, using Cancel button's click event handler.
First in your xaml, bind file name to Cancel button's Tag property.
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="FileName" text={Binding FileName}" />
<ProgressBar ... />
<Button Content="Cancel" Tag="{Binding FileName}"
Click="ButtonCancel_Click" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox>
Then in your click event handler
private void ButtonCancel_Click(object sender, RoutedEventArgs e)
{
Button myButton = (Button)sender;
string fileName = myButton.Tag.ToString();
// use fileName
}
Edit
Just to add a complete example, that was tested locally, and ensured that works.
XAML
<Window x:Class="WpfTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox Name="listBox1">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock x:Name="FileName" Text="{Binding Path=FileName}" />
<Button Content="Cancel" Tag="{Binding Path=FileName}"
Click="ButtonCancel_Click" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var fileNames = new List<DownloadModel>
{
new DownloadModel
{
FileName = "File1"
},
new DownloadModel
{
FileName = "File2"
},
new DownloadModel
{
FileName = "File3"
}
};
listBox1.ItemsSource = fileNames;
}
private void ButtonCancel_Click(object sender, RoutedEventArgs e)
{
var myButton = sender as Button;
if (myButton.Tag == null)
{
MessageBox.Show("Tag value was null.");
}
else
{
MessageBox.Show(string.Format("File name is {0}", myButton.Tag));
}
}
}
public class DownloadModel
{
public string FileName { get; set; }
}

Determining which listboxitem is clicked on in a listbox when executing a context menu

I am trying to use context menu in a listbox to run some come code.that require data from which item it originated.the click event context menu item shows msg but i found that it doent not access the originating listview item .
<Canvas x:Name="LeftCanvas" Grid.Column="0" Grid.Row="1" Margin="5,0,0,0">
<StackPanel>
<TextBlock Text="Unseated Guests" Background="Blue" Foreground="White" FontFamily="Verdana" FontSize="11" FontWeight="Bold" Height="17" Width="150" HorizontalAlignment="Left" TextAlignment="Center" Padding="0,4,5,2"></TextBlock>
<ListBox x:Name="UnseatedPersons" ItemsSource="{Binding}" Height="218" Width="150" BorderBrush="Blue" BorderThickness="2" HorizontalAlignment="Left" Padding="3,2,2,2" src:FloorPlanClass.DragEnabled="true" MouseEnter="UnseatedPersons_MouseEnter"
MouseLeave="SourceListBox_MouseLeave">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<DockPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Archive Info" Click="bt_click" />
<MenuItem Header="Guest Info" />
</ContextMenu>
</DockPanel.ContextMenu>
<Image Name="imgPerson" Source="{Binding ImagePath}" />
<TextBlock Name="txtPersonName" Text="{Binding PersonName}" Padding="2,4,0,0" />
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Canvas>
C#:
void bt_click(object sender, RoutedEventArgs e)
{
MessageBox.Show("my message");
}
Use the sender by casting them to MenuItem. Like:
void bt_click(object sender, RoutedEventArgs e)
{
MenuItem originalItem = (MenuItem)sender;
MessageBox.Show(string.Format("clicked from \"{0}\"", originalItem.Name));
}
The sender in the click event will be the MenuItem you clicked.
Its parent will be the ContextMenu
The PlacementTarget of the ContextMenu will be the DockPanel.
The DockPanel will have the ListBoxItem as an ancestor in the Visual Tree
So to get the ListBoxItem in the click event you can use something similar to this
private void bt_click(object sender, RoutedEventArgs e)
{
MenuItem clickedMenuItem = sender as MenuItem;
ContextMenu contextMenu = clickedMenuItem.Parent as ContextMenu;
DockPanel dockPanel = contextMenu.PlacementTarget as DockPanel;
ListBoxItem listBoxItem = GetVisualParent<ListBoxItem>(dockPanel);
MessageBox.Show(listBoxItem.ToString());
// Update. To display the content of the ListBoxItem
MessageBox.Show(listBoxItem.Content.ToString());
}
public static T GetVisualParent<T>(object childObject) where T : Visual
{
DependencyObject child = childObject as DependencyObject;
// iteratively traverse the visual tree
while ((child != null) && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}

Getting value of TextBlock inside ComboBox DataTemplate

I have the following XAML:
<ComboBox Height="23" HorizontalAlignment="Left" Grid.Row="6" Grid.Column="2"
Name="cbo_team" VerticalAlignment="Top" Width="148"
DataContext="{Binding ElementName=cbo_component, Path=SelectedItem}"
SelectedIndex="0">
<ComboBox.ItemsSource>
<Binding XPath="Teams/Team/#id"
Converter="{StaticResource xmlConverter}">
<Binding.ConverterParameter>
<local:XmlConverterParameter
XPathTemplate="/Products/Teams/Team[{0}]"
XPathCondition="#id='{0}'" />
</Binding.ConverterParameter>
</Binding>
</ComboBox.ItemsSource>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding XPath=#name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
In C#, I'm trying to get the value of the TextBlock that is in the current selected item in the ComboBox. How do I do that? This question is pretty much the same, but the only answer doesn't help.
Check this sample out. The textblock (below the combobox) is showing the value of the name attribute of the currently selected xml element in the combobox. A message box will popup with the same result from the lookup in the visual tree. The lookup fails on initial selection changed. Looks like comboboxitems are created after selected item is set.
XAML:
<Window x:Class="CBTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Window.Resources>
<XmlDataProvider x:Key="UsersData" XPath="Users">
<x:XData>
<Users xmlns="">
<User name="Sally" />
<User name="Lucy" />
<User name="Linus" />
<User name="Charlie" />
</Users>
</x:XData>
</XmlDataProvider>
</Window.Resources>
<StackPanel>
<ComboBox
Name="_comboBox"
ItemsSource="{Binding Source={StaticResource UsersData},XPath=*}"
SelectedIndex="0"
SelectionChanged="OnComboBoxSelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding XPath=#name}" Name="nameTextBlock" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- Below shows how to get the value of selected item directly from the data. -->
<TextBlock
DataContext="{Binding Path=SelectedItem, ElementName=_comboBox}"
Text="{Binding XPath=#name}" />
</StackPanel>
</Window>
Code behind, showing how to get the text directly by traversing the visual tree:
private void OnComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox comboBox = sender as ComboBox;
ComboBoxItem comboBoxItem = comboBox.ItemContainerGenerator.ContainerFromItem(comboBox.SelectedItem) as ComboBoxItem;
if (comboBoxItem == null)
{
return;
}
TextBlock textBlock = FindVisualChildByName<TextBlock>(comboBoxItem, "nameTextBlock");
MessageBox.Show(textBlock.Text);
}
private static T FindVisualChildByName<T>(DependencyObject parent, string name) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
string controlName = child.GetValue(NameProperty) as string;
if (controlName == name)
{
return child as T;
}
T result = FindVisualChildByName<T>(child, name);
if (result != null)
return result;
}
return null;
}
Sorry a little late to the party :) but the following works as well (ironically was in the same fix as you!!)
TextBlock tb1 = (TextBlock)cbo_team.SelectedItem;
MessageBox.Show(tb1.Text);
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox ContactListBox = sender as ListBox;
ListBoxItem listBoxItem = ContactListBox .ItemContainerGenerator.ContainerFromItem(ContactListBox.SelectedItem) as ListBoxItem;
if (listBoxItem == null)
{
return;
}
TextBlock txtBlock = FindVisualChildByName<TextBlock>(listBoxItem, "ListTextBlock");
MessageBox.Show(txtBlock.Text);
}
private static T FindVisualChildByName<T>(DependencyObject parent, string name) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
string controlName = child.GetValue(NameProperty) as string;
if (controlName == name)
{
return child as T;
}
T result = FindVisualChildByName<T>(child, name);
if (result != null)
return result;
}
return null;
}
Others already suggested to use a SelectionChanged event. Didn't test the code below, but you may give it a try.
private void OnMyComboBoxChanged(object sender, SelectionChangedEventArgs e)
{
TextBlock tvContent = (sender as ComboBox).SelectedItem as TextBlock;
string content = tvContent.Text;
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var comboBox = (ComboBox)sender;
ComboBoxItem comboAsTextblock = (ComboBoxItem)comboBox.SelectedItem;
string comboBoxItemText = comboAsTextblock.Content.ToString();
// comboBoxItemText is what you want :)
}

Categories