Skip to content

Commit d103721

Browse files
committed
improve cloned text box behaviour in editable combos #120
1 parent 4d3692a commit d103721

File tree

4 files changed

+187
-1
lines changed

4 files changed

+187
-1
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using System.Windows;
7+
using System.Windows.Controls;
8+
using System.Windows.Controls.Primitives;
9+
using System.Windows.Data;
10+
using System.Windows.Input;
11+
using System.Windows.Media;
12+
using System.Windows.Threading;
13+
14+
namespace MaterialDesignThemes.Wpf
15+
{
16+
public static class ComboBoxAssist
17+
{
18+
private static readonly HashSet<Key> KeysToDelegate = new HashSet<Key>
19+
{
20+
Key.Up,
21+
Key.Down,
22+
Key.F4,
23+
Key.Escape,
24+
Key.Enter,
25+
Key.Home,
26+
Key.End,
27+
//Key.Right,
28+
//Key.Left,
29+
Key.PageUp,
30+
Key.PageDown,
31+
Key.Oem5
32+
};
33+
34+
/// <summary>
35+
/// Intended for internal use only.
36+
/// </summary>
37+
public static readonly DependencyProperty ManagedOverlayProperty = DependencyProperty.RegisterAttached(
38+
"ManagedOverlay", typeof (bool), typeof (ComboBoxAssist), new PropertyMetadata(default(bool), ManagedOverlayPropertyChangedCallback));
39+
40+
private static void ManagedOverlayPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
41+
{
42+
var comboBox = dependencyObject as ComboBox;
43+
if (comboBox == null) return;
44+
45+
if ((bool) dependencyPropertyChangedEventArgs.NewValue)
46+
{
47+
comboBox.DropDownOpened += ComboBoxOnDropDownOpened;
48+
comboBox.DropDownClosed += ComboBoxOnDropDownClosed;
49+
}
50+
else
51+
{
52+
comboBox.DropDownOpened -= ComboBoxOnDropDownOpened;
53+
comboBox.DropDownClosed -= ComboBoxOnDropDownClosed;
54+
}
55+
}
56+
57+
/// <summary>
58+
/// Intended for internal use only.
59+
/// </summary>
60+
public static void SetManagedOverlay(DependencyObject element, bool value)
61+
{
62+
element.SetValue(ManagedOverlayProperty, value);
63+
}
64+
65+
/// <summary>
66+
/// Intended for internal use only.
67+
/// </summary>
68+
public static bool GetManagedOverlay(DependencyObject element)
69+
{
70+
return (bool) element.GetValue(ManagedOverlayProperty);
71+
}
72+
73+
public static readonly DependencyProperty ManagedOverlayInfoProperty = DependencyProperty.RegisterAttached(
74+
"ManagedOverlayInfo", typeof (ComboBoxAssistManagedOverlayInfo), typeof (ComboBoxAssist), new PropertyMetadata(default(ComboBoxAssistManagedOverlayInfo)));
75+
76+
/// <summary>
77+
/// Intended for internal use only.
78+
/// </summary>
79+
public static void SetManagedOverlayInfo(DependencyObject element, object value)
80+
{
81+
element.SetValue(ManagedOverlayInfoProperty, value);
82+
}
83+
84+
/// <summary>
85+
/// Intended for internal use only.
86+
/// </summary>
87+
public static object GetManagedOverlayInfo(DependencyObject element)
88+
{
89+
return (ComboBoxAssistManagedOverlayInfo) element.GetValue(ManagedOverlayInfoProperty);
90+
}
91+
92+
private static void ComboBoxOnDropDownOpened(object sender, EventArgs eventArgs)
93+
{
94+
var comboBox = (ComboBox)sender;
95+
var cloneTextBox = comboBox.Template.FindName("EditableTextBoxClone", comboBox) as TextBox;
96+
var originalTextBox = comboBox.Template.FindName("PART_EditableTextBox", comboBox) as TextBox;
97+
98+
if (cloneTextBox != null && originalTextBox != null)
99+
{
100+
var managedOverlayInfo = new ComboBoxAssistManagedOverlayInfo(comboBox, originalTextBox, cloneTextBox);
101+
102+
cloneTextBox.PreviewKeyDown += CloneTextBoxOnPreviewKeyDown;
103+
cloneTextBox.SelectionLength = originalTextBox.SelectionLength;
104+
cloneTextBox.SelectionStart = originalTextBox.SelectionStart;
105+
originalTextBox.SelectionChanged += OriginalTextBoxOnSelectionChanged;
106+
SetManagedOverlayInfo(comboBox, managedOverlayInfo);
107+
SetManagedOverlayInfo(originalTextBox, managedOverlayInfo);
108+
SetManagedOverlayInfo(cloneTextBox, managedOverlayInfo);
109+
}
110+
111+
cloneTextBox?.Dispatcher.BeginInvoke(new Action(() =>
112+
{
113+
cloneTextBox.Focus();
114+
}));
115+
}
116+
117+
private static void OriginalTextBoxOnSelectionChanged(object sender, RoutedEventArgs routedEventArgs)
118+
{
119+
var managedOverlayInfo = GetManagedOverlayInfo((DependencyObject)sender) as ComboBoxAssistManagedOverlayInfo;
120+
if (managedOverlayInfo == null) return;
121+
122+
managedOverlayInfo.CloneTextBox.SelectionLength = managedOverlayInfo.OriginalTextBox.SelectionLength;
123+
managedOverlayInfo.CloneTextBox.SelectionStart = managedOverlayInfo.OriginalTextBox.SelectionStart;
124+
}
125+
126+
private static void CloneTextBoxOnPreviewKeyDown(object sender, KeyEventArgs keyEventArgs)
127+
{
128+
if (!KeysToDelegate.Contains(keyEventArgs.Key)) return;
129+
130+
var textBox = (TextBox) sender;
131+
var managedOverlayInfo = GetManagedOverlayInfo(textBox) as ComboBoxAssistManagedOverlayInfo;
132+
if (managedOverlayInfo == null) return;
133+
134+
managedOverlayInfo.OriginalTextBox.RaiseEvent(new KeyEventArgs(keyEventArgs.KeyboardDevice, keyEventArgs.InputSource, keyEventArgs.Timestamp, keyEventArgs.Key)
135+
{
136+
RoutedEvent = keyEventArgs.RoutedEvent
137+
});
138+
139+
keyEventArgs.Handled = true;
140+
}
141+
142+
private static void ComboBoxOnDropDownClosed(object sender, EventArgs eventArgs)
143+
{
144+
var comboBox = (ComboBox)sender;
145+
146+
var managedOverlayInfo = GetManagedOverlayInfo(comboBox) as ComboBoxAssistManagedOverlayInfo;
147+
if (managedOverlayInfo == null) return;
148+
149+
SetManagedOverlayInfo(managedOverlayInfo.Owner, null);
150+
SetManagedOverlayInfo(managedOverlayInfo.CloneTextBox, null);
151+
SetManagedOverlayInfo(managedOverlayInfo.OriginalTextBox, null);
152+
153+
managedOverlayInfo.CloneTextBox.PreviewKeyDown -= CloneTextBoxOnPreviewKeyDown;
154+
managedOverlayInfo.OriginalTextBox.SelectionChanged -= OriginalTextBoxOnSelectionChanged;
155+
managedOverlayInfo.OriginalTextBox.SelectionLength = managedOverlayInfo.CloneTextBox.SelectionLength;
156+
managedOverlayInfo.OriginalTextBox.SelectionStart = managedOverlayInfo.CloneTextBox.SelectionStart;
157+
}
158+
}
159+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
using System.Windows.Controls;
3+
4+
namespace MaterialDesignThemes.Wpf
5+
{
6+
internal class ComboBoxAssistManagedOverlayInfo
7+
{
8+
public ComboBoxAssistManagedOverlayInfo(ComboBox owner, TextBox originalTextBox, TextBox cloneTextBox)
9+
{
10+
if (owner == null) throw new ArgumentNullException(nameof(owner));
11+
if (originalTextBox == null) throw new ArgumentNullException(nameof(originalTextBox));
12+
if (cloneTextBox == null) throw new ArgumentNullException(nameof(cloneTextBox));
13+
14+
Owner = owner;
15+
OriginalTextBox = originalTextBox;
16+
CloneTextBox = cloneTextBox;
17+
}
18+
19+
public ComboBox Owner { get; }
20+
21+
public TextBox OriginalTextBox { get; }
22+
23+
public TextBox CloneTextBox { get; }
24+
}
25+
}

MaterialDesignThemes.Wpf/MaterialDesignThemes.Wpf.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@
203203
<Compile Include="ClockChoiceMadeEventArgs.cs" />
204204
<Compile Include="ClockItemButton.cs" />
205205
<Compile Include="ColorZone.cs" />
206+
<Compile Include="ComboBoxAssist.cs" />
207+
<Compile Include="ComboBoxAssistManagedOverlayInfo.cs" />
206208
<Compile Include="Converters\BrushRoundConverter.cs" />
207209
<Compile Include="Converters\BrushToRadialGradientBrushConverter.cs" />
208210
<Compile Include="Converters\CalendarDateCoalesceConverter.cs" />

MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.ComboBox.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@
567567
</ControlTemplate>
568568

569569
<Style x:Key="MaterialDesignComboBox" TargetType="{x:Type ComboBox}">
570-
<!--Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/-->
570+
<Setter Property="wpf:ComboBoxAssist.ManagedOverlay" Value="True"/>
571571
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
572572
<Setter Property="Background" Value="Transparent"/>
573573
<Setter Property="BorderBrush" Value="{DynamicResource MaterialDesignTextBoxBorder}"/>

0 commit comments

Comments
 (0)