Iterate over VisualTreeHelper.GetChild() and call UpdateSource on Controls that contain databinding - wpf

I have a ContentControl in WPF which contains some input Controls, like TextBoxes and ComboBoxes.
Each of these Controls is databound to a given property in the ViewModel, with UpdateSourceTrigger=Explicit.
When I click some "Submit" button, I want to traverse every Child of FormularioPaciente that has bindings, and call UpdateSource:
private void btnSalvarEditarPaciente_Click(object sender, System.Windows.RoutedEventArgs e) {
foreach (var childControl in LogicalTreeHelper.GetChildren(FormularioPaciente)) {
// what should I do now?
// I would really like to "auto-find" everything that should be updated...
}
}

I think you can update the solution a bit
void GetBindingsRecursive(DependencyObject dObj, List<BindingExpressions> bindingList)
{
bindingList.AddRange(DependencyObjectHelper.GetBindingObjects(dObj));
int childrenCount = VisualTreeHelper.GetChildrenCount(dObj);
if (childrenCount > 0)
{
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(dObj, i);
GetBindingsRecursive(child, bindingList);
}
}
}
public static class DependencyObjectHelper
{
public static List<BindingExpression> GetBindingObjects(Object element)
{
List<BindingExpression> bindings = new List<BindingBase>();
List<DependencyProperty> dpList = new List<DependencyProperty>();
dpList.AddRange(GetDependencyProperties(element));
dpList.AddRange(GetAttachedProperties(element));
foreach (DependencyProperty dp in dpList)
{
BindingExpression b = BindingOperations.GetBindingExpression(element as DependencyObject, dp);
if (b != null)
{
bindings.Add(b);
}
}
return bindings;
}
public static List<DependencyProperty> GetDependencyProperties(Object element)
{
List<DependencyProperty> properties = new List<DependencyProperty>();
MarkupObject markupObject = MarkupWriter.GetMarkupObjectFor(element);
if (markupObject != null)
{
foreach (MarkupProperty mp in markupObject.Properties)
{
if (mp.DependencyProperty != null)
{
properties.Add(mp.DependencyProperty);
}
}
}
return properties;
}
public static List<DependencyProperty> GetAttachedProperties(Object element)
{
List<DependencyProperty> attachedProperties = new List<DependencyProperty>();
MarkupObject markupObject = MarkupWriter.GetMarkupObjectFor(element);
if (markupObject != null)
{
foreach (MarkupProperty mp in markupObject.Properties)
{
if (mp.IsAttached)
{
attachedProperties.Add(mp.DependencyProperty);
}
}
}
return attachedProperties;
}
}
and once you get the BindingExpression list you can call BindingExpression.UpdateSource() on those.

Related

Editable ComboBox behavior on arrow navigation in drop down

The default behavior for non-editable Combobox when you navigate through drop down list with Up and Down keys is, that the current item is highlighted but not selected. Only on Enter Key the Item gets selected.
If you set IsEditable="True" then the behavior is different. Currently selected item (and or Text input) changes by keyboard navigation in the drop down.
My problem with this is, that I'm filtering the items depending on text input. And when you select, you have one exact match and items count goes to one.
So it's not possible to select a correct item with a keyboard.
Inspired by blog post below (Thank you Diederik Krols) I'm finaly found a solution for my problem.
http://dotbay.blogspot.de/2009/04/building-filtered-combobox-for-wpf.html
It needed some extra work, but with a little bit Reflection and Binding suspendig, I have now combobox behavior like expected.
Here is my code
public enum FilterMode
{
Contains,
StartsWith
}
public class FilteredComboBoxBehavior : ManagedBehaviorBase<ComboBox>
{
private ICollectionView currentView;
private string currentFilter;
private Binding textBinding;
private TextBox textBox;
private PropertyInfo HighlightedInfoPropetyInfo { get; set; }
public static readonly DependencyProperty FilterModeProperty = DependencyProperty.Register("FilterMode", typeof(FilterMode), typeof(FilteredComboBoxBehavior), new PropertyMetadata(default(FilterMode)));
public FilterMode FilterMode
{
get
{
return (FilterMode)this.GetValue(FilterModeProperty);
}
set
{
this.SetValue(FilterModeProperty, value);
}
}
public static readonly DependencyProperty OpenDropDownOnFocusProperty = DependencyProperty.Register("OpenDropDownOnFocus", typeof(bool), typeof(FilteredComboBoxBehavior), new PropertyMetadata(true));
public bool OpenDropDownOnFocus
{
get
{
return (bool)this.GetValue(OpenDropDownOnFocusProperty);
}
set
{
this.SetValue(OpenDropDownOnFocusProperty, value);
}
}
protected override void OnSetup()
{
base.OnSetup();
this.AssociatedObject.KeyUp += this.AssociatedObjectOnKeyUp;
this.AssociatedObject.IsKeyboardFocusWithinChanged += this.OnIsKeyboardFocusWithinChanged;
this.textBox = this.AssociatedObject.FindChild<TextBox>();
this.textBinding = BindingOperations.GetBinding(this.AssociatedObject, ComboBox.TextProperty);
this.HighlightedInfoPropetyInfo = typeof(ComboBox).GetProperty(
"HighlightedInfo",
BindingFlags.Instance | BindingFlags.NonPublic);
var pd = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(ComboBox));
pd.AddValueChanged(this.AssociatedObject, this.OnItemsSourceChanged);
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.KeyUp -= this.AssociatedObjectOnKeyUp;
if (this.currentView != null)
{
// ReSharper disable once DelegateSubtraction
this.currentView.Filter -= this.TextInputFilter;
}
BindingOperations.ClearAllBindings(this);
}
private void OnItemsSourceChanged(object sender, EventArgs eventArgs)
{
this.currentFilter = this.AssociatedObject.Text;
if (this.currentView != null)
{
// ReSharper disable once DelegateSubtraction
this.currentView.Filter -= this.TextInputFilter;
}
if (this.AssociatedObject.ItemsSource != null)
{
this.currentView = CollectionViewSource.GetDefaultView(this.AssociatedObject.ItemsSource);
this.currentView.Filter += this.TextInputFilter;
}
this.Refresh();
}
private void OnIsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
if (this.AssociatedObject.IsKeyboardFocusWithin)
{
this.AssociatedObject.IsDropDownOpen = this.AssociatedObject.IsDropDownOpen || this.OpenDropDownOnFocus;
}
else
{
this.AssociatedObject.IsDropDownOpen = false;
this.currentFilter = this.AssociatedObject.Text;
this.Refresh();
}
}
private void AssociatedObjectOnKeyUp(object sender, KeyEventArgs keyEventArgs)
{
if (!this.IsTextManipulationKey(keyEventArgs)
|| (Keyboard.Modifiers.HasAnyFlag() && Keyboard.Modifiers != ModifierKeys.Shift)
)
{
return;
}
if (this.currentFilter != this.AssociatedObject.Text)
{
this.currentFilter = this.AssociatedObject.Text;
this.Refresh();
}
}
private bool TextInputFilter(object obj)
{
var stringValue = obj as string;
if (obj != null && !(obj is string))
{
var path = (string)this.GetValue(TextSearch.TextPathProperty);
if (path != null)
{
stringValue = obj.GetType().GetProperty(path).GetValue(obj) as string;
}
}
if (stringValue == null)
return false;
switch (this.FilterMode)
{
case FilterMode.Contains:
return stringValue.IndexOf(this.currentFilter, StringComparison.OrdinalIgnoreCase) >= 0;
case FilterMode.StartsWith:
return stringValue.StartsWith(this.currentFilter, StringComparison.OrdinalIgnoreCase);
default:
throw new ArgumentOutOfRangeException();
}
}
private bool IsTextManipulationKey(KeyEventArgs keyEventArgs)
{
return keyEventArgs.Key == Key.Back
|| keyEventArgs.Key == Key.Space
|| (keyEventArgs.Key >= Key.D0 && keyEventArgs.Key <= Key.Z)
|| (Keyboard.IsKeyToggled(Key.NumLock) && keyEventArgs.Key >= Key.NumPad0 && keyEventArgs.Key <= Key.NumPad9)
|| (keyEventArgs.Key >= Key.Multiply && keyEventArgs.Key <= Key.Divide)
|| (keyEventArgs.Key >= Key.Oem1 && keyEventArgs.Key <= Key.OemBackslash);
}
private void Refresh()
{
if (this.currentView != null)
{
var tempCurrentFilter = this.AssociatedObject.Text;
using (new SuspendBinding(this.textBinding, this.AssociatedObject, ComboBox.TextProperty))
{
this.currentView.Refresh();
//reset internal highlighted info
this.HighlightedInfoPropetyInfo.SetValue(this.AssociatedObject, null);
this.AssociatedObject.SelectedIndex = -1;
this.AssociatedObject.Text = tempCurrentFilter;
}
if (this.textBox != null && tempCurrentFilter != null)
{
this.textBox.SelectionStart = tempCurrentFilter.Length;
this.textBox.SelectionLength = 0;
}
}
}
}
/// <summary>
/// Temporarely suspend binding on dependency property
/// </summary>
public class SuspendBinding : IDisposable
{
private readonly Binding bindingToSuspend;
private readonly DependencyObject target;
private readonly DependencyProperty property;
public SuspendBinding(Binding bindingToSuspend, DependencyObject target, DependencyProperty property)
{
this.bindingToSuspend = bindingToSuspend;
this.target = target;
this.property = property;
BindingOperations.ClearBinding(target, property);
}
public void Dispose()
{
BindingOperations.SetBinding(this.target, this.property, this.bindingToSuspend);
}
}
public abstract class ManagedBehaviorBase<T> : Behavior<T> where T : FrameworkElement
{
private bool isSetup;
private bool isHookedUp;
private WeakReference weakTarget;
protected virtual void OnSetup() { }
protected virtual void OnCleanup() { }
protected override void OnChanged()
{
var target = this.AssociatedObject;
if (target != null)
{
this.HookupBehavior(target);
}
else
{
this.UnHookupBehavior();
}
}
private void OnTargetLoaded(object sender, RoutedEventArgs e) { this.SetupBehavior(); }
private void OnTargetUnloaded(object sender, RoutedEventArgs e) { this.CleanupBehavior(); }
private void HookupBehavior(T target)
{
if (this.isHookedUp) return;
this.weakTarget = new WeakReference(target);
this.isHookedUp = true;
target.Unloaded += this.OnTargetUnloaded;
target.Loaded += this.OnTargetLoaded;
if (target.IsLoaded)
{
this.SetupBehavior();
}
}
private void UnHookupBehavior()
{
if (!this.isHookedUp) return;
this.isHookedUp = false;
var target = this.AssociatedObject ?? (T)this.weakTarget.Target;
if (target != null)
{
target.Unloaded -= this.OnTargetUnloaded;
target.Loaded -= this.OnTargetLoaded;
}
this.CleanupBehavior();
}
private void SetupBehavior()
{
if (this.isSetup) return;
this.isSetup = true;
this.OnSetup();
}
private void CleanupBehavior()
{
if (!this.isSetup) return;
this.isSetup = false;
this.OnCleanup();
}
}
XAML
<ComboBox IsEditable="True"
Text="{Binding Path=ZipCode, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
ItemsSource="{Binding Path=PostalCodes}"
IsTextSearchEnabled="False"
behaviors:AttachedMaxLength.ChildTextBoxMaxLength="{Binding Path=ZipCodeMaxLength}">
<i:Interaction.Behaviors>
<behaviors:FilteredComboBoxBehavior FilterMode="StartsWith"/>
</i:Interaction.Behaviors>

GridvView.SelectedCells.Add() does not update view

GridViews Selected cells is a IList, so it does not update the view, when i add selections from my ViewModel.
Is there a way to force updating the view for selected Cells. The way i currently uddate views is by having a Attached behavior, which updates the list on ViewModel, but also the GridView, but the GridView does not update its visuals.
here is my attached behavior:
public static List<GridCell> GetSelectedCells(DependencyObject obj)
{
return (List<GridCell>)obj.GetValue(SelectedCellsProperty);
}
public static void SetSelectedCells(DependencyObject obj, List<GridCell> value)
{
obj.SetValue(SelectedCellsProperty, value);
}
public static readonly DependencyProperty SelectedCellsProperty =
DependencyProperty.RegisterAttached("SelectedCells", typeof(List<GridCell>), typeof(DataGridHelper), new UIPropertyMetadata(null, OnSelectedCellsChanged));
static SelectedCellsChangedEventHandler GetSelectionChangedHandler(DependencyObject obj)
{
return (SelectedCellsChangedEventHandler)obj.GetValue(SelectionChangedHandlerProperty);
}
static void SetSelectionChangedHandler(DependencyObject obj, SelectedCellsChangedEventHandler value)
{
obj.SetValue(SelectionChangedHandlerProperty, value);
}
static readonly DependencyProperty SelectionChangedHandlerProperty =
DependencyProperty.RegisterAttached(nameof(SelectedCellsChangedEventHandler), typeof(SelectedCellsChangedEventHandler), typeof(DataGridHelper), new UIPropertyMetadata(null));
private static bool NewResouce = false;
static void OnSelectedCellsChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
if (d is DataGrid)
{
NewResouce = true;
DataGrid datagrid = d as DataGrid;
if (GetSelectionChangedHandler(d) == null)
{
SelectedCellsChangedEventHandler selectionchanged = (sender, e) =>
{
if (!NewResouce)
{
List<GridCell> cells = new List<GridCell>();
foreach (var selectedell in datagrid.SelectedCells)
{
string header = selectedell.Column.Header.ToString();
GridCell cell = new GridCell
{
RowIndex = datagrid.Items.IndexOf(selectedell.Item),
ColumnIndex = selectedell.Column.DisplayIndex,
Parent = selectedell.Item as ExpandoObject,
ColumnHeader = header,
Value = (selectedell.Item as IDictionary<string, object>)[header]
};
cells.Add(cell);
}
SetSelectedCells(d, cells);
}
};
SetSelectionChangedHandler(d, selectionchanged);
datagrid.SelectedCellsChanged += GetSelectionChangedHandler(d);
}
foreach (var selected in GetSelectedCells(d) as List<GridCell>)
{
DataGridCellInfo cell = new DataGridCellInfo(selected.Parent, datagrid.Columns[selected.ColumnIndex]);
if (!datagrid.SelectedCells.Contains(cell))
{
datagrid.SelectedCells.Add(cell);
}
}
NewResouce = false;
}
}
}
The reason why i have the NewResource boolean, is that the event selection changed does actually fire when I add newly selected items. Its just the view that does not update its selections.
The SelectedCells is added after the view is loaded, due to its located inside a tab, and it looks like the data on gridview is empty before view is loaded, so I cannot set selected before the view is loaded.
Answer is simple. Please use an ObservableCollection instead of the list.
The observable collection is a dependency object and it will raise a propertyChanged event to the view to notify it regarding the property change and the view will be updated.
I ended up finden some properties that does implement INotifyPropertyChanged, on DataGridCells heres the solution:
static void OnSelectedCellsChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
if (d is DataGrid)
{
DataGrid datagrid = d as DataGrid;
if (GetSelectionChangedHandler(d) == null)
{
SelectedCellsChangedEventHandler selectionchanged = (sender, e) =>
{
List<GridCell> cells = new List<GridCell>();
foreach (var selectedell in datagrid.SelectedCells)
{
string header = selectedell.Column.Header.ToString();
GridCell cell = new GridCell
{
RowIndex = datagrid.Items.IndexOf(selectedell.Item),
ColumnIndex = selectedell.Column.DisplayIndex,
Parent = selectedell.Item as ExpandoObject,
ColumnHeader = header,
Value = (selectedell.Item as IDictionary<string, object>)[header]
};
cells.Add(cell);
}
SetSelectedCells(d, cells);
};
SetSelectionChangedHandler(d, selectionchanged);
datagrid.SelectedCellsChanged += GetSelectionChangedHandler(d);
}
foreach (var selected in GetSelectedCells(d) as List<GridCell>)
{
DataGridCell actualCell = datagrid.GetCell(selected.RowIndex, selected.ColumnIndex);
actualCell.IsSelected = true;
actualCell.Focus();
}
}
}
for it to work i added some extention method to datagrid, to make it easier to get the Cell here the extention methodes: (stolen from this blog)
public static T GetVisualChild<T>(Visual parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
public static DataGridCell GetCell(this DataGrid grid, int rowIndex, int columnIndex)
{
return GetCell(grid, GetRow(grid, rowIndex), columnIndex);
}
public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int column)
{
if (row != null)
{
DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);
if (presenter == null)
{
grid.ScrollIntoView(row, grid.Columns[column]);
presenter = GetVisualChild<DataGridCellsPresenter>(row);
}
DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
return cell;
}
return null;
}
public static DataGridRow GetRow(this DataGrid grid, int index)
{
DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
if (row == null)
{
// May be virtualized, bring into view and try again.
grid.UpdateLayout();
grid.ScrollIntoView(grid.Items[index]);
row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
}
return row;
}

WPF editable combobox slow typing

I have a WPF combobox like this :
<ComboBox x:Name="CustomerComboBox" IsEditable="True" ItemsSource="{Binding Relations.View}"
DisplayMemberPath="Model.sname" />
If I click in the editable combo while the binding is in place (MVVM) to give it focus, and I then press and hold any key, I assume the combo will be filled with that key rather quickly, but it isn't.
If I remove the displaymemberpath and then do the same, then I have the expected behavior. Of course I really need the binding.
The performance penalty only shows when the combo has a lot of elements mine has 6000.
I cannot understand where this performance penalty is coming from. Is there any way to bypass this problem ?
Below code solves the issues by creating a specialised combobox that basically caches all binding results. I created it by looking at the orginal source code of the combobox and itemscontrol using .NET reflector.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Reflection;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows.Controls.Primitives;
using System.Collections;
namespace ICeTechControlLibrary
{
public class FastEditComboBox : ComboBox
{
//PARTS
private TextBox _TextBoxPart = null;
//DEPENDENCY PROPERTIES
public static readonly DependencyProperty TextProperty
= DependencyProperty.Register("Text", typeof(string), typeof(AutoCompleteTextBox), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.Journal | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(FastEditComboBox.OnTextChanged)));
private List<string> _CompletionStrings = new List<string>();
private int _textBoxSelectionStart;
private bool _updatingText;
private bool _updatingSelectedItem;
private static Dictionary<TextBox, FastEditComboBox> _TextBoxDictionary = new Dictionary<TextBox,FastEditComboBox>();
static FastEditComboBox()
{
EventManager.RegisterClassHandler(typeof(TextBox), TextBox.TextChangedEvent, new TextChangedEventHandler(FastEditComboBox.OnTextChanged));
EventManager.RegisterClassHandler(typeof(TextBox), TextBox.SelectionChangedEvent, new RoutedEventHandler(FastEditComboBox.OnSelectionChanged));
}
public string Text
{
get
{
return (string)base.GetValue(TextProperty);
}
set
{
base.SetValue(TextProperty, value);
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_TextBoxPart = base.GetTemplateChild("PART_EditableTextBox") as TextBox;
if (!_TextBoxDictionary.ContainsKey(_TextBoxPart)) _TextBoxDictionary.Add(_TextBoxPart, this);
}
private void OnTextBoxSelectionChanged(object sender, RoutedEventArgs e)
{
this._textBoxSelectionStart = this._TextBoxPart.SelectionStart;
}
private void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
if (IsEditable)
{
TextUpdated(_TextBoxPart.Text, true);
}
}
private void TextUpdated(string newText, bool textBoxUpdated)
{
if (!_updatingText && !_updatingSelectedItem)
{
try
{
_updatingText = true;
if (base.IsTextSearchEnabled)
{
int num = FindMatchingPrefix(newText);
if (num >= 0)
{
if (textBoxUpdated)
{
int selectionStart = this._TextBoxPart.SelectionStart;
if ((selectionStart == newText.Length) && (selectionStart > this._textBoxSelectionStart))
{
string primaryTextFromItem = _CompletionStrings[num];
this._TextBoxPart.Text = primaryTextFromItem;
this._TextBoxPart.SelectionStart = newText.Length;
this._TextBoxPart.SelectionLength = primaryTextFromItem.Length - newText.Length;
newText = primaryTextFromItem;
}
}
else
{
string b = _CompletionStrings[num];
if (!string.Equals(newText, b, StringComparison.CurrentCulture))
{
num = -1;
}
}
}
if (num != base.SelectedIndex)
{
SelectedIndex = num;
}
}
if (textBoxUpdated)
{
Text = newText;
}
else if (_TextBoxPart != null)
{
_TextBoxPart.Text = newText;
}
}
finally
{
_updatingText = false;
}
}
}
internal void SelectedItemUpdated()
{
try
{
this._updatingSelectedItem = true;
if (!this._updatingText)
{
string primaryTextFromItem = GetPrimaryTextFromItem(SelectedItem);
Text = primaryTextFromItem;
}
this.Update();
}
finally
{
this._updatingSelectedItem = false;
}
}
private void Update()
{
if (this.IsEditable)
{
this.UpdateEditableTextBox();
}
else
{
//this.UpdateSelectionBoxItem();
}
}
private void UpdateEditableTextBox()
{
if (!_updatingText)
{
try
{
this._updatingText = true;
string text = this.Text;
if ((this._TextBoxPart != null) && (this._TextBoxPart.Text != text))
{
this._TextBoxPart.Text = text;
this._TextBoxPart.SelectAll();
}
}
finally
{
this._updatingText = false;
}
}
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.RaiseEvent(e);
this.SelectedItemUpdated();
if (this.IsDropDownOpen)
{
object Item = SelectedItem;
if (Item != null)
{
base.OnSelectionChanged(e);
}
//object internalSelectedItem = base.InternalSelectedItem;
//if (internalSelectedItem != null)
//{
// base.NavigateToItem(internalSelectedItem, ItemsControl.ItemNavigateArgs.Empty);
//}
}
}
int FindMatchingPrefix(string s)
{
int index = _CompletionStrings.BinarySearch(s, StringComparer.OrdinalIgnoreCase);
if (index >= 0) return index;
index = ~index;
string p = _CompletionStrings[index];
if (p.StartsWith(s, StringComparison.CurrentCultureIgnoreCase)) return index;
return -1;
}
protected override void OnDisplayMemberPathChanged(string oldDisplayMemberPath, string newDisplayMemberPath)
{
FillCompletionStrings();
}
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
switch (e.Action)
{
case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
AddCompletionStrings(e.NewItems);
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
RemoveCompletionStrings(e.OldItems);
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
FillCompletionStrings();
break;
}
}
private void FillCompletionStrings()
{
_CompletionStrings.Clear();
AddCompletionStrings(Items);
}
private void RemoveCompletionStrings(IList items)
{
foreach (object o in items)
{
RemoveCompletionStringForItem(o);
}
}
private void AddCompletionStrings(IList items)
{
foreach (object o in items)
{
AddCompletionStringForItem(o);
}
}
private void AddCompletionStringForItem(object item)
{
Binding binding = new Binding(DisplayMemberPath);
TextBlock tb = new TextBlock();
tb.DataContext = item;
tb.SetBinding(TextBlock.TextProperty, binding);
string s = tb.Text;
int index = _CompletionStrings.BinarySearch(s, StringComparer.OrdinalIgnoreCase);
if (index < 0)
{
_CompletionStrings.Insert(~index, s);
}
else
{
_CompletionStrings.Insert(index, s);
}
}
private string GetPrimaryTextFromItem(object item)
{
Binding binding = new Binding(DisplayMemberPath);
TextBlock tb = new TextBlock();
tb.DataContext = item;
tb.SetBinding(TextBlock.TextProperty, binding);
string s = tb.Text;
return s;
}
private void RemoveCompletionStringForItem(object item)
{
Binding binding = new Binding(DisplayMemberPath);
TextBlock tb = new TextBlock();
tb.DataContext = item;
tb.SetBinding(TextBlock.TextProperty, binding);
string s = tb.Text;
int index = _CompletionStrings.BinarySearch(s, StringComparer.OrdinalIgnoreCase);
if (index >= 0) _CompletionStrings.RemoveAt(index);
}
private static void OnTextChanged(object sender, TextChangedEventArgs e)
{
TextBox tb = e.Source as TextBox;
if (tb.Name == "PART_EditableTextBox")
{
if (_TextBoxDictionary.ContainsKey(tb))
{
FastEditComboBox combo = _TextBoxDictionary[tb];
combo.OnTextBoxTextChanged(sender, e);
e.Handled = true;
}
}
}
private static void OnSelectionChanged(object sender, RoutedEventArgs e)
{
TextBox tb = e.Source as TextBox;
if (tb.Name == "PART_EditableTextBox")
{
if (_TextBoxDictionary.ContainsKey(tb))
{
FastEditComboBox combo = _TextBoxDictionary[tb];
combo.OnTextBoxSelectionChanged(sender, e);
e.Handled = true;
}
}
}
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FastEditComboBox actb = (FastEditComboBox)d;
actb.TextUpdated((string)e.NewValue, false);
}
}
}
selecting the very first element clears the selection with this implementation.
so still some bugs here

Selection with mouse in DataGrid is not possible with filtered collection

We have a ComboBox with a DataGrid, based on this article.
Now we wan't to have the possibility to filter the values. So I implemented this one.
Filtering is working fine and it's possible to select suggestions with up and down on the keyboard. But it is not possible to select one with the mouse.
Why is this not possible? How can I fix that?
Here is the code of our combobox:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
namespace CustomControls {
[DefaultProperty("Columns")]
[ContentProperty("Columns")]
[TemplatePart(Name = s_partPopupDataGrid, Type = typeof(DataGrid))]
public class GridCombo : ComboBox {
#region Static
internal static readonly DependencyProperty ReplaceColumnsProperty =
DependencyProperty.Register(
"ReplaceColumns",
typeof(IEnumerable<DataGridBoundColumn>),
typeof(GridCombo),
new FrameworkPropertyMetadata());
public static readonly DependencyProperty CellStyleProperty =
DependencyProperty.Register(
"CellStyle",
typeof(Style),
typeof(GridCombo),
new FrameworkPropertyMetadata());
public static readonly DependencyProperty MinimumSearchLengthProperty =
DependencyProperty.Register(
"MinimumSearchLength",
typeof(int),
typeof(GridCombo),
new UIPropertyMetadata(1));
static GridCombo() {
DefaultStyleKeyProperty.OverrideMetadata(
typeof(GridCombo), new FrameworkPropertyMetadata(typeof(GridCombo)));
}
#endregion
// ======================================================================
#region Fields & Constructors
private const string s_partPopupDataGrid = "PART_PopupDataGrid";
// Columns of DataGrid
private ObservableCollection<DataGridBoundColumn> _columns;
private readonly Dictionary<Type, List<PropertyInfo>> _properties = new Dictionary<Type, List<PropertyInfo>>();
// Attached DataGrid control
private DataGrid _popupDataGrid;
private Popup _popup;
private string _oldFilter = string.Empty;
private string _currentFilter = string.Empty;
#endregion
// ======================================================================
#region Public
public Style CellStyle {
get { return (Style)GetValue(CellStyleProperty); }
set { SetValue(CellStyleProperty, value); }
}
/// <summary>
/// If set, the "Columns" property is ignored. Useful if you need
/// a dependency property.
/// </summary>
internal IEnumerable<DataGridBoundColumn> ReplaceColumns {
get { return (ObservableCollection<DataGridBoundColumn>)GetValue(ReplaceColumnsProperty); }
set { SetValue(ReplaceColumnsProperty, value); }
}
// The property is default and Content property for CustComboBox
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ObservableCollection<DataGridBoundColumn> Columns {
get {
if (_columns == null) {
_columns = new ObservableCollection<DataGridBoundColumn>();
}
return _columns;
}
}
// Apply theme and attach columns to DataGrid popup control
public override void OnApplyTemplate() {
if (_popupDataGrid == null) {
_popupDataGrid = Template.FindName(s_partPopupDataGrid, this) as DataGrid;
if (_popupDataGrid != null && (_columns != null || ReplaceColumns != null)) {
if (ReplaceColumns != null) {
foreach (var column in ReplaceColumns) {
var copy = DataGridFix.CopyDataGridColumn(column);
_popupDataGrid.Columns.Add(copy);
}
} else {
// Add columns to DataGrid columns
for (int i = 0; i < _columns.Count; i++)
_popupDataGrid.Columns.Add(_columns[i]);
}
// Add event handler for DataGrid popup
_popupDataGrid.MouseDown += PopupDataGridMouseDown;
_popupDataGrid.SelectionChanged += PopupDataGridSelectionChanged;
}
}
if (_popup == null) {
_popup = Template.FindName("PART_Popup", this) as Popup;
if (_popup != null && _popupDataGrid != null) {
_popup.Opened += PopupOpened;
_popup.Focusable = true;
}
}
// Call base class method
base.OnApplyTemplate();
}
[Description("Length of the search string that triggers filtering.")]
[Category("Filtered ComboBox")]
[DefaultValue(1)]
public int MinimumSearchLength {
[DebuggerStepThrough]
get { return (int)GetValue(MinimumSearchLengthProperty); }
[DebuggerStepThrough]
set { SetValue(MinimumSearchLengthProperty, value); }
}
#endregion
// ======================================================================
#region Protected
// When selection changed in combobox (pressing arrow key down or up) must be synchronized with opened DataGrid popup
protected override void OnSelectionChanged(SelectionChangedEventArgs e) {
base.OnSelectionChanged(e);
if (_popupDataGrid == null)
return;
if (!DesignerProperties.GetIsInDesignMode(this)) {
if (IsDropDownOpen) {
_popupDataGrid.SelectedItem = SelectedItem;
ScrollIntoView(SelectedItem);
}
}
}
protected override void OnDropDownOpened(EventArgs e) {
if (_popupDataGrid == null)
return;
_popupDataGrid.SelectedItem = SelectedItem;
base.OnDropDownOpened(e);
}
protected TextBox EditableTextBox {
get { return Template.FindName("PART_EditableTextBox", this) as TextBox; }
}
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e) {
if (!IsEditable) {
base.OnPreviewLostKeyboardFocus(e);
return;
}
ClearFilter();
int temp = SelectedIndex;
SelectedIndex = -1;
Text = string.Empty;
SelectedIndex = temp;
base.OnPreviewLostKeyboardFocus(e);
}
protected override void OnKeyUp(KeyEventArgs e) {
if (!IsEditable) {
base.OnKeyUp(e);
return;
}
if (e.Key == Key.Up || e.Key == Key.Down) {
// Navigation keys are ignored
} else if (e.Key == Key.Tab || e.Key == Key.Enter) {
// Explicit Select -> Clear Filter
ClearFilter();
} else {
// The text was changed
if (Text != _oldFilter) {
// Clear the filter if the text is empty,
// apply the filter if the text is long enough
if (Text.Length == 0 || Text.Length >= MinimumSearchLength) {
RefreshFilter();
IsDropDownOpen = true;
// Unselect
EditableTextBox.SelectionStart = int.MaxValue;
}
}
base.OnKeyUp(e);
_currentFilter = Text;
}
}
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) {
if (!IsEditable) {
base.OnItemsSourceChanged(oldValue, newValue);
return;
}
if (newValue != null) {
var view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += FilterPredicate;
}
if (oldValue != null) {
var view = CollectionViewSource.GetDefaultView(oldValue);
view.Filter -= FilterPredicate;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
protected override void OnPreviewKeyDown(KeyEventArgs e) {
if (!IsEditable) {
base.OnPreviewKeyDown(e);
return;
}
if (e.Key == Key.Tab || e.Key == Key.Enter) {
// Explicit Selection -> Close ItemsPanel
IsDropDownOpen = false;
} else if (e.Key == Key.Escape) {
// Escape -> Close DropDown and redisplay Filter
IsDropDownOpen = false;
SelectedIndex = -1;
Text = _currentFilter;
} else {
if (e.Key == Key.Down) {
// Arrow Down -> Open DropDown
IsDropDownOpen = true;
}
base.OnPreviewKeyDown(e);
}
_oldFilter = Text;
}
#endregion
// ======================================================================
#region Private
private void RefreshFilter() {
if (ItemsSource != null) {
var view = CollectionViewSource.GetDefaultView(ItemsSource);
view.Refresh();
}
}
private void ClearFilter() {
_currentFilter = string.Empty;
RefreshFilter();
}
private bool FilterPredicate(object value) {
if (value == null) {
return false;
}
if (Text.Length == 0) {
return true;
}
var properties = GetProperties(value.GetType());
foreach (var property in properties) {
var propertyValue = (property.GetValue(value, null) ?? string.Empty).ToString();
if (propertyValue.ToLowerInvariant().Contains(Text.ToLowerInvariant())) {
return true;
}
}
return false;
}
private IEnumerable<PropertyInfo> GetProperties(Type type) {
if (!_properties.ContainsKey(type)) {
_properties.Add(type, new List<PropertyInfo>());
foreach (var column in _columns) {
if (column.Binding != null && column.Binding is Binding) {
var path = ((Binding)column.Binding).Path.Path;
var property = type.GetProperty(path);
if (property != null) {
_properties[type].Add(property);
}
}
}
}
return _properties[type];
}
private void PopupOpened(object sender, EventArgs e) {
ScrollIntoView(SelectedItem);
}
private void ScrollIntoView(object item) {
if (item != null && _popupDataGrid.Items.Contains(item))
_popupDataGrid.ScrollIntoView(item);
}
// Synchronize selection between Combo and DataGrid popup
private void PopupDataGridSelectionChanged(object sender, SelectionChangedEventArgs e) {
// When open in Blend prevent raising exception
if (!DesignerProperties.GetIsInDesignMode(this)) {
var grid = sender as DataGrid;
if (grid != null && grid.IsVisible) {
SelectedItem = grid.SelectedItem;
}
}
}
// Event for DataGrid popup MouseDown
private void PopupDataGridMouseDown(object sender, MouseButtonEventArgs e) {
DataGrid dg = sender as DataGrid;
if (dg != null) {
var dep = (DependencyObject)e.OriginalSource;
// iteratively traverse the visual tree and stop when dep is one of ..
while ((dep != null) &&
!(dep is DataGridCell) &&
!(dep is DataGridColumnHeader)) {
dep = VisualTreeHelper.GetParent(dep);
}
if (dep == null)
return;
if (dep is DataGridColumnHeader) {
// do something
}
// When user clicks to DataGrid cell, popup have to be closed
if (dep is DataGridCell) {
IsDropDownOpen = false;
}
}
}
#endregion
}
}
The following Xaml can be used to test it:
<Window x:Class="GridComboTestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:combo="clr-namespace:CustomControls;assembly=CustomControls"
Height="300" Width="300">
<StackPanel>
<Button Content="ChangeElements" Command="{Binding ChangeElements}" />
<combo:GridCombo
x:Name="GridCombo"
ItemsSource="{Binding Elements}"
DisplayMemberPath="Number"
IsEditable="True"
SelectedItem="{Binding SelectedElement}"
MaxDropDownHeight="100">
<DataGridTextColumn Binding="{Binding Number, Mode=OneWay}" />
<DataGridTextColumn Binding="{Binding Name, Mode=OneWay}" />
</combo:GridCombo>
</StackPanel>
</Window>
You are not calling base() on PopupDataGridMouseDown. Not sure this will fix it but something to look at.

How to initialize adorner in attached property

I want to create Adorner for FrameworkElement in attached property. But in PropertyChangedCallback AdornerLayer for my element is null.
How can I solve this issue?
Currently I do this:
private static void _OnIsModalAdornerAttachedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as FrameworkElement;
bool oldValue = (bool)e.OldValue;
bool newValue = (bool)e.NewValue;
if (null != element && oldValue != newValue)
{
var descriptor = DependencyPropertyDescriptor.FromProperty(VisibilityProperty, typeof(FrameworkElement));
if (newValue)
{
descriptor.AddValueChanged(element, element_VisibilityChanged);
}
else
{
descriptor.RemoveValueChanged(element, element_VisibilityChanged);
}
}
private static void element_VisibilityChanged(object sender, EventArgs e)
{
var element = sender as FrameworkElement;
if (null != element)
{
var adornerLayer = AdornerLayer.GetAdornerLayer(element);
if (null != adornerLayer)
{
// check if adorner exists
bool isExists = false;
var adorners = adornerLayer.GetAdorners(element);
if (null != adorners)
{
foreach (var adorner in adorners)
{
if (adorner is ModalAdorner)
{
isExists = true;
break;
}
}
}
// add if is not presented
if (!isExists)
{
var modalAdorner = new ModalAdorner(element);
adornerLayer.Add(modalAdorner);
var visibilityBinding = new Binding { Path = new PropertyPath("Visibility"), Source = element };
modalAdorner.SetBinding(VisibilityProperty, visibilityBinding);
}
}
}
}
Then I manually change Visibility property for my FrameworkElementto Hidden and then to Visible. But this way is not really true.
UPDATE
I solved this issue. Now I do so:
private static void _OnIsModalAdornerAttachedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as FrameworkElement;
bool oldValue = (bool)e.OldValue;
bool newValue = (bool)e.NewValue;
if (null != element && oldValue != newValue)
{
if (newValue)
{
if (element.IsLoaded)
{
_AttachAdorner(element);
}
else
{
element.Loaded += (sender, args) =>
{
_AttachAdorner(element);
};
}
}
else
{
// remove adorner
var adornerLayer = AdornerLayer.GetAdornerLayer(element);
if (null != adornerLayer)
{
var adorners = adornerLayer.GetAdorners(element);
if (null != adorners)
{
foreach (var adorner in adorners)
{
if (adorner is ModalAdorner)
{
adornerLayer.Remove(adorner);
}
}
}
}
}
}
}
The answer is in update: use Loaded event.

Resources