-
-
Notifications
You must be signed in to change notification settings - Fork 876
Fix SplitButton various issues #1498
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
50601e0
634e6e9
f243e6d
a3da0f1
76f56aa
1e33cc4
a91aabe
0659cfd
6b0c909
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,16 +5,22 @@ | |
|
||
using System.Windows.Controls; | ||
using System.Windows.Controls.Primitives; | ||
using System.Windows.Input; | ||
|
||
// ReSharper disable once CheckNamespace | ||
namespace Wpf.Ui.Controls; | ||
|
||
/// <summary> | ||
/// Represents a button with two parts that can be invoked separately. One part behaves like a standard button and the other part invokes a flyout. | ||
/// </summary> | ||
[TemplatePart(Name = TemplateElementToggle, Type = typeof(Border))] | ||
[TemplatePart(Name = TemplateElementToggleButton, Type = typeof(ToggleButton))] | ||
[TemplatePart(Name = TemplateElementContent, Type = typeof(Border))] | ||
public class SplitButton : Button | ||
{ | ||
private const string TemplateElementContent = "PART_Content"; | ||
private const string TemplateElementToggle = "PART_Toggle"; | ||
|
||
/// <summary> | ||
/// Template element represented by the <c>ToggleButton</c> name. | ||
/// </summary> | ||
|
@@ -27,6 +33,9 @@ public class SplitButton : Button | |
/// </summary> | ||
protected ToggleButton SplitButtonToggleButton { get; set; } = null!; | ||
|
||
private Border _splitButtonToggleBorder; | ||
private Border _splitButtonContentBorder; | ||
|
||
/// <summary>Identifies the <see cref="Flyout"/> dependency property.</summary> | ||
public static readonly DependencyProperty FlyoutProperty = DependencyProperty.Register( | ||
nameof(Flyout), | ||
|
@@ -85,29 +94,12 @@ public SplitButton() | |
}; | ||
} | ||
|
||
protected virtual void AttachTemplateResources() | ||
{ | ||
base.OnApplyTemplate(); | ||
|
||
if (GetTemplateChild(TemplateElementToggleButton) is ToggleButton toggleButton) | ||
{ | ||
SplitButtonToggleButton = toggleButton; | ||
AttachToggleButtonClick(); | ||
} | ||
else | ||
{ | ||
throw new NullReferenceException( | ||
$"Element {nameof(TemplateElementToggleButton)} of type {typeof(ToggleButton)} not found in {typeof(SplitButton)}" | ||
); | ||
} | ||
} | ||
|
||
private void AttachToggleButtonClick() | ||
{ | ||
if (SplitButtonToggleButton != null) | ||
{ | ||
SplitButtonToggleButton.Click -= OnSplitButtonToggleButtonOnClick; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This had to be changed to PreviewMouseLeftButtonUp for two reasons.
|
||
SplitButtonToggleButton.Click += OnSplitButtonToggleButtonOnClick; | ||
SplitButtonToggleButton.PreviewMouseLeftButtonUp -= OnSplitButtonToggleButtonOnPreviewMouseLeftButtonUp; | ||
SplitButtonToggleButton.PreviewMouseLeftButtonUp += OnSplitButtonToggleButtonOnPreviewMouseLeftButtonUp; | ||
} | ||
} | ||
|
||
|
@@ -169,23 +161,78 @@ public override void OnApplyTemplate() | |
$"Element {nameof(TemplateElementToggleButton)} of type {typeof(ToggleButton)} not found in {typeof(SplitButton)}" | ||
); | ||
} | ||
|
||
if (GetTemplateChild(TemplateElementContent) is Border contentBorder) | ||
{ | ||
_splitButtonContentBorder = contentBorder; | ||
} | ||
|
||
if (GetTemplateChild(TemplateElementToggle) is Border toggleBorder) | ||
{ | ||
_splitButtonToggleBorder = toggleBorder; | ||
} | ||
|
||
PreviewMouseMove += OnPreviewMouseMove; | ||
MouseLeave += OnMouseLeave; | ||
} | ||
|
||
private void OnMouseLeave(object sender, MouseEventArgs e) | ||
{ | ||
if (_splitButtonToggleBorder != null) | ||
{ | ||
_splitButtonToggleBorder.Tag = null; | ||
} | ||
|
||
if (_splitButtonContentBorder != null) | ||
{ | ||
_splitButtonContentBorder.Tag = null; | ||
} | ||
} | ||
|
||
private void OnPreviewMouseMove(object sender, MouseEventArgs args) | ||
{ | ||
if (_splitButtonToggleBorder != null) | ||
{ | ||
var position = args.GetPosition(_splitButtonToggleBorder); | ||
HitTestResult hitTestResult = VisualTreeHelper.HitTest(_splitButtonToggleBorder, position); | ||
_splitButtonToggleBorder.Tag = hitTestResult?.VisualHit != null ? "IsMouseOver" : null; | ||
} | ||
|
||
if (_splitButtonContentBorder != null) | ||
{ | ||
var position = args.GetPosition(_splitButtonContentBorder); | ||
HitTestResult hitTestResult = VisualTreeHelper.HitTest(_splitButtonContentBorder, position); | ||
_splitButtonContentBorder.Tag = hitTestResult?.VisualHit != null ? "IsMouseOver" : null; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Triggered when the control is unloaded. Releases resource bindings. | ||
/// </summary> | ||
protected virtual void ReleaseTemplateResources() | ||
{ | ||
SplitButtonToggleButton.Click -= OnSplitButtonToggleButtonOnClick; | ||
if (SplitButtonToggleButton != null) | ||
SplitButtonToggleButton.PreviewMouseLeftButtonUp -= OnSplitButtonToggleButtonOnPreviewMouseLeftButtonUp; | ||
|
||
PreviewMouseMove -= OnPreviewMouseMove; | ||
MouseLeave -= OnMouseLeave; | ||
} | ||
|
||
private void OnSplitButtonToggleButtonOnClick(object sender, RoutedEventArgs e) | ||
private void OnSplitButtonToggleButtonOnPreviewMouseLeftButtonUp(object sender, MouseEventArgs e) | ||
{ | ||
if (sender is not ToggleButton || _contextMenu is null) | ||
{ | ||
return; | ||
} | ||
|
||
// Ensure mouse up actually happened inside the toggler, and not outside. | ||
var position = e.GetPosition(_splitButtonToggleBorder); | ||
HitTestResult hitTestResult = VisualTreeHelper.HitTest(_splitButtonToggleBorder, position); | ||
if (hitTestResult?.VisualHit == null) | ||
{ | ||
return; | ||
} | ||
|
||
_contextMenu.SetCurrentValue(MinWidthProperty, ActualWidth); | ||
_contextMenu.SetCurrentValue(ContextMenu.PlacementTargetProperty, this); | ||
_contextMenu.SetCurrentValue(ContextMenu.PlacementProperty, PlacementMode.Bottom); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,7 +42,6 @@ | |
<ControlTemplate TargetType="{x:Type ToggleButton}"> | ||
<Border | ||
x:Name="ContentBorder" | ||
Margin="0,-1,-1,-1" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This caused the toggle button to be off-center, there's no need for this, so just remove it. |
||
Padding="11,0" | ||
Background="{TemplateBinding Background}" | ||
BorderBrush="{TemplateBinding BorderBrush}" | ||
|
@@ -72,24 +71,24 @@ | |
<ControlTemplate TargetType="{x:Type controls:SplitButton}"> | ||
<Border | ||
x:Name="ContentBorder" | ||
Grid.Row="0" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't needed, there's no Grid, so I removed it as well. |
||
Width="{TemplateBinding Width}" | ||
Height="{TemplateBinding Height}" | ||
MinWidth="{TemplateBinding MinWidth}" | ||
MinHeight="{TemplateBinding MinHeight}" | ||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}" | ||
VerticalAlignment="{TemplateBinding VerticalAlignment}" | ||
Background="{TemplateBinding Background}" | ||
CornerRadius="{TemplateBinding CornerRadius}"> | ||
<Grid> | ||
<Grid.ColumnDefinitions> | ||
<ColumnDefinition Width="Auto" /> | ||
<ColumnDefinition Width="Auto" /> | ||
</Grid.ColumnDefinitions> | ||
<Border | ||
Name="PART_Content" | ||
Grid.Column="0" | ||
Margin="0" | ||
Padding="{TemplateBinding Padding}" | ||
Background="{TemplateBinding Background}" | ||
BorderBrush="{TemplateBinding BorderBrush}" | ||
BorderThickness="{TemplateBinding BorderThickness, | ||
Converter={StaticResource LeftSplitThicknessConverter}}" | ||
|
@@ -115,12 +114,15 @@ | |
Grid.Column="1" | ||
VerticalAlignment="Center" | ||
Content="{TemplateBinding Content}" | ||
ContentTemplate="{TemplateBinding ContentTemplate}" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also added this so ContentTemplate can be used. |
||
TextElement.Foreground="{TemplateBinding Foreground}" /> | ||
</Grid> | ||
</Border> | ||
<Border | ||
Name="PART_Toggle" | ||
Grid.Column="1" | ||
Margin="0" | ||
Background="{TemplateBinding Background}" | ||
BorderBrush="{TemplateBinding BorderBrush}" | ||
BorderThickness="{TemplateBinding BorderThickness, | ||
Converter={StaticResource RightSplitThicknessConverter}}" | ||
|
@@ -134,7 +136,7 @@ | |
Focusable="False" | ||
Foreground="{TemplateBinding Foreground}" | ||
IsChecked="{TemplateBinding IsDropDownOpen}" | ||
Style="{StaticResource DefaultSplitButtonToggleButtonStyle}"> | ||
Style="{DynamicResource DefaultSplitButtonToggleButtonStyle}"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So we can override the style of the toggle button without having to redeclare the entire template. |
||
<controls:SymbolIcon FontSize="10" Symbol="ChevronDown24" /> | ||
</ToggleButton> | ||
</Border> | ||
|
@@ -143,19 +145,35 @@ | |
<ControlTemplate.Triggers> | ||
<MultiTrigger> | ||
<MultiTrigger.Conditions> | ||
<Condition Property="IsMouseOver" Value="True" /> | ||
<Condition SourceName="PART_Toggle" Property="Tag" Value="IsMouseOver" /> | ||
<Condition Property="IsPressed" Value="False" /> | ||
</MultiTrigger.Conditions> | ||
<Setter TargetName="ContentBorder" Property="Background" Value="{Binding MouseOverBackground, RelativeSource={RelativeSource TemplatedParent}}" /> | ||
<Setter TargetName="ContentBorder" Property="BorderBrush" Value="{Binding MouseOverBorderBrush, RelativeSource={RelativeSource TemplatedParent}}" /> | ||
<Setter TargetName="PART_Toggle" Property="Background" Value="{Binding MouseOverBackground, RelativeSource={RelativeSource TemplatedParent}}" /> | ||
<Setter TargetName="PART_Toggle" Property="BorderBrush" Value="{Binding MouseOverBorderBrush, RelativeSource={RelativeSource TemplatedParent}}" /> | ||
</MultiTrigger> | ||
<MultiTrigger> | ||
<MultiTrigger.Conditions> | ||
<Condition SourceName="PART_Content" Property="Tag" Value="IsMouseOver" /> | ||
<Condition Property="IsPressed" Value="False" /> | ||
</MultiTrigger.Conditions> | ||
<Setter TargetName="PART_Content" Property="Background" Value="{Binding MouseOverBackground, RelativeSource={RelativeSource TemplatedParent}}" /> | ||
<Setter TargetName="PART_Content" Property="BorderBrush" Value="{Binding MouseOverBorderBrush, RelativeSource={RelativeSource TemplatedParent}}" /> | ||
</MultiTrigger> | ||
<MultiTrigger> | ||
<MultiTrigger.Conditions> | ||
<Condition SourceName="PART_Toggle" Property="Tag" Value="IsMouseOver" /> | ||
<Condition SourceName="PART_ToggleButton" Property="IsPressed" Value="True" /> | ||
</MultiTrigger.Conditions> | ||
<Setter TargetName="PART_Toggle" Property="Background" Value="{Binding PressedBackground, RelativeSource={RelativeSource TemplatedParent}}" /> | ||
<Setter TargetName="PART_Toggle" Property="BorderBrush" Value="{Binding PressedBorderBrush, RelativeSource={RelativeSource TemplatedParent}}" /> | ||
</MultiTrigger> | ||
<MultiTrigger> | ||
<MultiTrigger.Conditions> | ||
<Condition Property="IsMouseOver" Value="True" /> | ||
<Condition SourceName="PART_Content" Property="Tag" Value="IsMouseOver" /> | ||
<Condition Property="IsPressed" Value="True" /> | ||
</MultiTrigger.Conditions> | ||
<Setter TargetName="ContentBorder" Property="Background" Value="{Binding PressedBackground, RelativeSource={RelativeSource TemplatedParent}}" /> | ||
<Setter TargetName="ContentBorder" Property="BorderBrush" Value="{Binding PressedBorderBrush, RelativeSource={RelativeSource TemplatedParent}}" /> | ||
<Setter TargetName="PART_Content" Property="Background" Value="{Binding PressedBackground, RelativeSource={RelativeSource TemplatedParent}}" /> | ||
<Setter TargetName="PART_Content" Property="BorderBrush" Value="{Binding PressedBorderBrush, RelativeSource={RelativeSource TemplatedParent}}" /> | ||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{Binding PressedForeground, RelativeSource={RelativeSource TemplatedParent}}" /> | ||
<Setter TargetName="ControlIcon" Property="TextElement.Foreground" Value="{Binding PressedForeground, RelativeSource={RelativeSource TemplatedParent}}" /> | ||
</MultiTrigger> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unused, so remove.