WPF DataGridComboBoxColumn Edit on Key press - wpf

I have a datagrid with a DataGridComboBoxColumn in it.
I want my users to be able to enter edit mode by just typing.
This is the default behavior for DataGridTextColumn and I don't like that they have to press F2 in order to enable editing for just this column type.
How can I make the DataGridComboBoxColumn enter edit mode without them needing to press F2? Ideally on Key Press, but I would be fine if it entered edit mode on focus as well.
Solution
Modifications to the accepted answer that bring back the basic functionality of the datagrid:
void Cell_PreviewKeyDown(object sender, KeyEventArgs e)
{
if(e.Key == Key.Enter || e.Key == Key.Tab)
{
dgBins.CommitEdit();
dgBins.SelectedIndex += 1;
}else if(e.Key.ToString().Length == 1
|| (e.Key.ToString().StartsWith("D") && e.Key.ToString().Length == 2)
|| e.Key.ToString().StartsWith("NumPad")
|| e.Key == Key.Delete
|| e.Key == Key.Back )
{
if (e.OriginalSource is DataGridCell)
{
DataGridCell cell = (sender as DataGridCell);
Control elem = FindChild<Control>(cell, null);
elem.Focus();
}
}
}

<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<EventSetter Event="PreviewKeyDown" Handler="Cell_PreviewKeyDown"/>
<EventSetter Event="GotFocus" Handler="Cell_GotFocus"/>
</Style>
</DataGrid.CellStyle>
Handlers :
void Cell_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.OriginalSource is DataGridCell)
{
DataGridCell cell = (sender as DataGridCell);
Control elem = FindChild<Control>(cell, null);
elem.Focus();
}
}
void Cell_GotFocus(object sender, RoutedEventArgs e)
{
DataGridCell cell = (sender as DataGridCell);
cell.IsEditing = true;
}
Helper :
public static T FindChild<T>(DependencyObject parent, string childName)
where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null) return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
T childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
If this solves your problem.

You can try to use SelectedCellsChanged event. It works on focus changing.
If you don't want that behaviour on other columns you can check e.AddedCells[0].Column property (if your SelectionUnit="Cell" of DataGrid).
private void dgTest_SelectedCellsChanged( object sender, SelectedCellsChangedEventArgs e )
{
( sender as DataGrid ).BeginEdit();
}

My answer might be a little late but I want to add this, because no given answer was solving the problem for me without breaking normal DataGrid keyboard handling.
The question is simple:
I want my users to be able to enter edit mode by just typing.
So is the solution:
<DataGrid KeyDown="DataGrid_KeyDown">
...
</DataGrid>
With code in code behind xaml.cs:
private void DataGrid_KeyDown(object sender, KeyEventArgs e)
{
DataGrid dg = (sender as DataGrid);
if (dg.CurrentColumn is DataGridComboBoxColumn)
{
dg.BeginEdit();
}
}
Because this already is the normal behavior of TextBoxes I only wanted to apply this to DataGridComboBoxColumn. Of course this will be called on every key press, but a call to 'BeginEdit' does not seem to hurt when the DataGrid already is in edit mode.

Related

How to bring into view all TreeViewItems of expanded TreeViewItem

On my Window I have TreeView. TreeView.ItemsSource is binded to my data (hierarchical data structure). When I expand some TreeViewItem that is on the bottom of the current scrollbar position, my subitems are hidden and I have to scroll to see them. Is there any way to bring all subitems of expanded item into view?
I have tried this, but didn't work:
public void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
TreeViewItem tvi = e.OriginalSource as TreeViewItem;
if (tvi != null)
{
Debug.WriteLine("TreeNode '{0}' was expanded", tvi.Header);
tvi.BringIntoView();
}
}
Basically, select the last sub-item and bring it into view when an item is expanded. If it has no subitems, bring the item itself into view.
The tricky part is, subitems are not necessarily available when the event is executed, so I propose you check the ItemContainerGenerator.Status and if it's not yet done, subscribe to the ItemContainerGenerator.StatusChanged event.
The following code is only tested with static items, if it's making problem for dynamic data sources, I may have to re-check a few details.
void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
TreeViewItem tvi = e.OriginalSource as TreeViewItem;
if (tvi != null)
{
if (tvi.HasItems)
{
if (tvi.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
BringLastItemIntoView(tvi.ItemContainerGenerator);
}
else
{
tvi.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
}
}
else
{
tvi.BringIntoView();
}
}
}
void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
var s = sender as ItemContainerGenerator;
if (s.Status == GeneratorStatus.ContainersGenerated)
{
BringLastItemIntoView(s);
s.StatusChanged -= ItemContainerGenerator_StatusChanged;
}
}
void BringLastItemIntoView(ItemContainerGenerator generator)
{
var tvi = generator.ContainerFromItem(generator.Items.LastOrDefault()) as TreeViewItem;
if (tvi != null)
{
tvi.BringIntoView();
}
}

Is there simple way to make the whole row the only focusable unit of DataGrid?

I don't need focus navigation between cells.
I tried to set Focusable="False" in cell style and adjust focusvisualstyle for the row, but selection fails in that case.
Yeah you need to set the Selection Unit for DataGrid to FullRow and set borderThickness to 0 with FocusVisualStyle to null.
<DataGrid SelectionUnit="FullRow">
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
</Style>
</DataGrid.CellStyle>
<!-- ... -->
</DataGrid>
UPDATE
Above stated xaml is best you can do with xaml only approach but in case you want to handle the tabulation too, then you have to go to code behind. This is how i achieved it -
<DataGrid x:Name="dg" ItemsSource="{Binding Objects}" SelectionUnit="FullRow">
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<EventSetter Event="PreviewKeyDown" Handler="dg_PreviewKeyDown"/>
</Style>
</DataGrid.CellStyle>
</DataGrid>
Code behind (What i am doing here is if user pressed key right or left simply handle them
so as to stop the navigation from one cell to other and in case user press Tab key, focus
should go to the next row if available instead of moving to next cell) -
private void dg_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Left || e.Key == Key.Right)
e.Handled = true;
else if (e.Key == Key.Tab)
{
DataGridRow a = UtilityFunctions.FindParent<DataGridRow>(sender as DependencyObject);
DataGridRow nextDataGridRow =(DataGridRow)dg.ItemContainerGenerator
.ContainerFromIndex(a.GetIndex() + 1);
if (nextDataGridRow != null)
{
dg.SelectedIndex = a.GetIndex() + 1;
DataGridCell cell = UtilityFunctions.FindChild<DataGridCell>
(nextDataGridRow as DependencyObject, "");
cell.Focus();
}
e.Handled = true;
}
}
In the above code i have used some utility functions required to travel the Visual tree to find necessary parent or child in the Visual tree. For your reference the code for it as follows -
public class UtilityFunctions
{
public static Parent FindParent<Parent>(DependencyObject child)
where Parent : DependencyObject
{
DependencyObject parentObject = child;
//We are not dealing with Visual, so either we need to fnd parent or
//get Visual to get parent from Parent Heirarchy.
while (!((parentObject is System.Windows.Media.Visual) || (parentObject is System.Windows.Media.Media3D.Visual3D)))
{
if (parentObject is Parent || parentObject == null)
{
return parentObject as Parent;
}
else
{
parentObject = (parentObject as FrameworkContentElement).Parent;
}
}
//We have not found parent yet , and we have now visual to work with.
parentObject = VisualTreeHelper.GetParent(parentObject);
//check if the parent matches the type we're looking for
if (parentObject is Parent || parentObject == null)
{
return parentObject as Parent;
}
else
{
//use recursion to proceed with next level
return FindParent<Parent>(parentObject);
}
}
public static T FindChild<T>(DependencyObject parent, string childName)
where T : DependencyObject
{
// Confirm parent is valid.
if (parent == null) return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
T childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
}
To remove the focus navigation between cells: Use below code

How do I change when a Silverlight DataGrid updates bound data?

Currently my DataGrid(Silverlight 4) is updating when I step off of a cell. I need it to update whenever the value of a cell is changed.
I came to my own answer and its similar to the behavior injection used to instantly change a TextBox's binding source (see here). I subclassed DataGrid and added the following code:
protected override void OnPreparingCellForEdit(DataGridPreparingCellForEditEventArgs e)
{
base.OnPreparingCellForEdit(e);
TextBox textBox = e.EditingElement as TextBox;
if (textBox != null)
{
textBox.TextChanged -= OnTextChanged;
textBox.TextChanged += OnTextChanged;
}
ComboBox comboBox = e.EditingElement as ComboBox;
if (comboBox != null)
{
comboBox.SelectionChanged -= OnSelectionChanged;
comboBox.SelectionChanged += OnSelectionChanged;
}
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox comboBox = sender as ComboBox;
if (comboBox == null)
return;
BindingExpression expression = comboBox.GetBindingExpression(ComboBox.SelectedValueProperty);
if (expression != null)
expression.UpdateSource();
expression = comboBox.GetBindingExpression(ComboBox.SelectedItemProperty);
if (expression != null)
expression.UpdateSource();
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
TextBox textBox = sender as TextBox;
if (textBox == null)
return;
BindingExpression expression = textBox.GetBindingExpression(TextBox.TextProperty);
if (expression == null)
return;
expression.UpdateSource();
}
just implement INotifyPropertyChanged on class which you set itemssource of datagrid.
for example
public class CustomType:INotifyPropertyChanged
{
}
List<CustomType> list=new List<CustomType>();
add items
datagrid.ItemsSource=list;
Binding Mode=TwoWay

WPF MVVM design question

In my View I have a TreeView with a event "TreeView_MouseLeftButtonDown". If it fires it proofs if the mouse clicked on a TreeViewItem. If not it deselects the last TreeViewItem.
My question is, should i do this in the code-behind or call a static methode in the viewmodel-class? How would you solve this?
The Methode:
private void treeView_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender != null)
{
var treeView = sender as TreeView;
if (treeView != null && treeView.SelectedItem != null)
TreeViewHelper.ReturnTreeViewItem(ref treeView, (XmlNode)treeView.SelectedItem).IsSelected = false;
}
}
XAML:
<TreeView ... KeyDown="TreeView_KeyDown"
MouseLeftButtonDown="TreeView_MouseLeftButtonDown"
SelectedItemChanged="TreeView_SelectedItemChanged" />
You are trying to add a behaviour to the TreeView.
The way I would implement this would be using Attached Properties. I would create an attached property called VerifiesLeftClick or similar and implement the logic in there. This way you do not need an event in the code behind.
See here for samples.
I made for you solution using attached behaviors which were pretty well described here Introduction to Attached Behaviors in WPF by Josh Smith
My solution:
public static class TreeViewBehavior
{
public static bool GetIsResetMouseLeftButtonDown(TreeView treeView)
{
return (bool)treeView.GetValue(IsResetMouseLeftButtonDownProperty);
}
public static void SetIsResetMouseLeftButtonDown(TreeView treeViewItem, bool value)
{
treeViewItem.SetValue(IsResetMouseLeftButtonDownProperty, value);
}
public static readonly DependencyProperty IsResetMouseLeftButtonDownProperty =
DependencyProperty.RegisterAttached("PreviewMouseLeftButtonDown", typeof(bool), typeof(TreeViewBehavior),
new UIPropertyMetadata(false, OnIsMouseLeftButtonDownChanged));
static void OnIsMouseLeftButtonDownChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
TreeView item = depObj as TreeView;
if (item == null)
return;
if (e.NewValue is bool == false)
return;
if ((bool)e.NewValue)
{
item.PreviewMouseLeftButtonDown += OnMouseLeftButtonDown;
}
else
{
item.MouseLeftButtonDown -= OnMouseLeftButtonDown;
}
}
static void OnMouseLeftButtonDown(object sender, RoutedEventArgs e)
{
var tempItem = e.Source as TreeViewItem;
if (tempItem != null && tempItem.IsSelected == false)
{
tempItem.IsSelected = true;
}
else
{
var tree = e.Source as TreeView;
if (tree != null && tree.SelectedItem != null)
{
var selItem = (tree.SelectedItem as TreeViewItem);
if (selItem != null)
{
selItem.IsSelected = false;
}
}
}
}
}
and then in View you should add this:
<TreeView local:TreeViewBehavior.IsResetMouseLeftButtonDown="True">
I hope my solution do what you are trying to achieve.

How to deselect all selected items in a WPF treeview when clicking on some empty area?

I've got a rather funny problem with WPF. I have a tree-view, and selecting items works fine so far. The problem is, I want to unselect the currently selected item when the user clicks inside the blank area of the treeview. By default, the treeview keeps the current item selected, and I have added a context-menu option to deselect it, which is rather hardcore:
// Note: This is done recursivly from the start, so it
// works for child items as well
treeView.ItemContainerGenerator.ContainerFromItem(treeView.SelectedItem) as TreeViewItem).IsSelected = false;
Moreover, this is counter-intuitive, as it requires the user to right-click first, and second, after deselecting it with this way, the user cannot select it any more by clicking on the item. How is this supposed to work?
Edit: Some more information: I've added a handler to the TreeView to handle mouse click events, but the sender is always a TreeView instance, even if I click directly on a TreeViewItem. If I add a handler to my TreeView.ItemTemplate instead (i.e. the first child in the template), I never get events when I click on the empty area (which is rather logical). The code looks like this:
private void MyTreeView_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if ((sender as TreeViewItem) == null)
{
// Always triggered
System.Diagnostics.Trace.Write("Empty area clicked");
}
}
And the XAML for this is:
<TreeView x:Name="MyTreeView" Margin="3" MouseUp="MyTreeView_MouseUp">
I found this to work much better for me. I check the originalsource which for me if it comes form a treeviewitem will be an image or a textblock. I also use a view object with a HierarchicalDataTemplate and the BasicTreeViewBase is the base class for all of my different objects. Here is the code.
private void TemplateTreeView_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Right && !(e.OriginalSource is Image) && !(e.OriginalSource is TextBlock))
{
BasicTreeViewBase item = TemplateTreeView.SelectedItem as BasicTreeViewBase;
if (item != null)
{
TemplateTreeView.Focus();
item.IsSelected = false;
}
}
}
The un-selectable problem can be solved with a call to Focus on the TreeView after setting TreeViewItem.IsSelected.
There can be two more problem :
The treeview is binded so the SelectedItem is an item of the binded collection.
There is many levels so ItemContainerGenerator do not contain deepest level objects
for all this reason i use this function, but the selection must not fire any events.
private void UnselectTreeViewItem(TreeView pTreeView)
{
if(pTreeView.SelectedItem == null)
return;
if(pTreeView.SelectedItem is TreeViewItem)
{
(pTreeView.SelectedItem as TreeViewItem).IsSelected = false;
}
else
{
TreeViewItem item = pTreeView.ItemContainerGenerator.ContainerFromIndex(0) as TreeViewItem;
if (item != null)
{
item.IsSelected = true;
item.IsSelected = false;
}
}
}
I implemented a general selection control once, and required this behaviour.
This is how my method looked (adapted for treeview):
protected override void OnMouseUp(MouseButtonEventArgs e)
{
base.OnMouseUp(e);
DependencyObject dpSource = e.OriginalSource as DependencyObject;
if (dpSource.FindVisualAncestor(o => typeof(TreeViewItem).IsAssignableFrom(o.GetType())) == null)
UnselectAll();
}
Basically, walk up the tree from the source. If a TreeViewItem was not found, than the user clicked empty space.
Use the extension class below
public static class TreeViewExtensions
{
public static TreeViewItem ContainerFromItem(this TreeView treeView, object item)
{
TreeViewItem containerThatMightContainItem = (TreeViewItem)treeView.ItemContainerGenerator.ContainerFromItem(item);
if (containerThatMightContainItem != null)
return containerThatMightContainItem;
else
return ContainerFromItem(treeView.ItemContainerGenerator, treeView.Items, item);
}
private static TreeViewItem ContainerFromItem(ItemContainerGenerator parentItemContainerGenerator, ItemCollection itemCollection, object item)
{
foreach (object curChildItem in itemCollection)
{
TreeViewItem parentContainer = (TreeViewItem)parentItemContainerGenerator.ContainerFromItem(curChildItem);
TreeViewItem containerThatMightContainItem = (TreeViewItem)parentContainer.ItemContainerGenerator.ContainerFromItem(item);
if (containerThatMightContainItem != null)
return containerThatMightContainItem;
TreeViewItem recursionResult = ContainerFromItem(parentContainer.ItemContainerGenerator, parentContainer.Items, item);
if (recursionResult != null)
return recursionResult;
}
return null;
}
}
Then in MouseDown event of treeview use the extension method as below:
private void trview_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if ((sender as TreeViewItem) == null)
{
if (this.trview.ContainerFromItem(trview.SelectedItem) != null)
{
this.trview.ContainerFromItem(trview.SelectedItem).IsSelected = false;
}
}
this.trview.Focus();
}
Hope it works for you. I have it working in this way...
I was running into this situation myself with a custom Tree List View implementation after looking for a long time I finally found a solution that worked for me.
The full explanation can be found at http://social.msdn.microsoft.com/Forums/vstudio/en-US/36aca7f7-0b47-488b-8e16-840b86addfa3/getting-treeviewitem-for-the-selected-item-in-a-treeview
The basic idea is you capture the TreeViewItem.Selected event and save the source of the event into the Tag attribute on your TreeView. Then when you need to clear it, you can access the Tag attribute on your control and set the IsSelected value to False. This works for me with 2 levels of nested children. Hopefully it will work for you.
For persistence sake:
TreeView declaration
<TreeView Name="myTreeView" TreeViewItem.Selected="OnItemSelected"
ItemsSource="{Binding Source={StaticResource myHierarchicalData}}"/>
Event Handler
private void OnItemSelected(object sender, RoutedEventArgs e)
{
myTreeView.Tag = e.OriginalSource;
}
Clear selection logic
if (myTreeView.SelectedItem != null)
{
TreeViewItem selectedTVI = myTreeView.Tag as TreeViewItem;
// add your code here mine was selectedTVI.IsSelected = false;
}
This will deselect the currently selected TreeViewItem if none were clicked:
private void MyTreeView_PreviewMouseDown(object sender, MouseButtonEventArgs e) {
if ((sender as TreeViewItem) == null) {
TreeViewItem item = MyTreeView.SelectedItem as TreeViewItem;
if(item != null){
item.IsSelected = false;
}
}
}
Hope this is what you were looking for!
MVVM: Call this method in the eventhandler righ/left mouse click:
private void SetTreeViewSelection(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
var treeView = (TreeView)sender;
if (treeView.SelectedItem == null)
{
return;
}
IInputElement dropNode = treeView.InputHitTest(mouseButtonEventArgs.GetPosition(treeView));
if (dropNode is ScrollViewer)
{
var myBindableObject = (MyBindableObject)treeView.SelectedItem;
myBindableObject.IsSelected = false;
}
}
For a C# treeview you use treeview.SelectedNode = null; I'm not sure if this works for WPF.

Resources