WinForm ComboBox SelectedValue property vs SelectedIndex - winforms

I'm adding an "Index" object to each item in a Comboxbox
foreach (var index in indexes)
{
UniqueIndexComboBox.Items.Add(index);
}
When the user selects one of the index items from the drop, the following events are both fired. I'm not sure the difference.
private void UniqueIndexComboBox_SelectedValueChanged(object sender, EventArgs e)
private void UniqueIndexComboBox_SelectedIndexChanged(object sender, EventArgs e)
When I interegate the following properties, the SelectedValue is always null but I can still access the selected Index value by using the SelectedIndex value as an index into the items list.
Using a WinForm ComboBox, why would the Selected
? UniqueIndexComboBox.Items[UniqueIndexComboBox.SelectedIndex] == null
false
? UniqueIndexComboBox.SelectedValue == null
true
Why doesn't the SelectedValue option also work? Is the value of the DropDownStyle property relevant?

SelectedIndex is the Zero based index number (indirectly place number)
SelectedValue is the actual value of the selected item (which is invisible to user). in your case SelectedValue is always null as you have not supplied it as below.
to achieve ComboBox's SelectedValue, the combobox should be set it's DataSource Property not Items.Add() method
for example
var items = new List<object>();
for (int i = 1; i <= 10; i++)
{
items.Add(new { Value = i, Text = "Text "+i });
}
comboBox1.DataSource = items;
comboBox1.DisplayMember = "Text";
comboBox1.ValueMember = "Value";

Related

Filter ObservableCollection and bind the new results

So I have this ObservableCollection:
ObservableCollection<ClipboardItem> Clipboards
This ObservableCollection binding into my ListView:
<ListView ItemsSource="{Binding Clipboards}"/>
And in my application I have this TextBox to allow the user to insert string to search for specific string inside my ListView and I want to filter all the result:
public void Execute(object? parameter)
{
TextBox textBox = parameter as TextBox;
if (textBox != null)
{
var text = textBox.Text;
var filteredItems = ViewModel.Clipboards.Where(x => x.Text.Contains(text)).ToList();
}
}
So this filteredItems is the new collection I want to bind into my ListView.
What is the best approach to do that ?
I was thinking to move the original ObservableCollection collection into new collection, Bind the filtered list and in case the user clear the TextBox bind again the original ObservableCollection collection.
You could store the original unfiltered collection in a variable and then either add and remove items from the filtered source collection property or replace it alltogether.
Something like this:
private readonly ClipboardItem[] allClipboards;
...
public ObservableCollection<ClipboardItem> Clipboards { get; }
= new ObservableCollection<ClipboardItem>
public void Execute(object? parameter)
{
TextBox textBox = parameter as TextBox;
if (textBox != null)
{
var text = textBox.Text;
var filteredItems = new HashSet<ClipboardItem>(allClipboards.Where(x => x.Text.Contains(text)));
foreach (var item in Clipboards)
if (!filteredItems.Contains(item))
Clipboards.Remove(item);
foreach (var item in filteredItems)
if (!Clipboards.Contains(item))
Clipboards.Add(item);
}
}

WPF Datagrid DataGridComboBoxColumn cells displayed value set during the AutoGeneratingColumn event

I have a DataGrid ComboBox Column that is bound to an Observable Collection of ComboBoxOption objects, these objects just contain 2 values, one that is the value displayed to the user and the other the required value. I am setting the bindings etc. during the AutoGenerating Columns event and this is all working fine. The problem I am having is trying to make the combo box display the required value only.
This cannot be done in the XAML (Unless I can set up a column template in the XAML and use it in the code behind) because the table columns etc are not defined in the XAML as the datagrid works with collections of different objects and the columns etc are configured during the Auto Generating Columns events. I have tried to set this up using the column cell style but I cant get it working. can anybody point me in the right direction
stripped down code:
private void dataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
DataGridComboBoxColumn comboBoxColumn = new DataGridComboBoxColumn();
comboBoxColumn.ItemsSource = (DataContext as vm_DataTable).DataGridComboBoxColumnOptions; //// The item source is in the View Model (An Observable Collection of ComboBoxOptions)
Binding comboBoxSelectionBinding = new Binding("ComboBoxSelectedItem"); //// Bind the selected item to the ComboBoxOption "ComboBoxSelectedItem" Getter/Setter in the Model
comboBoxSelectionBinding.Mode = BindingMode.TwoWay;
comboBoxSelectionBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
comboBoxColumn.SelectedValueBinding = comboBoxSelectionBinding;
e.Column = comboBoxColumn;
Style comboBoxCellStyle = new Style { TargetType = typeof(DataGridCell) };
comboBoxCellStyle.Setters.Add(new Setter(ComboBox.DisplayMemberPathProperty, "DisplayedValue"));
e.Column.CellStyle = comboBoxCellStyle;
}
Solution: (I was pointed in the right direction by a post by #heliar)
ComboBoxOption Class:
class ComboBoxOption : INotifyPropertyChanged
{
private string requiredValue = "";
private string displayedValue = "";
public string RequiredValue
{
get { return this.requiredValue; }
set
{
this.requiredValue = value;
OnPropertyChanged("requiredValue");
}
}
public string DisplayedValue
{
get { return this.displayedValue; }
set
{
this.displayedValue = value;
OnPropertyChanged("displayedValue");
}
}
public ComboBoxOption(string RequiredValue, string DisplayedValue)
{
this.requiredValue = RequiredValue;
this.displayedValue = DisplayedValue;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
In the code behind for the view:
private void dataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
DataGridComboBoxColumn comboBoxColumn = new DataGridComboBoxColumn();
comboBoxColumn.ItemsSource = (DataContext as vm_DataTable).DataGridComboBoxColumnOptions; //// The item source is in the View Model/Data Context (An Observable Collection of ComboBoxOptions)
Binding comboBoxSelectionBinding = new Binding("ComboBoxSelectedItem"); //// Bind the selected item to the ComboBoxOption "ComboBoxSelectedItem" Getter/Setter in the Model
comboBoxSelectionBinding.Mode = BindingMode.TwoWay;
comboBoxSelectionBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
comboBoxColumn.SelectedValueBinding = comboBoxSelectionBinding;
comboBoxColumn.SelectedValuePath = "RequiredValue"; // RequiredValue is the ComboBoxOption Getter/Setter for this field, (this is the value that the record contains for this column)
comboBoxColumn.DisplayMemberPath = "DisplayedValue"; // DisplayedValue is the ComboBoxOption Getter/Setter for this field
e.Column = comboBoxColumn;
}
not sure the selected value binding would be required in most cases, but in my case I sometimes need access to both the displayed and required values :)

Datagrid cell focus xaml

How to disable next cell focusing after adding new item in datagrid? Please note that I am doing my application in MVVM pattern.
You can’t disable the selection of the next item. It is the intended behavior of the datagrid.
I hope you want a behavior like when user clicks on some row it should be selected and when a new value is added you don’t want the selection to change automatically.
For that the best thing to do is to bind the SelectedInex of the datagrid to some property and then set it manually when the selection changes when a data is updated.
You could also set the required behavior in the setter of SelectedIndex property too.
Actually there are a few way to select items in the DataGrid. It just depends which one works best for the situation
First and most basic is SelectedIndex this will just select the Row at that index in the DataGrid
<DataGrid SelectedIndex="{Binding SelectedIndex}" />
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set { _selectedIndex = value; NotifyPropertyChanged("SelectedIndex"); }
}
SelectedIndex = 2;
SelectedItem will select the row that matches the row you set
<DataGrid SelectedItem="{Binding SelectedRow}" />
private DataRow _selectedRow;
public DataRow SelectedRow
{
get { return _selectedRow; }
set { _selectedRow = value; NotifyPropertyChanged("SelectedRow");}
}
SelectedRow = items.First(x => x.whatever == something);

Focus on DataGridCell for SelectedItem when DataGrid Receives Keyboard Focus

I have a DataGrid where the SelectedItem is bound to a VM Selected property. I have a search control that will do a find and the SelectedItem of the DataGrid changes (and scrolls into view). WPF 4.0 and DataGrid SelectionUnit="FullRow".
My problem is with the focus. The DataGrid receives focus (via attached property / binding) but you can't use the Up, Down, Page Up, Page Down keys to change rows (SelectedItem). If I tab again, the first cell of the first row displayed is selected which changes the SelectedItem.
Bottom line, how can I give keyboard focus to the DataGridCell for the SelectedItem when the DataGrid receives focus?
There are so many DataGrid / Focus questions and tried a few things already. Thanks for your help.
You need to give the newly selected row logical focus. After selecting the new item try replacing your SetFocus call with this:
var selectedRow = (DataGridRow)dataGrid1.ItemContainerGenerator.ContainerFromIndex(dataGrid1.SelectedIndex);
FocusManager.SetIsFocusScope(selectedRow, true);
FocusManager.SetFocusedElement(selectedRow, selectedRow);
The FocusManager solution didn't work for me for some reason. Also I required a more general apporach. So here is, what I came up with:
using System.Windows.Controls;
public static void RestoreFocus(this DataGrid dataGrid,
int column = 0, bool scrollIntoView = false)
{
if (dataGrid.IsKeyboardFocusWithin && dataGrid.SelectedItem != null)
{
// make sure everything is up to date
dataGrid.UpdateLayout();
if (scrollIntoView)
{
dataGrid.ScrollIntoView(dataGrid.SelectedItem);
}
var cellcontent = dataGrid.Columns[column].GetCellContent(dataGrid.SelectedItem);
var cell = cellcontent?.Parent as DataGridCell;
if (cell != null)
{
cell.Focus();
}
}
}
And call it like this:
MyDataGrid.IsKeyboardFocusWithinChanged += (sender, e) =>
{
if ((bool)e.NewValue == true)
{
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Loaded, new Action(() =>
{
MyDataGrid.RestoreFocus(scrollIntoView: true);
}));
}
};
This PowerShell snippet worked for me:
$dataGrid = ...
$dataGrid.add_GotKeyboardFocus({
param($Sender,$EventArgs)
if ($EventArgs.OldFocus -isnot [System.Windows.Controls.DataGridCell) {
$row = $dataGrid.ItemContainerGenerator.ContainerFromIndex($dataGrid.SelectedIndex)
$row.MoveFocus((New-Object System.Windows.Input.TraversalRequest("Next")))
}
})

Silverlight DataGrid: Can't seem to get the RowDetailsVisibilityChanged to fire on load of the grid?

I have a standard Silverlight DataGrid with the RowDetailsVisibilityMode = DataGridRowDetailsVisibilityMode.VisibleWhenSelected
I tried setting this property both in the XAML as well as in the parent control's Loaded event.
In the parent's Loaded event I'm setting the itemsource of the grid, then manually setting the selected index to 0.
This does NOT fire the RowDetailsVisibilityChanged event.
However, once I change the selection by clicking a new row, it will fire.
I need to access that first selection's row's "DetailElement" to populate a control within it with data. However the only way I know how to get that DetailElement is in the RowDetailsVisibilityChanged event.
Here's my code:
void ViewAssociationUserControl_Loaded(object sender, RoutedEventArgs e)
{
viewAssociationsDataGrid.RowDetailsVisibilityMode = DataGridRowDetailsVisibilityMode.VisibleWhenSelected;
viewAssociationsDataGrid.ItemSource = myData;
viewAssociationsDataGrid.SelectedIndex = 0;
}
private void viewAssociationsDataGrid_RowDetailsVisibilityChanged(object sender, System.Windows.Controls.DataGridRowDetailsEventArgs e)
{
if (viewAssociationsDataGrid.RowDetailsVisibilityMode == DataGridRowDetailsVisibilityMode.VisibleWhenSelected
&& e.Row.DetailsVisibility == System.Windows.Visibility.Visible)
{
Grid detailElement = e.DetailsElement as Grid;
if (detailElement != null)
{
ListBox assocControl = detailElement.FindName("oneToManyGridPanel") as ListBox;
UpdateOneToManyPanel(assocControl);
}
}
}
I couldn't figure out a way to cleanly handle this scenario so I ended up hi-jacking the initial "selection changed" occurrence and that first time, manually fire the visibility changed event myself:
private void viewAssociationsDataGrid_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (!initialTriggerFired) //manually fire the visibility the first time
{
initialTriggerFired = true;
DataGrid dataGrid = sender as DataGrid;
int selectedIndex = dataGrid.SelectedIndex;
if (selectedIndex > -1)
{
DataGridColumn column = dataGrid.Columns[0];
FrameworkElement fe = column.GetCellContent(dataGrid.SelectedItem);
DataGridRow row = fe.GetAncestorOfType<DataGridRow>();
if (row != null)
{
row.DetailsVisibility = System.Windows.Visibility.Collapsed;
row.DetailsVisibility = System.Windows.Visibility.Visible;
}
}
}
}
The problem is that your datagrid defaults the selected index to 0. Simply setting viewAssociationsDataGrid.SelectedIndex = -1 in your ViewAssociationUserControl_Loaded event handler will make it so that the selection change will register when the first row is initially selected.

Resources