I'm trying to set the keyboard focus to a textbox that is included in a stackpanel collapsed by default. When the stackpanel becomes visible i want the textbox to become, by default, focused.
I've tried this code:
<StackPanel Orientation="Vertical" FocusManager.FocusedElement="{Binding ElementName=TxtB}">
<TextBox x:Name="TxtA" Text="A" />
<TextBox x:Name="TxtB" Text="B" />
</StackPanel>
However, it didn't work. The type cursor showed up, but it wasn't blinking and didn't allow writing.
Is it possible to solve my problem using only XAML? Perhaps triggers?
Yes, as you said yourself, simple trigger seems to do the trick:
<StackPanel Orientation="Vertical">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Setter Property="FocusManager.FocusedElement"
Value="{Binding ElementName=TxtA}" />
</Trigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBox x:Name="TxtA" Text="A" />
<TextBox x:Name="TxtB" Text="B" />
</StackPanel>
You need to create attached property IsFocused which will call Focus() method of the attached element when set to true. Wait, I'll add some code.
public static class FocusHelper
{
static FocusHelper()
{
var fpmd = new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, HandleAttachedIsFocusedChanged) { DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged };
IsFocusedProperty = DependencyProperty.RegisterAttached("IsFocused", typeof(bool?), typeof(FocusHelper), fpmd);
}
public static readonly DependencyProperty IsFocusedProperty;
[Conditional("DEBUG")]
public static void StartFocusTracing()
{
FocusManager.FocusedElementProperty.OverrideMetadata(typeof(FrameworkElement), new PropertyMetadata(HandleFocusedElementChanged));
}
private static void HandleFocusedElementChanged(DependencyObject o, DependencyPropertyChangedEventArgs args)
{
var element = args.NewValue as FrameworkElement;
if (element == null)
{
Debug.WriteLine("Focus is lost");
return;
}
Debug.WriteLine("Focus moved to {0} type {1}", element.Name, element.GetType().Name);
var fs = FocusManager.GetFocusScope(element) as FrameworkElement;
if (fs == null)
return;
Debug.WriteLine("Focus scope {0} of type {1}", fs.Name, fs.GetType().Name);
}
public static bool? GetIsFocused(DependencyObject element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (bool?)element.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject element, bool? value)
{
if (element == null)
throw new ArgumentNullException("element");
element.SetValue(IsFocusedProperty, value);
}
private static void HandleAttachedIsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
WriteDependencyPropertyBindingInformation(d, IsFocusedProperty);
var fe = (UIElement)d;
// значение ранее было не задано
if (e.OldValue == null)
{
var pd = DependencyPropertyDescriptor.FromProperty(UIElement.IsFocusedProperty, typeof(UIElement));
pd.AddValueChanged(fe, HandleUIElementIsFocusedChanged);
}
if (e.NewValue == null)
{
var pd = DependencyPropertyDescriptor.FromProperty(UIElement.IsFocusedProperty, typeof(UIElement));
pd.RemoveValueChanged(fe, HandleUIElementIsFocusedChanged);
return;
}
if ((bool)e.NewValue)
{
Action setFocus = () =>
{
IInputElement elementToBeFocused = null;
IInputElement finalyFocusedElement = null;
// If current element is Focus Scope we try to restore logical focus
if (FocusManager.GetIsFocusScope(fe))
{
elementToBeFocused = FocusManager.GetFocusedElement(fe);
if (elementToBeFocused != null)
{
finalyFocusedElement = Keyboard.Focus(elementToBeFocused);
}
}
// If focus was not restored we try to focus
if (finalyFocusedElement == null
|| (elementToBeFocused != finalyFocusedElement))
{
fe.FocusThisOrChild();
}
};
if (ReflectionHelper.IsInMethod("MeasureOverride", typeof(FrameworkElement))) // hack of layout issue
Dispatcher.CurrentDispatcher.BeginInvoke(setFocus);
else
setFocus();
}
}
[Conditional("DEBUG")]
private static void WriteDependencyPropertyBindingInformation(DependencyObject d, DependencyProperty property)
{
var binding = BindingOperations.GetBindingBase(d, IsFocusedProperty);
if (binding == null)
{
Debug.WriteLine("Property {1} of object {0} has no bindings.", d, property.Name);
}
else
{
Debug.WriteLine("Property {1} of object {0} has binding.", d, property.Name);
Debug.WriteLine("Type {0}", binding.GetType());
var expressionBase = BindingOperations.GetBindingExpressionBase(d, IsFocusedProperty);
Debug.Assert(expressionBase != null);
Debug.WriteLine("Status {0}", expressionBase.Status);
var expression = expressionBase as BindingExpression;
if (expression != null)
{
Debug.WriteLine("Source type {0}", expression.DataItem.GetType());
Debug.WriteLine("Source {0}",expression.DataItem);
}
}
}
private static void HandleUIElementIsFocusedChanged(object sender, EventArgs e)
{
var uiElement = sender as UIElement;
var isFocused = uiElement.IsFocused;
((DependencyObject)sender).SetCurrentValue(IsFocusedProperty, isFocused);
}
/// <summary>
/// Tries to set focus to the element or any child element inside this one.
/// Tab index is respected
/// </summary>
public static bool FocusThisOrChild(this DependencyObject element)
{
if (element == null)
throw new ArgumentNullException("element");
var inputElement = element as IInputElement;
var wasFocused = inputElement != null && inputElement.Focus();
if (!wasFocused)
{
element.SetFocusWithin();
}
return true;
}
public static bool SetFocusWithin(this DependencyObject element)
{
if (element == null)
throw new ArgumentNullException("element");
var children = element.GetVisualChildrenSortedByTabIndex();
return children.Any(FocusThisOrChild);
}
}
and helper methods:
public static IEnumerable<DependencyObject> GetVisualChildrenSortedByTabIndex(this DependencyObject parent)
{
if (parent == null)
throw new ArgumentNullException("parent");
return parent.GetVisualChildren().OrderBy(KeyboardNavigation.GetTabIndex);
}
public static bool IsInMethod(string methodName, Type ownerType, bool isStatic = false)
{
if (string.IsNullOrWhiteSpace(methodName))
throw new ArgumentNullException("methodName");
if (ownerType == null)
throw new ArgumentNullException("ownerType");
var stackTrace = new StackTrace(false);
var isInMethod = stackTrace.GetFrames().Skip(1).Any(frame =>
{
var method = frame.GetMethod();
return method.Name == methodName
&& method.IsStatic == isStatic
&& ownerType.IsAssignableFrom(method.ReflectedType);
});
return isInMethod;
}
Related
I have a UWP project that uses MapControl, which is a sealed class - cant derive a new class from it.
Trying to make a bindable Attached Property, which would have access to MapControl.Children.
The problem is that it only works when I set ViewModel's collection, but not when I add a new element to that collection:
// Works fine
this.MapChildrenExtCollection = new ObservableCollection<MapChildElement>();
// Nothing happens
this.MapChildrenExtCollection.Add(new MapChildElement());
Heres my code for the Attached Property:
namespace UWPMap.Extensions
{
public class MapControlExt : DependencyObject
{
public static readonly DependencyProperty ChildrenExtProperty = DependencyProperty.Register(
"ChildrenExt",
typeof(ObservableCollection<MapChildElement>),
typeof(MapControlExt),
new PropertyMetadata(new ObservableCollection<MapChildElement>(), ChildrenExtPropertyChanged));
public ObservableCollection<MapChildElement> ChildrenExt
{
get { return (ObservableCollection<MapChildElement>)GetValue(ChildrenExtProperty); }
set { SetValue(ChildrenExtProperty, value); }
}
public static void SetChildrenExt(UIElement element, ObservableCollection<MapChildElement> value)
{
element.SetValue(ChildrenExtProperty, value);
}
public static ObservableCollection<MapChildElement> GetChildrenExt(UIElement element)
{
return (ObservableCollection<MapChildElement>)element.GetValue(ChildrenExtProperty);
}
private static void ChildrenExtPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var control = (MapControl)obj;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= Extensions.MapControlExt.ChildrenExtCollectionChanged;
}
if (newCollection != null)
{
oldCollection.CollectionChanged += Extensions.MapControlExt.ChildrenExtCollectionChanged;
}
ManageChildrenExt();
}
static void ChildrenExtCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ManageChildrenExt();
}
static private void ManageChildrenExt()
{
// Access MapControl.Children here
}
}
}
XAML:
<maps:MapControl x:Name="MyMap"
ext:MapControlExt.ChildrenExt="{x:Bind Path=MapChildrenExtCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</maps:MapControl>
The problem is that you are not adding the event handler to the new collection and using oldCollection variable by mistake.
The following snippet:
if (newCollection != null)
{
oldCollection.CollectionChanged += //here you have oldCollection by mistake
Extensions.MapControlExt.ChildrenExtCollectionChanged;
}
Should be:
if (newCollection != null)
{
newCollection.CollectionChanged +=
Extensions.MapControlExt.ChildrenExtCollectionChanged;
}
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>
I have color picker. If I selected different colors means it is fire color changed event. But I select color is already selected color, the Color changed event is not fired. So how can I achieve this requirement or how can hook event for when select color is already selected.
Xaml:
<system:SplitButton x:Name="Font_FontColor" Height="24" DataContext="{Binding ElementName=Font_FontColorPicker}">
<system:ColorPickerPalette x:Name="Font_FontColorPicker" system:SkinStorage.VisualStyle="Metro"
BlackWhiteVisibility="Both"
IsExpanded="True"
MoreColorOptionVisibility="Collapsed"/>
C#
it is a control behavior. so can make the code for achieve this by below codes
XAML:
<syncfusion:SplitButton x:Name="Font_FontColor" Height="24"
DataContext="{Binding ElementName=Font_FontColorPicker}">
<syncfusion:ColorPickerPalette x:Name="Font_FontColorPicker"
syncfusion:SkinStorage.VisualStyle="Metro"
BlackWhiteVisibility="Both" IsExpanded="True" MoreColorOptionVisibility="Collapsed"
Color="Red" />
C#:
public partial class MainWindow : Window
{
bool CanHookEvents = true;
public MainWindow()
{
InitializeComponent();
Font_FontColorPicker.ColorChanged += Font_FontColorPicker_ColorChanged;
//Font_FontColor.IsDropDownOpenChanged += Font_FontColor_IsDropDownOpenChanged;
Font_FontColorPicker.Loaded += Font_FontColorPicker_Loaded;
}
private void Font_FontColorPicker_Loaded(object sender, RoutedEventArgs e)
{
if (CanHookEvents)
{
foreach (ColorGroupItem item in FindVisualChildrenOfType<ColorGroupItem>(Font_FontColorPicker))
{
if (item != null)
{
item.PreviewMouseLeftButtonDown += item_PreviewMouseLeftButtonDown;
}
}
}
}
void item_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if(Font_FontColorPicker.Color.Equals((((sender as ColorGroupItem).Color) as SolidColorBrush).Color))
{
// I have closed dropdown. Do your stuff here
Font_FontColor.IsDropDownOpen = false;
}
}
public static IEnumerable<T> FindVisualChildrenOfType<T>(DependencyObject parent)
where T : DependencyObject
{
List<T> foundChildren = new List<T>();
int childCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
T childType = child as T;
if (childType == null)
{
foreach (var other in FindVisualChildrenOfType<T>(child))
yield return other;
}
else
{
yield return (T)child;
}
}
}
void Font_FontColorPicker_ColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Font_FontColor.IsDropDownOpen = false;
}
}
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.
The simplified example this;
picture a Venn diagram made from two elements, A and B, that overlap.
If I mouse over (A AND (NOT B)) all of A lights up.
If I mouse over (B AND (NOT A)) all of B lights up.
If I mouse over (A AND B), BOTH should light up. Only the top most is marked as having the mouse over it.
Is there a way to allow IsMouseOver to tunnel like this?
If not, any suggestions?
You can do manual hit testing using VisualTreeHelper. This can go into a MouseMove handler on some parent object. Here I'm assuming a Venn diagram made of ellipses named RedCircle and BlueCircle:
bool overRed = false;
bool overBlue = false;
if (BlueCircle.IsMouseOver || RedCircle.IsMouseOver)
{
HitTestParameters parameters = new PointHitTestParameters(e.GetPosition(RedCircle));
VisualTreeHelper.HitTest(RedCircle, new HitTestFilterCallback(element => HitTestFilterBehavior.Continue), result =>
{
if (result.VisualHit == RedCircle)
overRed = true;
return HitTestResultBehavior.Continue;
}, parameters);
parameters = new PointHitTestParameters(e.GetPosition(BlueCircle));
VisualTreeHelper.HitTest(BlueCircle, new HitTestFilterCallback(element => HitTestFilterBehavior.Continue), result =>
{
if (result.VisualHit == BlueCircle)
overBlue = true;
return HitTestResultBehavior.Continue;
}, parameters);
}
Use IsMouseDirectlyOver property. It seems to be thing you need.
http://msdn.microsoft.com/en-us/library/system.windows.uielement.ismousedirectlyoverproperty.aspx
I needed something similiar in my project, and whipped up a quick solution.
public sealed class IsMouseOverEnchancementBehavior
{
#region MouseCurrentPosition
internal sealed class MouseCurrentPosition
{
private Point _currentPosition;
private readonly Timer _timer = new Timer(250);
public event EventHandler<EventArgs> PositionChanged;
public MouseCurrentPosition()
{
_timer.Elapsed += timer_Elapsed;
_timer.Start();
}
public Point CurrentPosition
{
get { return _currentPosition; }
set
{
if (_currentPosition != value)
{
_currentPosition = value;
var pos = PositionChanged;
if (pos != null)
PositionChanged(null, null);
}
}
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetCursorPos(ref NativePoint pt);
public static Point GetCurrentMousePosition()
{
var nativePoint = new NativePoint();
GetCursorPos(ref nativePoint);
return new Point(nativePoint.X, nativePoint.Y);
}
private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
Point current = GetCurrentMousePosition();
CurrentPosition = current;
}
[StructLayout(LayoutKind.Sequential)]
internal struct NativePoint
{
public int X;
public int Y;
};
}
#endregion
private static readonly MouseCurrentPosition _mouseCurrentPosition = new MouseCurrentPosition();
public static DependencyProperty IsMouseOverIgnoreChild =
DependencyProperty.RegisterAttached("IsMouseOverIgnoreChild", typeof (bool),
typeof (IsMouseOverEnchancementBehavior),
new FrameworkPropertyMetadata(false));
public static readonly DependencyProperty IsMouseOverEnchancementEnabled =
DependencyProperty.RegisterAttached("IsMouseOverEnchancementEnabled",
typeof (bool), typeof (IsMouseOverEnchancementBehavior),
new UIPropertyMetadata(false, OnMouseOverEnchancementEnabled));
private static void OnMouseOverEnchancementEnabled(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// todo: unhook if necessary.
var frameworkElement = (FrameworkElement) d;
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(UIElement.IsMouseOverProperty,
typeof (UIElement));
Action calculateCurrentStateAction = () =>
{
// cheap check.
if (frameworkElement.IsMouseOver)
{
SetIsMouseOverIgnoreChild(frameworkElement, true);
return;
}
// through hit-testing.
var isMouseOver = VisualTreeHelper.
HitTest(frameworkElement, Mouse.GetPosition(frameworkElement)) != null;
SetIsMouseOverIgnoreChild(frameworkElement, isMouseOver);
};
// if the mose moves,
// we shall re-do hit testing.
_mouseCurrentPosition.PositionChanged += delegate
{
frameworkElement.Dispatcher.Invoke(
calculateCurrentStateAction);
};
// If IsMouseOver changes,
// we can propagate it to our property.
dpd.AddValueChanged(frameworkElement,
delegate { calculateCurrentStateAction(); });
}
#region Misc
public static bool GetIsMouseOverEnchancementEnabled(DependencyObject obj)
{
return (bool) obj.GetValue(IsMouseOverEnchancementEnabled);
}
public static void SetIsMouseOverEnchancementEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsMouseOverEnchancementEnabled, value);
}
public static bool GetIsMouseOverIgnoreChild(DependencyObject obj)
{
return (bool) obj.GetValue(IsMouseOverIgnoreChild);
}
public static void SetIsMouseOverIgnoreChild(DependencyObject obj, bool value)
{
obj.SetValue(IsMouseOverIgnoreChild, value);
}
#endregion
}
It's more of a generic solution that uses timers.
This is how I use it in the style:
<Setter Property="behaviors:IsMouseOverEnchancementBehavior.
IsMouseOverEnchancementEnabled" Value="True" />
<Style.Triggers>
<!-- Just a visual feedback -->
<!-- Let the user know that mouse is over the element -->
<!-- When we are in editmode -->
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsInEditMode" Value="True" />
<Condition Property="behaviors:IsMouseOverEnchancementBehavior.IsMouseOverIgnoreChild" Value="True" />
</MultiTrigger.Conditions>
...do stuff here....