Skip to content

Commit 5d4db1a

Browse files
committed
Add ComboBoxAssist.OnlyClassicMode property
1 parent 184c79a commit 5d4db1a

File tree

4 files changed

+184
-53
lines changed

4 files changed

+184
-53
lines changed

MainDemo.Wpf/TextFields.xaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@
6262
<RowDefinition Height="Auto" />
6363
<RowDefinition Height="Auto" />
6464
<RowDefinition Height="Auto" />
65+
<RowDefinition Height="Auto" />
66+
<RowDefinition Height="Auto" />
6567
</Grid.RowDefinitions>
6668
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Style="{StaticResource MaterialDesignHeadlineTextBlock}">Common Fields</TextBlock>
6769
<materialDesign:PackIcon Grid.Row="1" Grid.Column="0" Kind="Account" Foreground="{Binding ElementName=NameTextBox, Path=BorderBrush}" />
@@ -262,6 +264,7 @@
262264
</TextBox>
263265
<CheckBox Grid.Row="9" Grid.Column="4"
264266
x:Name="DisplaySelectedItemCheckBox"
267+
IsThreeState="False"
265268
Margin="0 8 0 0">Display Selected Item In Drop Down</CheckBox>
266269
<ComboBox Grid.Row="10" Grid.Column="4"
267270
materialDesign:ComboBoxAssist.ShowSelectedItem="{Binding ElementName=DisplaySelectedItemCheckBox, Path=IsChecked}"
@@ -271,6 +274,18 @@
271274
<ComboBoxItem>Pear</ComboBoxItem>
272275
<ComboBoxItem>Orange</ComboBoxItem>
273276
</ComboBox>
277+
<CheckBox Grid.Row="11" Grid.Column="4"
278+
x:Name="OnlyClassicModeCheckBox"
279+
IsThreeState="False" IsChecked="True"
280+
Margin="0 8 0 0">Use classic mode</CheckBox>
281+
<ComboBox Grid.Row="12" Grid.Column="4"
282+
materialDesign:ComboBoxAssist.OnlyClassicMode="{Binding ElementName=OnlyClassicModeCheckBox, Path=IsChecked}"
283+
Margin="0 8 0 8" HorizontalAlignment="Left">
284+
<ComboBoxItem IsSelected="True">Apple</ComboBoxItem>
285+
<ComboBoxItem>Banana</ComboBoxItem>
286+
<ComboBoxItem>Pear</ComboBoxItem>
287+
<ComboBoxItem>Orange</ComboBoxItem>
288+
</ComboBox>
274289

275290
</Grid>
276291
</UserControl>

MaterialDesignThemes.Wpf/ComboBoxAssist.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,27 @@ namespace MaterialDesignThemes.Wpf
1010
public static class ComboBoxAssist
1111
{
1212
/// <summary>
13-
/// By default the selected item his hidden from the drop down list, as per Material Design specifications.
13+
/// By default ComboBox uses the wrapper popup. Popup can be switched to classic Windows desktop view by means of this attached property.
14+
/// </summary>
15+
public static readonly DependencyProperty OnlyClassicModeProperty = DependencyProperty.RegisterAttached(
16+
"OnlyClassicMode",
17+
typeof (bool),
18+
typeof (ComboBoxAssist),
19+
new FrameworkPropertyMetadata(false,
20+
FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.Inherits));
21+
22+
public static bool GetOnlyClassicMode(DependencyObject element, object value)
23+
{
24+
return (bool)element.GetValue(OnlyClassicModeProperty);
25+
}
26+
27+
public static void SetOnlyClassicMode(DependencyObject element, object value)
28+
{
29+
element.SetValue(OnlyClassicModeProperty, value);
30+
}
31+
32+
/// <summary>
33+
/// By default the selected item is hidden from the drop down list, as per Material Design specifications.
1434
/// To revert to a more classic Windows desktop behaviour, and show the currently selected item again in the drop
1535
/// down, set this attached propety to true.
1636
/// </summary>

MaterialDesignThemes.Wpf/ComboBoxPopup.cs

Lines changed: 146 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.ComponentModel;
34
using System.Diagnostics;
45
using System.Linq;
56
using System.Windows;
@@ -26,7 +27,7 @@ public static readonly DependencyProperty UpContentTemplateProperty
2627
= DependencyProperty.Register(nameof(UpContentTemplate),
2728
typeof(ControlTemplate),
2829
typeof(ComboBoxPopup),
29-
new UIPropertyMetadata(null));
30+
new UIPropertyMetadata(null, CreateTemplatePropertyChangedCallback(ComboBoxPopupPlacement.Classic)));
3031

3132
public ControlTemplate UpContentTemplate
3233
{
@@ -42,7 +43,7 @@ public static readonly DependencyProperty DownContentTemplateProperty
4243
= DependencyProperty.Register(nameof(DownContentTemplate),
4344
typeof(ControlTemplate),
4445
typeof(ComboBoxPopup),
45-
new UIPropertyMetadata(null));
46+
new UIPropertyMetadata(null, CreateTemplatePropertyChangedCallback(ComboBoxPopupPlacement.Down)));
4647

4748
public ControlTemplate DownContentTemplate
4849
{
@@ -58,7 +59,7 @@ public static readonly DependencyProperty ClassicContentTemplateProperty
5859
= DependencyProperty.Register(nameof(ClassicContentTemplate),
5960
typeof(ControlTemplate),
6061
typeof(ComboBoxPopup),
61-
new UIPropertyMetadata(null));
62+
new UIPropertyMetadata(null, CreateTemplatePropertyChangedCallback(ComboBoxPopupPlacement.Up)));
6263

6364
public ControlTemplate ClassicContentTemplate
6465
{
@@ -166,9 +167,36 @@ public static readonly DependencyProperty VisiblePlacementWidthProperty
166167

167168
#endregion
168169

170+
#region OnlyClassicMode property
171+
172+
public static readonly DependencyProperty OnlyClassicModeProperty
173+
= DependencyProperty.Register(
174+
nameof(OnlyClassicMode),
175+
typeof(bool),
176+
typeof(ComboBoxPopup),
177+
new FrameworkPropertyMetadata(true));
178+
179+
public bool OnlyClassicMode
180+
{
181+
get { return (bool)GetValue(OnlyClassicModeProperty); }
182+
set { SetValue(OnlyClassicModeProperty, value); }
183+
}
184+
185+
#endregion
186+
169187
public ComboBoxPopup()
170188
{
171-
this.CustomPopupPlacementCallback = ComboBoxCustomPopupPlacementCallback;
189+
CustomPopupPlacementCallback = ComboBoxCustomPopupPlacementCallback;
190+
191+
DependencyPropertyDescriptor.FromProperty(ComboBoxPopup.ChildProperty, typeof(ComboBoxPopup))
192+
.AddValueChanged(this,
193+
delegate
194+
{
195+
if (PopupPlacement != ComboBoxPopupPlacement.Undefined)
196+
{
197+
UpdateChildTemplate(PopupPlacement);
198+
}
199+
});
172200
}
173201

174202
private void SetupBackground(IEnumerable<DependencyObject> visualAncestry)
@@ -198,88 +226,103 @@ private CustomPopupPlacement[] ComboBoxCustomPopupPlacementCallback(
198226

199227
SetupVisiblePlacementWidth(visualAncestry);
200228

201-
var locationFromScreen = PlacementTarget.PointToScreen(new Point(0, 0));
202-
203-
var mainVisual = visualAncestry.OfType<Visual>().LastOrDefault();
204-
if (mainVisual == null) return new CustomPopupPlacement[0];
205-
206-
var screenWidth = (int) DpiHelper.TransformToDeviceX(mainVisual, SystemParameters.PrimaryScreenWidth);
207-
var screenHeight = (int) DpiHelper.TransformToDeviceY(mainVisual, SystemParameters.PrimaryScreenHeight);
208-
209-
var locationX = (int)locationFromScreen.X % screenWidth;
210-
var locationY = (int)locationFromScreen.Y % screenHeight;
211-
212-
var realOffsetX = (popupSize.Width - targetSize.Width) / 2.0;
213-
214-
double offsetX;
215-
const int rtlHorizontalOffset = 20;
216-
217-
if (FlowDirection == FlowDirection.LeftToRight)
218-
offsetX = DpiHelper.TransformToDeviceX(mainVisual, offset.X);
219-
else
220-
offsetX = DpiHelper.TransformToDeviceX(mainVisual,
221-
offset.X - targetSize.Width - rtlHorizontalOffset);
229+
var data = GetPositioningData(visualAncestry, popupSize, targetSize, offset);
222230

223-
224-
if (locationX + popupSize.Width - realOffsetX > screenWidth
225-
|| locationX - realOffsetX < 0)
231+
if (OnlyClassicMode ||
232+
(data.LocationX + data.PopupSize.Width - data.RealOffsetX > data.ScreenWidth
233+
|| data.LocationX - data.RealOffsetX < 0))
226234
{
227235
PopupPlacement = ComboBoxPopupPlacement.Classic;
228236

229-
var defaultVerticalOffsetIndepent = DpiHelper.TransformToDeviceY(mainVisual, DefaultVerticalOffset);
230-
var newY = locationY + popupSize.Height > screenHeight
231-
? -(defaultVerticalOffsetIndepent + popupSize.Height)
232-
: defaultVerticalOffsetIndepent + targetSize.Height;
233-
234-
return new[] { new CustomPopupPlacement(new Point(offsetX, newY), PopupPrimaryAxis.Horizontal) };
237+
return new[] { GetClassicPopupPlacement(this, data) };
235238
}
236-
237-
if (locationY + popupSize.Height > screenHeight)
239+
else if (data.LocationY + data.PopupSize.Height > data.ScreenHeight)
238240
{
239241
PopupPlacement = ComboBoxPopupPlacement.Up;
240242

241-
var upVerticalOffsetIndepent = DpiHelper.TransformToDeviceY(mainVisual, UpVerticalOffset);
242-
var newY = upVerticalOffsetIndepent - popupSize.Height + targetSize.Height;
243-
244-
return new[] { new CustomPopupPlacement(new Point(offsetX, newY), PopupPrimaryAxis.None) };
243+
return new[] { GetUpPopupPlacement(this, data) };
245244
}
246245
else
247246
{
248247
PopupPlacement = ComboBoxPopupPlacement.Down;
249248

250-
var downVerticalOffsetIndepent = DpiHelper.TransformToDeviceY(mainVisual, DownVerticalOffset);
251-
var newY = downVerticalOffsetIndepent;
252-
253-
return new[] { new CustomPopupPlacement(new Point(offsetX, newY), PopupPrimaryAxis.None) };
249+
return new[] { GetDownPopupPlacement(this, data) };
254250
}
255251
}
256252

257253
private void SetChildTemplateIfNeed(ControlTemplate template)
258254
{
259255
var contentControl = Child as ContentControl;
260-
if (contentControl == null) throw new InvalidOperationException("Child must be ContentControl");
256+
if (contentControl == null) return;
257+
//throw new InvalidOperationException($"The type of {nameof(Child)} must be {nameof(ContentControl)}");
261258

262259
if (!ReferenceEquals(contentControl.Template, template))
263260
{
264261
contentControl.Template = template;
265262
}
266263
}
267264

268-
private void SetChildTemplate(ComboBoxPopupPlacement placement)
265+
private PositioningData GetPositioningData(IEnumerable<DependencyObject> visualAncestry, Size popupSize, Size targetSize, Point offset)
266+
{
267+
var locationFromScreen = PlacementTarget.PointToScreen(new Point(0, 0));
268+
269+
var mainVisual = visualAncestry.OfType<Visual>().LastOrDefault();
270+
if (mainVisual == null) throw new ArgumentException($"{nameof(visualAncestry)} must contains unless one {nameof(Visual)} control inside.");
271+
272+
var screenWidth = (int)DpiHelper.TransformToDeviceX(mainVisual, SystemParameters.PrimaryScreenWidth);
273+
var screenHeight = (int)DpiHelper.TransformToDeviceY(mainVisual, SystemParameters.PrimaryScreenHeight);
274+
275+
var locationX = (int)locationFromScreen.X % screenWidth;
276+
var locationY = (int)locationFromScreen.Y % screenHeight;
277+
278+
double offsetX;
279+
const int rtlHorizontalOffset = 20;
280+
281+
if (FlowDirection == FlowDirection.LeftToRight)
282+
offsetX = DpiHelper.TransformToDeviceX(mainVisual, offset.X);
283+
else
284+
offsetX = DpiHelper.TransformToDeviceX(mainVisual,
285+
offset.X - targetSize.Width - rtlHorizontalOffset);
286+
287+
return new PositioningData(
288+
mainVisual, offsetX,
289+
popupSize, targetSize,
290+
locationX, locationY,
291+
screenHeight, screenWidth);
292+
}
293+
294+
private static PropertyChangedCallback CreateTemplatePropertyChangedCallback(ComboBoxPopupPlacement popupPlacement)
295+
{
296+
return delegate (DependencyObject d, DependencyPropertyChangedEventArgs e)
297+
{
298+
var popup = d as ComboBoxPopup;
299+
if (popup == null) return;
300+
301+
var template = e.NewValue as ControlTemplate;
302+
if (template == null) return;
303+
304+
if (popup.PopupPlacement == popupPlacement)
305+
{
306+
popup.SetChildTemplateIfNeed(template);
307+
}
308+
};
309+
}
310+
311+
private void UpdateChildTemplate(ComboBoxPopupPlacement placement)
269312
{
270313
switch (placement)
271314
{
272315
case ComboBoxPopupPlacement.Classic:
273316
SetChildTemplateIfNeed(ClassicContentTemplate);
274-
return;
317+
break;
275318
case ComboBoxPopupPlacement.Down:
276319
SetChildTemplateIfNeed(DownContentTemplate);
277-
return;
320+
break;
278321
case ComboBoxPopupPlacement.Up:
279322
SetChildTemplateIfNeed(UpContentTemplate);
280-
return;
281-
default:
282-
throw new NotImplementedException($"Unexpected value {placement} of the {nameof(PopupPlacement)} property inside the {nameof(ComboBoxPopup)} control.");
323+
break;
324+
// default:
325+
// throw new NotImplementedException($"Unexpected value {placement} of the {nameof(PopupPlacement)} property inside the {nameof(ComboBoxPopup)} control.");
283326
}
284327
}
285328

@@ -290,8 +333,59 @@ private static void PopupPlacementPropertyChangedCallback(DependencyObject d, De
290333

291334
if (!(e.NewValue is ComboBoxPopupPlacement)) return;
292335
var placement = (ComboBoxPopupPlacement)e.NewValue;
336+
popup.UpdateChildTemplate(placement);
337+
}
338+
339+
private static CustomPopupPlacement GetClassicPopupPlacement(ComboBoxPopup popup, PositioningData data)
340+
{
341+
var defaultVerticalOffsetIndepent = DpiHelper.TransformToDeviceY(data.MainVisual, popup.DefaultVerticalOffset);
342+
var newY = data.LocationY + data.PopupSize.Height > data.ScreenHeight
343+
? -(defaultVerticalOffsetIndepent + data.PopupSize.Height)
344+
: defaultVerticalOffsetIndepent + data.TargetSize.Height;
345+
346+
return new CustomPopupPlacement(new Point(data.OffsetX, newY), PopupPrimaryAxis.Horizontal);
347+
}
348+
349+
private static CustomPopupPlacement GetDownPopupPlacement(ComboBoxPopup popup, PositioningData data)
350+
{
351+
var downVerticalOffsetIndepent = DpiHelper.TransformToDeviceY(data.MainVisual, popup.DownVerticalOffset);
293352

294-
popup.SetChildTemplate(placement);
353+
return new CustomPopupPlacement(new Point(data.OffsetX, downVerticalOffsetIndepent), PopupPrimaryAxis.None);
354+
}
355+
356+
private static CustomPopupPlacement GetUpPopupPlacement(ComboBoxPopup popup, PositioningData data)
357+
{
358+
var upVerticalOffsetIndepent = DpiHelper.TransformToDeviceY(data.MainVisual, popup.UpVerticalOffset);
359+
var newY = upVerticalOffsetIndepent - data.PopupSize.Height + data.TargetSize.Height;
360+
361+
return new CustomPopupPlacement(new Point(data.OffsetX, newY), PopupPrimaryAxis.None);
362+
}
363+
364+
private struct PositioningData
365+
{
366+
public Visual MainVisual { get; }
367+
public double OffsetX { get; }
368+
public double RealOffsetX => (PopupSize.Width - TargetSize.Width) / 2.0;
369+
public Size PopupSize { get; }
370+
public Size TargetSize { get; }
371+
public double LocationX { get; }
372+
public double LocationY { get; }
373+
public double ScreenHeight { get; }
374+
public double ScreenWidth { get; }
375+
376+
public PositioningData(
377+
Visual mainVisual,
378+
double offsetX,
379+
Size popupSize, Size targetSize,
380+
double locationX, double locationY,
381+
double screenHeight, double screenWidth)
382+
{
383+
MainVisual = mainVisual;
384+
OffsetX = offsetX;
385+
PopupSize = popupSize; TargetSize = targetSize;
386+
LocationX = locationX; LocationY = locationY;
387+
ScreenWidth = screenWidth; ScreenHeight = screenHeight;
388+
}
295389
}
296390
}
297391
}

MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.ComboBox.xaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<system:Double x:Key="PopupTopBottomMargin">8</system:Double>
1919
<system:Double x:Key="PopupLeftRightMargin">16</system:Double>
2020
<system:Boolean x:Key="TrueValue">True</system:Boolean>
21+
<system:Boolean x:Key="FalseValue">False</system:Boolean>
2122

2223
<Style x:Key="FocusVisual">
2324
<Setter Property="Control.Template">
@@ -458,6 +459,7 @@
458459
DefaultVerticalOffset="5"
459460
DownVerticalOffset="-15.5"
460461
UpVerticalOffset="15"
462+
OnlyClassicMode="{Binding Path=(wpf:ComboBoxAssist.OnlyClassicMode), RelativeSource={RelativeSource TemplatedParent}}"
461463
UpContentTemplate="{StaticResource PopupContentUpTemplate}"
462464
DownContentTemplate="{StaticResource PopupContentDownTemplate}"
463465
ClassicContentTemplate="{StaticResource PopupContentClassicTemplate}">

0 commit comments

Comments
 (0)