committed by
GitHub
30 changed files with 4632 additions and 19 deletions
@ -0,0 +1,123 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
|||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
|||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
|||
x:Class="ControlCatalog.Pages.DateTimePickerPage"> |
|||
<StackPanel Orientation="Vertical" Spacing="4" HorizontalAlignment="Stretch"> |
|||
<TextBlock Classes="h1">DatePicker and TimePicker</TextBlock> |
|||
<TextBlock Name="DatePickerDesc" Classes="h2" TextWrapping="Wrap"/> |
|||
<TextBlock Name="TimePickerDesc" Classes="h2" TextWrapping="Wrap"/> |
|||
|
|||
<StackPanel Orientation="Vertical" |
|||
Margin="16" |
|||
HorizontalAlignment="Stretch" |
|||
Spacing="16"> |
|||
<TextBlock FontSize="18">A simple DatePicker with a header</TextBlock> |
|||
<StackPanel Orientation="Vertical"> |
|||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" |
|||
BorderThickness="1" Padding="15"> |
|||
<DatePicker Header="Pick a date" /> |
|||
</Border> |
|||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> |
|||
<TextBlock Padding="15"> |
|||
<TextBlock.Text> |
|||
<x:String> |
|||
<DatePicker Header="Pick a date" /> |
|||
</x:String> |
|||
</TextBlock.Text> |
|||
</TextBlock> |
|||
</Panel> |
|||
</StackPanel> |
|||
|
|||
<TextBlock FontSize="18">A DatePicker with day formatted and year hidden.</TextBlock> |
|||
<StackPanel Orientation="Vertical"> |
|||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" |
|||
BorderThickness="1" Padding="15"> |
|||
<DatePicker x:Name="Control2" DayFormat="d (ddd)" |
|||
YearVisible="False" /> |
|||
</Border> |
|||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> |
|||
<TextBlock Padding="15"> |
|||
<TextBlock.Text> |
|||
<x:String> |
|||
<DatePicker DayFormat="d (ddd)" YearVisible="False" /> |
|||
</x:String> |
|||
</TextBlock.Text> |
|||
</TextBlock> |
|||
</Panel> |
|||
</StackPanel> |
|||
|
|||
<Border Background="{DynamicResource SystemControlHighlightBaseLowBrush}" BorderThickness="1" Margin="15" /> |
|||
|
|||
<TextBlock FontSize="18">A simple TimePicker.</TextBlock> |
|||
<StackPanel Orientation="Vertical"> |
|||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" |
|||
BorderThickness="1" Padding="15"> |
|||
<TimePicker /> |
|||
</Border> |
|||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> |
|||
<TextBlock Padding="15"> |
|||
<TextBlock.Text> |
|||
<x:String> |
|||
<TimePicker /> |
|||
</x:String> |
|||
</TextBlock.Text> |
|||
</TextBlock> |
|||
</Panel> |
|||
</StackPanel> |
|||
|
|||
<TextBlock FontSize="18">A TimePicker with a header and minute increments specified.</TextBlock> |
|||
<StackPanel Orientation="Vertical"> |
|||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" |
|||
BorderThickness="1" Padding="15"> |
|||
<TimePicker Header="Arrival time" MinuteIncrement="15" /> |
|||
</Border> |
|||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> |
|||
<TextBlock Padding="15"> |
|||
<TextBlock.Text> |
|||
<x:String> |
|||
<TimePicker Header="Arrival time" MinuteIncrement="15" /> |
|||
</x:String> |
|||
</TextBlock.Text> |
|||
</TextBlock> |
|||
</Panel> |
|||
</StackPanel> |
|||
|
|||
<TextBlock FontSize="18">A TimePicker using a 12-hour clock.</TextBlock> |
|||
<StackPanel Orientation="Vertical"> |
|||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" |
|||
BorderThickness="1" Padding="15"> |
|||
<TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" /> |
|||
</Border> |
|||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> |
|||
<TextBlock Padding="15"> |
|||
<TextBlock.Text> |
|||
<x:String> |
|||
<TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" /> |
|||
</x:String> |
|||
</TextBlock.Text> |
|||
</TextBlock> |
|||
</Panel> |
|||
</StackPanel> |
|||
|
|||
<TextBlock FontSize="18">A TimePicker using a 24-hour clock.</TextBlock> |
|||
<StackPanel Orientation="Vertical"> |
|||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" |
|||
BorderThickness="1" Padding="15"> |
|||
<TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" /> |
|||
</Border> |
|||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"> |
|||
<TextBlock Padding="15"> |
|||
<TextBlock.Text> |
|||
<x:String> |
|||
<TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" /> |
|||
</x:String> |
|||
</TextBlock.Text> |
|||
</TextBlock> |
|||
</Panel> |
|||
</StackPanel> |
|||
|
|||
</StackPanel> |
|||
</StackPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,30 @@ |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class DateTimePickerPage : UserControl |
|||
{ |
|||
public DateTimePickerPage() |
|||
{ |
|||
this.InitializeComponent(); |
|||
this.FindControl<TextBlock>("DatePickerDesc").Text = "Use a DatePicker to let users set a date in your app, " + |
|||
"for example to schedule an appointment. The DatePicker displays three controls for month, day, and year. " + |
|||
"These controls are easy to use with touch or mouse, and they can be styled and configured in several different ways. " + |
|||
"Order of month, day, and year is dynamically set based on user date settings"; |
|||
|
|||
this.FindControl<TextBlock>("TimePickerDesc").Text = "Use a TimePicker to let users set a time in your app, for example " + |
|||
"to set a reminder. The TimePicker displays three controls for hour, minute, and AM / PM(if necessary).These controls " + |
|||
"are easy to use with touch or mouse, and they can be styled and configured in several different ways. " + |
|||
"12 - hour or 24 - hour clock and visiblility of AM / PM is dynamically set based on user time settings, or can be overridden."; |
|||
|
|||
|
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,97 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
|||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
|||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
|||
x:Class="ControlCatalog.Pages.SplitViewPage"> |
|||
|
|||
<Border> |
|||
|
|||
<Grid ColumnDefinitions="*,225"> |
|||
|
|||
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="4" Margin="5"> |
|||
<ToggleButton Name="PaneOpenButton" |
|||
Content="IsPaneOpen" |
|||
IsChecked="{Binding IsPaneOpen, ElementName=SplitView}" /> |
|||
|
|||
<ToggleButton Name="UseLightDismissOverlayModeButton" |
|||
Content="UseLightDismissOverlayMode" |
|||
IsChecked="{Binding UseLightDismissOverlayMode, ElementName=SplitView}" /> |
|||
|
|||
<ToggleSwitch OffContent="Left" OnContent="Right" Content="Placement" IsChecked="{Binding !IsLeft}" /> |
|||
|
|||
<TextBlock Text="DisplayMode" /> |
|||
<ComboBox Name="DisplayModeSelector" Width="170" Margin="10" SelectedIndex="{Binding DisplayMode}"> |
|||
<ComboBoxItem>Inline</ComboBoxItem> |
|||
<ComboBoxItem>CompactInline</ComboBoxItem> |
|||
<ComboBoxItem>Overlay</ComboBoxItem> |
|||
<ComboBoxItem>CompactOverlay</ComboBoxItem> |
|||
</ComboBox> |
|||
|
|||
<TextBlock Text="PaneBackground" /> |
|||
<ComboBox Name="PaneBackgroundSelector" SelectedIndex="0" Width="170" Margin="10"> |
|||
<ComboBoxItem Tag="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}">SystemControlBackgroundChromeMediumLowBrush</ComboBoxItem> |
|||
<ComboBoxItem Tag="Red">Red</ComboBoxItem> |
|||
<ComboBoxItem Tag="Blue">Blue</ComboBoxItem> |
|||
<ComboBoxItem Tag="Green">Green</ComboBoxItem> |
|||
</ComboBox> |
|||
|
|||
<TextBlock Text="{Binding Value, ElementName=OpenPaneLengthSlider, StringFormat='{}OpenPaneLength: {0}'}" /> |
|||
<Slider Name="OpenPaneLengthSlider" Value="256" Minimum="128" Maximum="500" |
|||
Width="150" /> |
|||
|
|||
<TextBlock Text="{Binding Value, ElementName=CompactPaneLengthSlider, StringFormat='{}CompactPaneLength: {0}'}" /> |
|||
<Slider Name="CompactPaneLengthSlider" Value="48" Minimum="24" Maximum="128" |
|||
Width="150" /> |
|||
|
|||
</StackPanel> |
|||
|
|||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" |
|||
BorderThickness="1"> |
|||
<!--{Binding SelectedItem.Tag, ElementName=PaneBackgroundSelector}--> |
|||
<SplitView Name="SplitView" |
|||
PanePlacement="{Binding PanePlacement}" |
|||
PaneBackground="{Binding SelectedItem.Tag, ElementName=PaneBackgroundSelector}" |
|||
OpenPaneLength="{Binding Value, ElementName=OpenPaneLengthSlider}" |
|||
CompactPaneLength="{Binding Value, ElementName=CompactPaneLengthSlider}" |
|||
DisplayMode="{Binding CurrentDisplayMode}"> |
|||
<SplitView.Pane> |
|||
<Grid> |
|||
<Grid.RowDefinitions> |
|||
<RowDefinition Height="Auto" /> |
|||
<RowDefinition Height="*" /> |
|||
<RowDefinition Height="Auto" /> |
|||
</Grid.RowDefinitions> |
|||
<TextBlock Text="PANE CONTENT" FontWeight="Bold" Name="PaneHeader" Margin="5,12,0,0" /> |
|||
<ListBoxItem Grid.Row="1" VerticalAlignment="Top" Margin="0 10"> |
|||
<StackPanel Orientation="Horizontal"> |
|||
<!--Path glyph from materialdesignicons.com--> |
|||
<Border Width="48"> |
|||
<Viewbox Width="24" Height="24" HorizontalAlignment="Left"> |
|||
<Canvas Width="24" Height="24"> |
|||
<Path Fill="{DynamicResource SystemControlForegroundBaseHighBrush}" Data="M16 17V19H2V17S2 13 9 13 16 17 16 17M12.5 7.5A3.5 3.5 0 1 0 9 11A3.5 3.5 0 0 0 12.5 7.5M15.94 13A5.32 5.32 0 0 1 18 17V19H22V17S22 13.37 15.94 13M15 4A3.39 3.39 0 0 0 13.07 4.59A5 5 0 0 1 13.07 10.41A3.39 3.39 0 0 0 15 11A3.5 3.5 0 0 0 15 4Z" /> |
|||
</Canvas> |
|||
</Viewbox> |
|||
</Border> |
|||
<TextBlock Text="People" VerticalAlignment="Center" /> |
|||
</StackPanel> |
|||
</ListBoxItem> |
|||
<TextBlock Grid.Row="2" Text="Item at bottom" Margin="60,12" /> |
|||
</Grid> |
|||
</SplitView.Pane> |
|||
|
|||
<Grid> |
|||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" /> |
|||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" /> |
|||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Right" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" /> |
|||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" /> |
|||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" HorizontalAlignment="Right" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" /> |
|||
</Grid> |
|||
|
|||
</SplitView> |
|||
</Border> |
|||
|
|||
</Grid> |
|||
</Border> |
|||
|
|||
</UserControl> |
|||
@ -0,0 +1,21 @@ |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
using ControlCatalog.ViewModels; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class SplitViewPage : UserControl |
|||
{ |
|||
public SplitViewPage() |
|||
{ |
|||
this.InitializeComponent(); |
|||
DataContext = new SplitViewPageViewModel(); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using ReactiveUI; |
|||
|
|||
namespace ControlCatalog.ViewModels |
|||
{ |
|||
public class SplitViewPageViewModel : ReactiveObject |
|||
{ |
|||
private bool _isLeft = true; |
|||
private int _displayMode = 3; //CompactOverlay
|
|||
|
|||
public bool IsLeft |
|||
{ |
|||
get => _isLeft; |
|||
set |
|||
{ |
|||
this.RaiseAndSetIfChanged(ref _isLeft, value); |
|||
this.RaisePropertyChanged(nameof(PanePlacement)); |
|||
} |
|||
} |
|||
|
|||
public int DisplayMode |
|||
{ |
|||
get => _displayMode; |
|||
set |
|||
{ |
|||
this.RaiseAndSetIfChanged(ref _displayMode, value); |
|||
this.RaisePropertyChanged(nameof(CurrentDisplayMode)); |
|||
} |
|||
} |
|||
|
|||
public SplitViewPanePlacement PanePlacement => _isLeft ? SplitViewPanePlacement.Left : SplitViewPanePlacement.Right; |
|||
|
|||
public SplitViewDisplayMode CurrentDisplayMode |
|||
{ |
|||
get |
|||
{ |
|||
if (Enum.IsDefined(typeof(SplitViewDisplayMode), _displayMode)) |
|||
{ |
|||
return (SplitViewDisplayMode)_displayMode; |
|||
} |
|||
return SplitViewDisplayMode.CompactOverlay; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,85 @@ |
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases a <see cref="double"/> value
|
|||
/// using a user-defined cubic bezier curve.
|
|||
/// Good for custom easing functions that doesn't quite
|
|||
/// fit with the built-in ones.
|
|||
/// </summary>
|
|||
public class SplineEasing : Easing |
|||
{ |
|||
/// <summary>
|
|||
/// X coordinate of the first control point
|
|||
/// </summary>
|
|||
public double X1 |
|||
{ |
|||
get => _internalKeySpline.ControlPointX1; |
|||
set |
|||
{ |
|||
_internalKeySpline.ControlPointX1 = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Y coordinate of the first control point
|
|||
/// </summary>
|
|||
public double Y1 |
|||
{ |
|||
get => _internalKeySpline.ControlPointY1; |
|||
set |
|||
{ |
|||
_internalKeySpline.ControlPointY1 = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// X coordinate of the second control point
|
|||
/// </summary>
|
|||
public double X2 |
|||
{ |
|||
get => _internalKeySpline.ControlPointX2; |
|||
set |
|||
{ |
|||
_internalKeySpline.ControlPointX2 = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Y coordinate of the second control point
|
|||
/// </summary>
|
|||
public double Y2 |
|||
{ |
|||
get => _internalKeySpline.ControlPointY2; |
|||
set |
|||
{ |
|||
_internalKeySpline.ControlPointY2 = value; |
|||
} |
|||
} |
|||
|
|||
private readonly KeySpline _internalKeySpline; |
|||
|
|||
public SplineEasing(double x1 = 0d, double y1 = 0d, double x2 = 1d, double y2 = 1d) |
|||
{ |
|||
_internalKeySpline = new KeySpline(); |
|||
|
|||
this.X1 = x1; |
|||
this.Y1 = y1; |
|||
this.X2 = x2; |
|||
this.Y1 = y2; |
|||
} |
|||
|
|||
public SplineEasing(KeySpline keySpline) |
|||
{ |
|||
_internalKeySpline = keySpline; |
|||
} |
|||
|
|||
public SplineEasing() |
|||
{ |
|||
_internalKeySpline = new KeySpline(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) => |
|||
_internalKeySpline.GetSplineProgress(progress); |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Globalization; |
|||
|
|||
// Ported from WPF open-source code.
|
|||
// https://github.com/dotnet/wpf/blob/ae1790531c3b993b56eba8b1f0dd395a3ed7de75/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Animation/KeySpline.cs
|
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Converts string values to <see cref="KeySpline"/> values
|
|||
/// </summary>
|
|||
public class KeySplineTypeConverter : TypeConverter |
|||
{ |
|||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) |
|||
{ |
|||
return sourceType == typeof(string); |
|||
} |
|||
|
|||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) |
|||
{ |
|||
return KeySpline.Parse((string)value, culture); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,412 @@ |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Controls.Shapes; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Interactivity; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// A control to allow the user to select a date
|
|||
/// </summary>
|
|||
public class DatePicker : TemplatedControl |
|||
{ |
|||
/// <summary>
|
|||
/// Define the <see cref="DayFormat"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, string> DayFormatProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(DayFormat), |
|||
x => x.DayFormat, (x, v) => x.DayFormat = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="DayVisible"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, bool> DayVisibleProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(DayVisible), |
|||
x => x.DayVisible, (x, v) => x.DayVisible = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Header"/> Property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<object> HeaderProperty = |
|||
AvaloniaProperty.Register<DatePicker, object>(nameof(Header)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="HeaderTemplate"/> Property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty = |
|||
AvaloniaProperty.Register<DatePicker, IDataTemplate>(nameof(HeaderTemplate)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MaxYear"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, DateTimeOffset> MaxYearProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset>(nameof(MaxYear), |
|||
x => x.MaxYear, (x, v) => x.MaxYear = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MinYear"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, DateTimeOffset> MinYearProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset>(nameof(MinYear), |
|||
x => x.MinYear, (x, v) => x.MinYear = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MonthFormat"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, string> MonthFormatProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(MonthFormat), |
|||
x => x.MonthFormat, (x, v) => x.MonthFormat = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MonthVisible"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, bool> MonthVisibleProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(MonthVisible), |
|||
x => x.MonthVisible, (x, v) => x.MonthVisible = v); |
|||
|
|||
/// <summary>
|
|||
/// Defiens the <see cref="YearFormat"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, string> YearFormatProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(YearFormat), |
|||
x => x.YearFormat, (x, v) => x.YearFormat = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="YearVisible"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, bool> YearVisibleProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(YearVisible), |
|||
x => x.YearVisible, (x, v) => x.YearVisible = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="SelectedDate"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePicker, DateTimeOffset?> SelectedDateProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset?>(nameof(SelectedDate), |
|||
x => x.SelectedDate, (x, v) => x.SelectedDate = v); |
|||
|
|||
//Template Items
|
|||
private Button _flyoutButton; |
|||
private TextBlock _dayText; |
|||
private TextBlock _monthText; |
|||
private TextBlock _yearText; |
|||
private Grid _container; |
|||
private Rectangle _spacer1; |
|||
private Rectangle _spacer2; |
|||
private Popup _popup; |
|||
private DatePickerPresenter _presenter; |
|||
|
|||
private bool _areControlsAvailable; |
|||
|
|||
private string _dayFormat = "%d"; |
|||
private bool _dayVisible = true; |
|||
private DateTimeOffset _maxYear; |
|||
private DateTimeOffset _minYear; |
|||
private string _monthFormat = "MMMM"; |
|||
private bool _monthVisible = true; |
|||
private string _yearFormat = "yyyy"; |
|||
private bool _yearVisible = true; |
|||
private DateTimeOffset? _selectedDate; |
|||
|
|||
public DatePicker() |
|||
{ |
|||
PseudoClasses.Set(":hasnodate", true); |
|||
var now = DateTimeOffset.Now; |
|||
_minYear = new DateTimeOffset(now.Date.Year - 100, 1, 1, 0, 0, 0, now.Offset); |
|||
_maxYear = new DateTimeOffset(now.Date.Year + 100, 12, 31, 0, 0, 0, now.Offset); |
|||
} |
|||
|
|||
public string DayFormat |
|||
{ |
|||
get => _dayFormat; |
|||
set => SetAndRaise(DayFormatProperty, ref _dayFormat, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether the day is visible
|
|||
/// </summary>
|
|||
public bool DayVisible |
|||
{ |
|||
get => _dayVisible; |
|||
set |
|||
{ |
|||
SetAndRaise(DayVisibleProperty, ref _dayVisible, value); |
|||
SetGrid(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the DatePicker header
|
|||
/// </summary>
|
|||
public object Header |
|||
{ |
|||
get => GetValue(HeaderProperty); |
|||
set => SetValue(HeaderProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the header template
|
|||
/// </summary>
|
|||
public IDataTemplate HeaderTemplate |
|||
{ |
|||
get => GetValue(HeaderTemplateProperty); |
|||
set => SetValue(HeaderTemplateProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the maximum year for the picker
|
|||
/// </summary>
|
|||
public DateTimeOffset MaxYear |
|||
{ |
|||
get => _maxYear; |
|||
set |
|||
{ |
|||
if (value < MinYear) |
|||
throw new InvalidOperationException("MaxDate cannot be less than MinDate"); |
|||
SetAndRaise(MaxYearProperty, ref _maxYear, value); |
|||
|
|||
if (SelectedDate.HasValue && SelectedDate.Value > value) |
|||
SelectedDate = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the minimum year for the picker
|
|||
/// </summary>
|
|||
public DateTimeOffset MinYear |
|||
{ |
|||
get => _minYear; |
|||
set |
|||
{ |
|||
if (value > MaxYear) |
|||
throw new InvalidOperationException("MinDate cannot be greater than MaxDate"); |
|||
SetAndRaise(MinYearProperty, ref _minYear, value); |
|||
|
|||
if (SelectedDate.HasValue && SelectedDate.Value < value) |
|||
SelectedDate = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the month format
|
|||
/// </summary>
|
|||
public string MonthFormat |
|||
{ |
|||
get => _monthFormat; |
|||
set => SetAndRaise(MonthFormatProperty, ref _monthFormat, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether the month is visible
|
|||
/// </summary>
|
|||
public bool MonthVisible |
|||
{ |
|||
get => _monthVisible; |
|||
set |
|||
{ |
|||
SetAndRaise(MonthVisibleProperty, ref _monthVisible, value); |
|||
SetGrid(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the year format
|
|||
/// </summary>
|
|||
public string YearFormat |
|||
{ |
|||
get => _yearFormat; |
|||
set => SetAndRaise(YearFormatProperty, ref _yearFormat, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether the year is visible
|
|||
/// </summary>
|
|||
public bool YearVisible |
|||
{ |
|||
get => _yearVisible; |
|||
set |
|||
{ |
|||
SetAndRaise(YearVisibleProperty, ref _yearVisible, value); |
|||
SetGrid(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the Selected Date for the picker, can be null
|
|||
/// </summary>
|
|||
public DateTimeOffset? SelectedDate |
|||
{ |
|||
get => _selectedDate; |
|||
set |
|||
{ |
|||
var old = _selectedDate; |
|||
SetAndRaise(SelectedDateProperty, ref _selectedDate, value); |
|||
SetSelectedDateText(); |
|||
OnSelectedDateChanged(this, new DatePickerSelectedValueChangedEventArgs(old, value)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Raised when the <see cref="SelectedDate"/> changes
|
|||
/// </summary>
|
|||
public event EventHandler<DatePickerSelectedValueChangedEventArgs> SelectedDateChanged; |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
_areControlsAvailable = false; |
|||
if (_flyoutButton != null) |
|||
_flyoutButton.Click -= OnFlyoutButtonClicked; |
|||
if (_presenter != null) |
|||
{ |
|||
_presenter.Confirmed -= OnConfirmed; |
|||
_presenter.Dismissed -= OnDismissPicker; |
|||
} |
|||
|
|||
base.OnApplyTemplate(e); |
|||
_flyoutButton = e.NameScope.Find<Button>("FlyoutButton"); |
|||
_dayText = e.NameScope.Find<TextBlock>("DayText"); |
|||
_monthText = e.NameScope.Find<TextBlock>("MonthText"); |
|||
_yearText = e.NameScope.Find<TextBlock>("YearText"); |
|||
_container = e.NameScope.Find<Grid>("ButtonContentGrid"); |
|||
_spacer1 = e.NameScope.Find<Rectangle>("FirstSpacer"); |
|||
_spacer2 = e.NameScope.Find<Rectangle>("SecondSpacer"); |
|||
_popup = e.NameScope.Find<Popup>("Popup"); |
|||
_presenter = e.NameScope.Find<DatePickerPresenter>("PickerPresenter"); |
|||
|
|||
_areControlsAvailable = true; |
|||
|
|||
SetGrid(); |
|||
SetSelectedDateText(); |
|||
|
|||
if (_flyoutButton != null) |
|||
_flyoutButton.Click += OnFlyoutButtonClicked; |
|||
|
|||
if (_presenter != null) |
|||
{ |
|||
_presenter.Confirmed += OnConfirmed; |
|||
_presenter.Dismissed += OnDismissPicker; |
|||
|
|||
_presenter[!DatePickerPresenter.MaxYearProperty] = this[!MaxYearProperty]; |
|||
_presenter[!DatePickerPresenter.MinYearProperty] = this[!MinYearProperty]; |
|||
|
|||
_presenter[!DatePickerPresenter.MonthVisibleProperty] = this[!MonthVisibleProperty]; |
|||
_presenter[!DatePickerPresenter.MonthFormatProperty] = this[!MonthFormatProperty]; |
|||
|
|||
_presenter[!DatePickerPresenter.DayVisibleProperty] = this[!DayVisibleProperty]; |
|||
_presenter[!DatePickerPresenter.DayFormatProperty] = this[!DayFormatProperty]; |
|||
|
|||
_presenter[!DatePickerPresenter.YearVisibleProperty] = this[!YearVisibleProperty]; |
|||
_presenter[!DatePickerPresenter.YearFormatProperty] = this[!YearFormatProperty]; |
|||
} |
|||
} |
|||
|
|||
private void OnDismissPicker(object sender, EventArgs e) |
|||
{ |
|||
_popup.Close(); |
|||
Focus(); |
|||
} |
|||
|
|||
private void OnConfirmed(object sender, EventArgs e) |
|||
{ |
|||
_popup.Close(); |
|||
SelectedDate = _presenter.Date; |
|||
} |
|||
|
|||
private void SetGrid() |
|||
{ |
|||
if (_container == null) |
|||
return; |
|||
|
|||
var fmt = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern; |
|||
var columns = new List<(TextBlock, int)> |
|||
{ |
|||
(_monthText, MonthVisible ? fmt.IndexOf("m", StringComparison.OrdinalIgnoreCase) : -1), |
|||
(_yearText, YearVisible ? fmt.IndexOf("y", StringComparison.OrdinalIgnoreCase) : -1), |
|||
(_dayText, DayVisible ? fmt.IndexOf("d", StringComparison.OrdinalIgnoreCase) : -1), |
|||
}; |
|||
|
|||
columns.Sort((x, y) => x.Item2 - y.Item2); |
|||
_container.ColumnDefinitions.Clear(); |
|||
|
|||
var columnIndex = 0; |
|||
|
|||
foreach (var column in columns) |
|||
{ |
|||
if (column.Item1 is null) |
|||
continue; |
|||
|
|||
column.Item1.IsVisible = column.Item2 != -1; |
|||
|
|||
if (column.Item2 != -1) |
|||
{ |
|||
if (columnIndex > 0) |
|||
{ |
|||
_container.ColumnDefinitions.Add(new ColumnDefinition(0, GridUnitType.Auto)); |
|||
} |
|||
|
|||
_container.ColumnDefinitions.Add( |
|||
new ColumnDefinition(column.Item1 == _monthText ? 138 : 78, GridUnitType.Star)); |
|||
|
|||
if (column.Item1.Parent is null) |
|||
{ |
|||
_container.Children.Add(column.Item1); |
|||
} |
|||
|
|||
Grid.SetColumn(column.Item1, (columnIndex++ * 2)); |
|||
} |
|||
} |
|||
|
|||
Grid.SetColumn(_spacer1, 1); |
|||
Grid.SetColumn(_spacer2, 3); |
|||
_spacer1.IsVisible = columnIndex > 1; |
|||
_spacer2.IsVisible = columnIndex > 2; |
|||
} |
|||
|
|||
private void SetSelectedDateText() |
|||
{ |
|||
if (!_areControlsAvailable) |
|||
return; |
|||
|
|||
if (SelectedDate.HasValue) |
|||
{ |
|||
PseudoClasses.Set(":hasnodate", false); |
|||
var selDate = SelectedDate.Value; |
|||
_monthText.Text = selDate.ToString(MonthFormat); |
|||
_yearText.Text = selDate.ToString(YearFormat); |
|||
_dayText.Text = selDate.ToString(DayFormat); |
|||
} |
|||
else |
|||
{ |
|||
PseudoClasses.Set(":hasnodate", true); |
|||
_monthText.Text = "month"; |
|||
_yearText.Text = "year"; |
|||
_dayText.Text = "day"; |
|||
} |
|||
} |
|||
|
|||
private void OnFlyoutButtonClicked(object sender, RoutedEventArgs e) |
|||
{ |
|||
if (_presenter == null) |
|||
throw new InvalidOperationException("No DatePickerPresenter found"); |
|||
|
|||
_presenter.Date = SelectedDate ?? DateTimeOffset.Now; |
|||
|
|||
_popup.IsOpen = true; |
|||
|
|||
var deltaY = _presenter.GetOffsetForPopup(); |
|||
|
|||
//The extra 5 px I think is related to default popup placement behavior
|
|||
_popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5), |
|||
Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom, |
|||
Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY); |
|||
} |
|||
|
|||
protected virtual void OnSelectedDateChanged(object sender, DatePickerSelectedValueChangedEventArgs e) |
|||
{ |
|||
SelectedDateChanged?.Invoke(sender, e); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,531 @@ |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Controls.Shapes; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the presenter used for selecting a date for a
|
|||
/// <see cref="DatePicker"/>
|
|||
/// </summary>
|
|||
public class DatePickerPresenter : PickerPresenterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="Date"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> DateProperty = |
|||
AvaloniaProperty.RegisterDirect<DatePickerPresenter, DateTimeOffset>(nameof(Date), |
|||
x => x.Date, (x, v) => x.Date = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="DayFormat"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, string> DayFormatProperty = |
|||
DatePicker.DayFormatProperty.AddOwner<DatePickerPresenter>(x => |
|||
x.DayFormat, (x, v) => x.DayFormat = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="DayVisible"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, bool> DayVisibleProperty = |
|||
DatePicker.DayVisibleProperty.AddOwner<DatePickerPresenter>(x => |
|||
x.DayVisible, (x, v) => x.DayVisible = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MaxYear"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> MaxYearProperty = |
|||
DatePicker.MaxYearProperty.AddOwner<DatePickerPresenter>(x => |
|||
x.MaxYear, (x, v) => x.MaxYear = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MinYear"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> MinYearProperty = |
|||
DatePicker.MinYearProperty.AddOwner<DatePickerPresenter>(x => |
|||
x.MinYear, (x, v) => x.MinYear = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MonthFormat"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, string> MonthFormatProperty = |
|||
DatePicker.MonthFormatProperty.AddOwner<DatePickerPresenter>(x => |
|||
x.MonthFormat, (x, v) => x.MonthFormat = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="MonthVisible"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, bool> MonthVisibleProperty = |
|||
DatePicker.MonthVisibleProperty.AddOwner<DatePickerPresenter>(x => |
|||
x.MonthVisible, (x, v) => x.MonthVisible = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="YearFormat"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, string> YearFormatProperty = |
|||
DatePicker.YearFormatProperty.AddOwner<DatePickerPresenter>(x => |
|||
x.YearFormat, (x, v) => x.YearFormat = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="YearVisible"/> Property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<DatePickerPresenter, bool> YearVisibleProperty = |
|||
DatePicker.YearVisibleProperty.AddOwner<DatePickerPresenter>(x => |
|||
x.YearVisible, (x, v) => x.YearVisible = v); |
|||
|
|||
//Template Items
|
|||
private Grid _pickerContainer; |
|||
private Button _acceptButton; |
|||
private Button _dismissButton; |
|||
private Rectangle _spacer1; |
|||
private Rectangle _spacer2; |
|||
private Panel _monthHost; |
|||
private Panel _yearHost; |
|||
private Panel _dayHost; |
|||
private DateTimePickerPanel _monthSelector; |
|||
private DateTimePickerPanel _yearSelector; |
|||
private DateTimePickerPanel _daySelector; |
|||
private Button _monthUpButton; |
|||
private Button _dayUpButton; |
|||
private Button _yearUpButton; |
|||
private Button _monthDownButton; |
|||
private Button _dayDownButton; |
|||
private Button _yearDownButton; |
|||
|
|||
private DateTimeOffset _date; |
|||
private string _dayFormat = "%d"; |
|||
private bool _dayVisible = true; |
|||
private DateTimeOffset _maxYear; |
|||
private DateTimeOffset _minYear; |
|||
private string _monthFormat = "MMMM"; |
|||
private bool _monthVisible = true; |
|||
private string _yearFormat = "yyyy"; |
|||
private bool _yearVisible = true; |
|||
private DateTimeOffset _syncDate; |
|||
|
|||
private GregorianCalendar _calendar; |
|||
private bool _suppressUpdateSelection; |
|||
|
|||
public DatePickerPresenter() |
|||
{ |
|||
var now = DateTimeOffset.Now; |
|||
_minYear = new DateTimeOffset(now.Year - 100, 1, 1, 0, 0, 0, now.Offset); |
|||
_maxYear = new DateTimeOffset(now.Year + 100, 12, 31, 0, 0, 0, now.Offset); |
|||
_date = now; |
|||
_calendar = new GregorianCalendar(); |
|||
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the current Date for the picker
|
|||
/// </summary>
|
|||
public DateTimeOffset Date |
|||
{ |
|||
get => _date; |
|||
set |
|||
{ |
|||
SetAndRaise(DateProperty, ref _date, value); |
|||
_syncDate = Date; |
|||
InitPicker(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the DayFormat
|
|||
/// </summary>
|
|||
public string DayFormat |
|||
{ |
|||
get => _dayFormat; |
|||
set => SetAndRaise(DayFormatProperty, ref _dayFormat, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get or sets whether the Day selector is visible
|
|||
/// </summary>
|
|||
public bool DayVisible |
|||
{ |
|||
get => _dayVisible; |
|||
set |
|||
{ |
|||
SetAndRaise(DayVisibleProperty, ref _dayVisible, value); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the maximum pickable year
|
|||
/// </summary>
|
|||
public DateTimeOffset MaxYear |
|||
{ |
|||
get => _maxYear; |
|||
set |
|||
{ |
|||
if (value < MinYear) |
|||
throw new InvalidOperationException("MaxDate cannot be less than MinDate"); |
|||
SetAndRaise(MaxYearProperty, ref _maxYear, value); |
|||
|
|||
if (Date > value) |
|||
Date = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the minimum pickable year
|
|||
/// </summary>
|
|||
public DateTimeOffset MinYear |
|||
{ |
|||
get => _minYear; |
|||
set |
|||
{ |
|||
if (value > MaxYear) |
|||
throw new InvalidOperationException("MinDate cannot be greater than MaxDate"); |
|||
SetAndRaise(MinYearProperty, ref _minYear, value); |
|||
|
|||
if (Date < value) |
|||
Date = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the month format
|
|||
/// </summary>
|
|||
public string MonthFormat |
|||
{ |
|||
get => _monthFormat; |
|||
set => SetAndRaise(MonthFormatProperty, ref _monthFormat, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether the month selector is visible
|
|||
/// </summary>
|
|||
public bool MonthVisible |
|||
{ |
|||
get => _monthVisible; |
|||
set |
|||
{ |
|||
SetAndRaise(MonthVisibleProperty, ref _monthVisible, value); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the year format
|
|||
/// </summary>
|
|||
public string YearFormat |
|||
{ |
|||
get => _yearFormat; |
|||
set => SetAndRaise(YearFormatProperty, ref _yearFormat, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether the year selector is visible
|
|||
/// </summary>
|
|||
public bool YearVisible |
|||
{ |
|||
get => _yearVisible; |
|||
set |
|||
{ |
|||
SetAndRaise(YearVisibleProperty, ref _yearVisible, value); |
|||
} |
|||
} |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
base.OnApplyTemplate(e); |
|||
//These are requirements, so throw if not found
|
|||
_pickerContainer = e.NameScope.Get<Grid>("PickerContainer"); |
|||
_monthHost = e.NameScope.Get<Panel>("MonthHost"); |
|||
_dayHost = e.NameScope.Get<Panel>("DayHost"); |
|||
_yearHost = e.NameScope.Get<Panel>("YearHost"); |
|||
|
|||
_monthSelector = e.NameScope.Get<DateTimePickerPanel>("MonthSelector"); |
|||
_monthSelector.SelectionChanged += OnMonthChanged; |
|||
|
|||
_daySelector = e.NameScope.Get<DateTimePickerPanel>("DaySelector"); |
|||
_daySelector.SelectionChanged += OnDayChanged; |
|||
|
|||
_yearSelector = e.NameScope.Get<DateTimePickerPanel>("YearSelector"); |
|||
_yearSelector.SelectionChanged += OnYearChanged; |
|||
|
|||
_acceptButton = e.NameScope.Get<Button>("AcceptButton"); |
|||
|
|||
_monthUpButton = e.NameScope.Find<RepeatButton>("MonthUpButton"); |
|||
if (_monthUpButton != null) |
|||
{ |
|||
_monthUpButton.Click += OnSelectorButtonClick; |
|||
} |
|||
_monthDownButton = e.NameScope.Find<RepeatButton>("MonthDownButton"); |
|||
if (_monthDownButton != null) |
|||
{ |
|||
_monthDownButton.Click += OnSelectorButtonClick; |
|||
} |
|||
|
|||
_dayUpButton = e.NameScope.Find<RepeatButton>("DayUpButton"); |
|||
if (_dayUpButton != null) |
|||
{ |
|||
_dayUpButton.Click += OnSelectorButtonClick; |
|||
} |
|||
_dayDownButton = e.NameScope.Find<RepeatButton>("DayDownButton"); |
|||
if (_dayDownButton != null) |
|||
{ |
|||
_dayDownButton.Click += OnSelectorButtonClick; |
|||
} |
|||
|
|||
_yearUpButton = e.NameScope.Find<RepeatButton>("YearUpButton"); |
|||
if (_yearUpButton != null) |
|||
{ |
|||
_yearUpButton.Click += OnSelectorButtonClick; |
|||
} |
|||
_yearDownButton = e.NameScope.Find<RepeatButton>("YearDownButton"); |
|||
if (_yearDownButton != null) |
|||
{ |
|||
_yearDownButton.Click += OnSelectorButtonClick; |
|||
} |
|||
|
|||
_dismissButton = e.NameScope.Find<Button>("DismissButton"); |
|||
_spacer1 = e.NameScope.Find<Rectangle>("FirstSpacer"); |
|||
_spacer2 = e.NameScope.Find<Rectangle>("SecondSpacer"); |
|||
|
|||
if (_acceptButton != null) |
|||
{ |
|||
_acceptButton.Click += OnAcceptButtonClicked; |
|||
} |
|||
if (_dismissButton != null) |
|||
{ |
|||
_dismissButton.Click += OnDismissButtonClicked; |
|||
} |
|||
InitPicker(); |
|||
} |
|||
|
|||
protected override void OnKeyDown(KeyEventArgs e) |
|||
{ |
|||
switch (e.Key) |
|||
{ |
|||
case Key.Escape: |
|||
OnDismiss(); |
|||
e.Handled = true; |
|||
break; |
|||
case Key.Tab: |
|||
var nextFocus = KeyboardNavigationHandler.GetNext(FocusManager.Instance.Current, NavigationDirection.Next); |
|||
KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None); |
|||
e.Handled = true; |
|||
break; |
|||
case Key.Enter: |
|||
Date = _syncDate; |
|||
OnConfirmed(); |
|||
e.Handled = true; |
|||
break; |
|||
} |
|||
base.OnKeyDown(e); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes the picker selectors.
|
|||
/// </summary>
|
|||
private void InitPicker() |
|||
{ |
|||
//OnApplyTemplate must've been called before we can init here...
|
|||
if (_pickerContainer == null) |
|||
return; |
|||
|
|||
_suppressUpdateSelection = true; |
|||
|
|||
_monthSelector.MaximumValue = 12; |
|||
_monthSelector.MinimumValue = 1; |
|||
_monthSelector.ItemFormat = MonthFormat; |
|||
|
|||
_daySelector.ItemFormat = DayFormat; |
|||
|
|||
_yearSelector.MaximumValue = MaxYear.Year; |
|||
_yearSelector.MinimumValue = MinYear.Year; |
|||
_yearSelector.ItemFormat = YearFormat; |
|||
|
|||
SetGrid(); |
|||
|
|||
//Date should've been set when we reach this point
|
|||
var dt = Date; |
|||
if (DayVisible) |
|||
{ |
|||
GregorianCalendar gc = new GregorianCalendar(); |
|||
var maxDays = gc.GetDaysInMonth(dt.Year, dt.Month); |
|||
_daySelector.MaximumValue = maxDays; |
|||
_daySelector.MinimumValue = 1; |
|||
_daySelector.SelectedValue = dt.Day; |
|||
_daySelector.FormatDate = dt.Date; |
|||
} |
|||
|
|||
if (MonthVisible) |
|||
_monthSelector.SelectedValue = dt.Month; |
|||
|
|||
if (YearVisible) |
|||
_yearSelector.SelectedValue = dt.Year; |
|||
_suppressUpdateSelection = false; |
|||
|
|||
SetInitialFocus(); |
|||
} |
|||
|
|||
private void SetGrid() |
|||
{ |
|||
var fmt = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern; |
|||
var columns = new List<(Panel, int)> |
|||
{ |
|||
(_monthHost, MonthVisible ? fmt.IndexOf("m", StringComparison.OrdinalIgnoreCase) : -1), |
|||
(_yearHost, YearVisible ? fmt.IndexOf("y", StringComparison.OrdinalIgnoreCase) : -1), |
|||
(_dayHost, DayVisible ? fmt.IndexOf("d", StringComparison.OrdinalIgnoreCase) : -1), |
|||
}; |
|||
|
|||
columns.Sort((x, y) => x.Item2 - y.Item2); |
|||
_pickerContainer.ColumnDefinitions.Clear(); |
|||
|
|||
var columnIndex = 0; |
|||
|
|||
foreach (var column in columns) |
|||
{ |
|||
if (column.Item1 is null) |
|||
continue; |
|||
|
|||
column.Item1.IsVisible = column.Item2 != -1; |
|||
|
|||
if (column.Item2 != -1) |
|||
{ |
|||
if (columnIndex > 0) |
|||
{ |
|||
_pickerContainer.ColumnDefinitions.Add(new ColumnDefinition(0, GridUnitType.Auto)); |
|||
} |
|||
|
|||
_pickerContainer.ColumnDefinitions.Add( |
|||
new ColumnDefinition(column.Item1 == _monthHost ? 138 : 78, GridUnitType.Star)); |
|||
|
|||
if (column.Item1.Parent is null) |
|||
{ |
|||
_pickerContainer.Children.Add(column.Item1); |
|||
} |
|||
|
|||
Grid.SetColumn(column.Item1, (columnIndex++ * 2)); |
|||
} |
|||
} |
|||
|
|||
Grid.SetColumn(_spacer1, 1); |
|||
Grid.SetColumn(_spacer2, 3); |
|||
_spacer1.IsVisible = columnIndex > 1; |
|||
_spacer2.IsVisible = columnIndex > 2; |
|||
} |
|||
|
|||
private void SetInitialFocus() |
|||
{ |
|||
int monthCol = MonthVisible ? Grid.GetColumn(_monthHost) : int.MaxValue; |
|||
int dayCol = DayVisible ? Grid.GetColumn(_dayHost) : int.MaxValue; |
|||
int yearCol = YearVisible ? Grid.GetColumn(_yearHost) : int.MaxValue; |
|||
|
|||
if (monthCol < dayCol && monthCol < yearCol) |
|||
{ |
|||
KeyboardDevice.Instance?.SetFocusedElement(_monthSelector, NavigationMethod.Pointer, KeyModifiers.None); |
|||
} |
|||
else if (dayCol < monthCol && dayCol < yearCol) |
|||
{ |
|||
KeyboardDevice.Instance?.SetFocusedElement(_daySelector, NavigationMethod.Pointer, KeyModifiers.None); |
|||
} |
|||
else if (yearCol < monthCol && yearCol < dayCol) |
|||
{ |
|||
KeyboardDevice.Instance?.SetFocusedElement(_yearSelector, NavigationMethod.Pointer, KeyModifiers.None); |
|||
} |
|||
} |
|||
|
|||
private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) |
|||
{ |
|||
OnDismiss(); |
|||
} |
|||
|
|||
private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) |
|||
{ |
|||
Date = _syncDate; |
|||
OnConfirmed(); |
|||
} |
|||
|
|||
private void OnSelectorButtonClick(object sender, RoutedEventArgs e) |
|||
{ |
|||
if (sender == _monthUpButton) |
|||
_monthSelector.ScrollUp(); |
|||
else if (sender == _monthDownButton) |
|||
_monthSelector.ScrollDown(); |
|||
else if (sender == _yearUpButton) |
|||
_yearSelector.ScrollUp(); |
|||
else if (sender == _yearDownButton) |
|||
_yearSelector.ScrollDown(); |
|||
else if (sender == _dayUpButton) |
|||
_daySelector.ScrollUp(); |
|||
else if (sender == _dayDownButton) |
|||
_daySelector.ScrollDown(); |
|||
} |
|||
|
|||
private void OnYearChanged(object sender, EventArgs e) |
|||
{ |
|||
if (_suppressUpdateSelection) |
|||
return; |
|||
|
|||
int maxDays = _calendar.GetDaysInMonth(_yearSelector.SelectedValue, _syncDate.Month); |
|||
var newDate = new DateTimeOffset(_yearSelector.SelectedValue, _syncDate.Month, |
|||
_syncDate.Day > maxDays ? maxDays : _syncDate.Day, 0, 0, 0, _syncDate.Offset); |
|||
|
|||
_syncDate = newDate; |
|||
|
|||
//We don't need to update the days if not displaying day, not february
|
|||
if (!DayVisible || _syncDate.Month != 2) |
|||
return; |
|||
|
|||
_suppressUpdateSelection = true; |
|||
|
|||
_daySelector.FormatDate = newDate.Date; |
|||
|
|||
if (_daySelector.MaximumValue != maxDays) |
|||
_daySelector.MaximumValue = maxDays; |
|||
else |
|||
_daySelector.RefreshItems(); |
|||
|
|||
_suppressUpdateSelection = false; |
|||
} |
|||
|
|||
private void OnDayChanged(object sender, EventArgs e) |
|||
{ |
|||
if (_suppressUpdateSelection) |
|||
return; |
|||
_syncDate = new DateTimeOffset(_syncDate.Year, _syncDate.Month, _daySelector.SelectedValue, 0, 0, 0, _syncDate.Offset); |
|||
} |
|||
|
|||
private void OnMonthChanged(object sender, EventArgs e) |
|||
{ |
|||
if (_suppressUpdateSelection) |
|||
return; |
|||
|
|||
int maxDays = _calendar.GetDaysInMonth(_syncDate.Year, _monthSelector.SelectedValue); |
|||
var newDate = new DateTimeOffset(_syncDate.Year, _monthSelector.SelectedValue, |
|||
_syncDate.Day > maxDays ? maxDays : _syncDate.Day, 0, 0, 0, _syncDate.Offset); |
|||
|
|||
if (!DayVisible) |
|||
{ |
|||
_syncDate = newDate; |
|||
return; |
|||
} |
|||
|
|||
_suppressUpdateSelection = true; |
|||
|
|||
_daySelector.FormatDate = newDate.Date; |
|||
_syncDate = newDate; |
|||
|
|||
if (_daySelector.MaximumValue != maxDays) |
|||
_daySelector.MaximumValue = maxDays; |
|||
else |
|||
_daySelector.RefreshItems(); |
|||
|
|||
_suppressUpdateSelection = false; |
|||
} |
|||
|
|||
internal double GetOffsetForPopup() |
|||
{ |
|||
var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41; |
|||
return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_monthSelector.ItemHeight / 2); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the argument passed when the <see cref="DatePicker"/> SelectedDate changes
|
|||
/// </summary>
|
|||
public class DatePickerSelectedValueChangedEventArgs |
|||
{ |
|||
public DateTimeOffset? NewDate { get; } |
|||
public DateTimeOffset? OldDate { get; } |
|||
|
|||
public DatePickerSelectedValueChangedEventArgs(DateTimeOffset? oldDate, DateTimeOffset? newDate) |
|||
{ |
|||
NewDate = newDate; |
|||
OldDate = oldDate; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,566 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using Avalonia.Input; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
public enum DateTimePickerPanelType |
|||
{ |
|||
Year, |
|||
Month, |
|||
Day, |
|||
Hour, |
|||
Minute, |
|||
TimePeriod //AM or PM
|
|||
} |
|||
|
|||
public class DateTimePickerPanel : Panel, ILogicalScrollable |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="ItemHeight"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<double> ItemHeightProperty = |
|||
AvaloniaProperty.Register<DateTimePickerPanel, double>(nameof(ItemHeight), 40.0); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="PanelType"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<DateTimePickerPanelType> PanelTypeProperty = |
|||
AvaloniaProperty.Register<DateTimePickerPanel, DateTimePickerPanelType>(nameof(PanelType)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ItemFormat"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<string> ItemFormatProperty = |
|||
AvaloniaProperty.Register<DateTimePickerPanel, string>(nameof(ItemFormat), "yyyy"); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ShouldLoop"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<bool> ShouldLoopProperty = |
|||
AvaloniaProperty.Register<DateTimePickerPanel, bool>(nameof(ShouldLoop)); |
|||
|
|||
//Backing fields for properties
|
|||
private int _minimumValue = 1; |
|||
private int _maximumValue = 2; |
|||
private int _selectedValue = 1; |
|||
private int _increment = 1; |
|||
|
|||
//Helper fields
|
|||
private int _selectedIndex = 0; |
|||
private int _totalItems; |
|||
private int _numItemsAboveBelowSelected; |
|||
private int _range; |
|||
private double _extentOne; |
|||
private Size _extent; |
|||
private Vector _offset; |
|||
private bool _hasInit; |
|||
private bool _suppressUpdateOffset; |
|||
private ListBoxItem _pressedItem; |
|||
|
|||
public DateTimePickerPanel() |
|||
{ |
|||
FormatDate = DateTime.Now; |
|||
AddHandler(ListBoxItem.PointerPressedEvent, OnItemPointerDown, Avalonia.Interactivity.RoutingStrategies.Bubble); |
|||
AddHandler(ListBoxItem.PointerReleasedEvent, OnItemPointerUp, Avalonia.Interactivity.RoutingStrategies.Bubble); |
|||
} |
|||
|
|||
static DateTimePickerPanel() |
|||
{ |
|||
FocusableProperty.OverrideDefaultValue<DateTimePickerPanel>(true); |
|||
AffectsMeasure<DateTimePickerPanel>(ItemHeightProperty); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets what this panel displays in date or time units
|
|||
/// </summary>
|
|||
public DateTimePickerPanelType PanelType |
|||
{ |
|||
get => GetValue(PanelTypeProperty); |
|||
set => SetValue(PanelTypeProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the height of each item
|
|||
/// </summary>
|
|||
public double ItemHeight |
|||
{ |
|||
get => GetValue(ItemHeightProperty); |
|||
set => SetValue(ItemHeightProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the string format for the items, using standard
|
|||
/// .net DateTime or TimeSpan formatting. Format must match panel type
|
|||
/// </summary>
|
|||
public string ItemFormat |
|||
{ |
|||
get => GetValue(ItemFormatProperty); |
|||
set => SetValue(ItemFormatProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether the panel should loop
|
|||
/// </summary>
|
|||
public bool ShouldLoop |
|||
{ |
|||
get => GetValue(ShouldLoopProperty); |
|||
set => SetValue(ShouldLoopProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the minimum value
|
|||
/// </summary>
|
|||
public int MinimumValue |
|||
{ |
|||
get => _minimumValue; |
|||
set |
|||
{ |
|||
if (value > MaximumValue) |
|||
throw new InvalidOperationException("Minimum cannot be greater than Maximum"); |
|||
_minimumValue = value; |
|||
UpdateHelperInfo(); |
|||
var sel = CoerceSelected(SelectedValue); |
|||
if (sel != SelectedValue) |
|||
SelectedValue = sel; |
|||
UpdateItems(); |
|||
InvalidateArrange(); |
|||
RaiseScrollInvalidated(EventArgs.Empty); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the maximum value
|
|||
/// </summary>
|
|||
public int MaximumValue |
|||
{ |
|||
get => _maximumValue; |
|||
set |
|||
{ |
|||
if (value < MinimumValue) |
|||
throw new InvalidOperationException("Maximum cannot be less than Minimum"); |
|||
_maximumValue = value; |
|||
UpdateHelperInfo(); |
|||
var sel = CoerceSelected(SelectedValue); |
|||
if (sel != SelectedValue) |
|||
SelectedValue = sel; |
|||
UpdateItems(); |
|||
InvalidateArrange(); |
|||
RaiseScrollInvalidated(EventArgs.Empty); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the selected value
|
|||
/// </summary>
|
|||
public int SelectedValue |
|||
{ |
|||
get => _selectedValue; |
|||
set |
|||
{ |
|||
if (value > MaximumValue || value < MinimumValue) |
|||
throw new ArgumentOutOfRangeException("SelectedValue"); |
|||
|
|||
var sel = CoerceSelected(value); |
|||
_selectedValue = sel; |
|||
_selectedIndex = (value - MinimumValue) / Increment; |
|||
|
|||
if (!ShouldLoop) |
|||
CreateOrDestroyItems(Children); |
|||
|
|||
if (!_suppressUpdateOffset) |
|||
_offset = new Vector(0, ShouldLoop ? _selectedIndex * ItemHeight + (_extentOne * 50) : |
|||
_selectedIndex * ItemHeight); |
|||
|
|||
UpdateItems(); |
|||
InvalidateArrange(); |
|||
RaiseScrollInvalidated(EventArgs.Empty); |
|||
|
|||
SelectionChanged?.Invoke(this, EventArgs.Empty); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the increment
|
|||
/// </summary>
|
|||
public int Increment |
|||
{ |
|||
get => _increment; |
|||
set |
|||
{ |
|||
if (value <= 0 || value > _range) |
|||
throw new ArgumentOutOfRangeException("Increment"); |
|||
_increment = value; |
|||
UpdateHelperInfo(); |
|||
var sel = CoerceSelected(SelectedValue); |
|||
if (sel != SelectedValue) |
|||
SelectedValue = sel; |
|||
UpdateItems(); |
|||
InvalidateArrange(); |
|||
RaiseScrollInvalidated(EventArgs.Empty); |
|||
} |
|||
} |
|||
|
|||
//Used to help format the date (if applicable), for ex.,
|
|||
//if we're want to display the day of week, we need context
|
|||
//for the month/year, this is our context
|
|||
internal DateTime FormatDate { get; set; } |
|||
|
|||
public Vector Offset |
|||
{ |
|||
get => _offset; |
|||
set |
|||
{ |
|||
var old = _offset; |
|||
_offset = value; |
|||
var dy = _offset.Y - old.Y; |
|||
var children = Children; |
|||
|
|||
if (dy > 0) // Scroll Down
|
|||
{ |
|||
int numContsToMove = 0; |
|||
for (int i = 0; i < children.Count; i++) |
|||
{ |
|||
if (children[i].Bounds.Bottom - dy < 0) |
|||
numContsToMove++; |
|||
else |
|||
break; |
|||
} |
|||
children.MoveRange(0, numContsToMove, children.Count); |
|||
|
|||
var scrollHeight = _extent.Height - Viewport.Height; |
|||
if (ShouldLoop && value.Y >= scrollHeight - _extentOne) |
|||
_offset = new Vector(0, value.Y - (_extentOne * 50)); |
|||
} |
|||
else if (dy < 0) // Scroll Up
|
|||
{ |
|||
int numContsToMove = 0; |
|||
for (int i = children.Count - 1; i >= 0; i--) |
|||
{ |
|||
if (children[i].Bounds.Top - dy > Bounds.Height) |
|||
numContsToMove++; |
|||
else |
|||
break; |
|||
} |
|||
children.MoveRange(children.Count - numContsToMove, numContsToMove, 0); |
|||
if (ShouldLoop && value.Y < _extentOne) |
|||
_offset = new Vector(0, value.Y + (_extentOne * 50)); |
|||
} |
|||
|
|||
//Setting selection will handle all invalidation
|
|||
var newSel = (Offset.Y / ItemHeight) % _totalItems; |
|||
_suppressUpdateOffset = true; |
|||
SelectedValue = (int)newSel * Increment + MinimumValue; |
|||
_suppressUpdateOffset = false; |
|||
} |
|||
} |
|||
|
|||
public bool CanHorizontallyScroll { get => false; set { } } |
|||
|
|||
public bool CanVerticallyScroll { get => true; set { } } |
|||
|
|||
public bool IsLogicalScrollEnabled => true; |
|||
|
|||
public Size ScrollSize => new Size(0, ItemHeight); |
|||
|
|||
public Size PageScrollSize => new Size(0, ItemHeight * 4); |
|||
|
|||
public Size Extent => _extent; |
|||
|
|||
public Size Viewport => new Size(0, ItemHeight); |
|||
|
|||
public event EventHandler ScrollInvalidated; |
|||
|
|||
public event EventHandler SelectionChanged; |
|||
|
|||
protected override Size MeasureOverride(Size availableSize) |
|||
{ |
|||
if (double.IsInfinity(availableSize.Width) || |
|||
double.IsInfinity(availableSize.Height)) |
|||
throw new InvalidOperationException("Panel must have finite height"); |
|||
|
|||
if (!_hasInit) |
|||
UpdateHelperInfo(); |
|||
|
|||
double initY = (availableSize.Height / 2.0) - (ItemHeight / 2.0); |
|||
_numItemsAboveBelowSelected = (int)Math.Ceiling(initY / ItemHeight) + 1; |
|||
|
|||
var children = Children; |
|||
|
|||
CreateOrDestroyItems(children); |
|||
|
|||
for (int i = 0; i < children.Count; i++) |
|||
children[i].Measure(availableSize); |
|||
|
|||
if (!_hasInit) |
|||
{ |
|||
UpdateItems(); |
|||
RaiseScrollInvalidated(EventArgs.Empty); |
|||
_hasInit = true; |
|||
} |
|||
|
|||
return availableSize; |
|||
} |
|||
|
|||
protected override Size ArrangeOverride(Size finalSize) |
|||
{ |
|||
if (Children.Count == 0) |
|||
return base.ArrangeOverride(finalSize); |
|||
|
|||
var itemHgt = ItemHeight; |
|||
var children = Children; |
|||
Rect rc; |
|||
double initY = (finalSize.Height / 2.0) - (itemHgt / 2.0); |
|||
|
|||
if (ShouldLoop) |
|||
{ |
|||
var currentSet = Math.Truncate(Offset.Y / _extentOne); |
|||
initY += (_extentOne * currentSet) + (_selectedIndex - _numItemsAboveBelowSelected) * ItemHeight; |
|||
|
|||
for (int i = 0; i < children.Count; i++) |
|||
{ |
|||
rc = new Rect(0, initY - Offset.Y, finalSize.Width, itemHgt); |
|||
children[i].Arrange(rc); |
|||
initY += itemHgt; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
var first = Math.Max(0, (_selectedIndex - _numItemsAboveBelowSelected)); |
|||
for (int i = 0; i < children.Count; i++) |
|||
{ |
|||
rc = new Rect(0, (initY + first * itemHgt) - Offset.Y, finalSize.Width, itemHgt); |
|||
children[i].Arrange(rc); |
|||
initY += itemHgt; |
|||
} |
|||
} |
|||
|
|||
return finalSize; |
|||
} |
|||
|
|||
protected override void OnKeyDown(KeyEventArgs e) |
|||
{ |
|||
switch (e.Key) |
|||
{ |
|||
case Key.Up: |
|||
ScrollUp(); |
|||
e.Handled = true; |
|||
break; |
|||
case Key.Down: |
|||
ScrollDown(); |
|||
e.Handled = true; |
|||
break; |
|||
case Key.PageUp: |
|||
ScrollUp(4); |
|||
e.Handled = true; |
|||
break; |
|||
case Key.PageDown: |
|||
ScrollDown(4); |
|||
e.Handled = true; |
|||
break; |
|||
} |
|||
base.OnKeyDown(e); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Refreshes the content of the visible items
|
|||
/// </summary>
|
|||
public void RefreshItems() |
|||
{ |
|||
UpdateItems(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Scrolls up the specified number of items
|
|||
/// </summary>
|
|||
public void ScrollUp(int numItems = 1) |
|||
{ |
|||
var newY = Math.Max(Offset.Y - (numItems * ItemHeight), 0); |
|||
Offset = new Vector(0, newY); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Scrolls down the specified number of items
|
|||
/// </summary>
|
|||
public void ScrollDown(int numItems = 1) |
|||
{ |
|||
var scrollHeight = _extent.Height - Viewport.Height; |
|||
var newY = Math.Min(Offset.Y + (numItems * ItemHeight), scrollHeight); |
|||
Offset = new Vector(0, newY); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Updates helper fields used in various calculations
|
|||
/// </summary>
|
|||
private void UpdateHelperInfo() |
|||
{ |
|||
_range = _maximumValue - _minimumValue + 1; |
|||
_totalItems = (int)Math.Ceiling((double)_range / _increment); |
|||
|
|||
var itemHgt = ItemHeight; |
|||
//If looping, measure 100x as many items as we actually have
|
|||
_extent = new Size(0, ShouldLoop ? _totalItems * itemHgt * 100 : _totalItems * itemHgt); |
|||
|
|||
//Height of 1 "set" of items
|
|||
_extentOne = _totalItems * itemHgt; |
|||
_offset = new Vector(0, ShouldLoop ? _extentOne * 50 + _selectedIndex * itemHgt : _selectedIndex * itemHgt); |
|||
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Ensures enough containers are visible in the viewport
|
|||
/// </summary>
|
|||
/// <param name="children"></param>
|
|||
private void CreateOrDestroyItems(Controls children) |
|||
{ |
|||
int totalItemsInViewport = _numItemsAboveBelowSelected * 2 + 1; |
|||
|
|||
if (!ShouldLoop) |
|||
{ |
|||
int numItemAboveSelect = _numItemsAboveBelowSelected; |
|||
if (_selectedIndex - _numItemsAboveBelowSelected < 0) |
|||
numItemAboveSelect = _selectedIndex; |
|||
int numItemBelowSelect = _numItemsAboveBelowSelected; |
|||
if (_selectedIndex + _numItemsAboveBelowSelected >= _totalItems) |
|||
numItemBelowSelect = _totalItems - _selectedIndex - 1; |
|||
|
|||
totalItemsInViewport = numItemBelowSelect + numItemAboveSelect + 1; |
|||
} |
|||
|
|||
while (children.Count < totalItemsInViewport) |
|||
{ |
|||
children.Add(new ListBoxItem |
|||
{ |
|||
Height = ItemHeight, |
|||
Classes = new Classes("DateTimePickerItem", $"{PanelType}Item"), |
|||
VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Center, |
|||
Focusable = false |
|||
}); |
|||
} |
|||
if (children.Count > totalItemsInViewport) |
|||
{ |
|||
var numToRemove = children.Count - totalItemsInViewport; |
|||
children.RemoveRange(children.Count - numToRemove, numToRemove); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Updates item content based on the current selection
|
|||
/// and the panel type
|
|||
/// </summary>
|
|||
private void UpdateItems() |
|||
{ |
|||
var children = Children; |
|||
var min = MinimumValue; |
|||
var panelType = PanelType; |
|||
var selected = SelectedValue; |
|||
var max = MaximumValue; |
|||
|
|||
int first; |
|||
if (ShouldLoop) |
|||
{ |
|||
first = (_selectedIndex - _numItemsAboveBelowSelected) % _totalItems; |
|||
first = first < 0 ? min + (first + _totalItems) * Increment : min + first * Increment; |
|||
} |
|||
else |
|||
{ |
|||
first = min + Math.Max(0, _selectedIndex - _numItemsAboveBelowSelected) * Increment; |
|||
} |
|||
|
|||
for (int i = 0; i < children.Count; i++) |
|||
{ |
|||
ListBoxItem item = (ListBoxItem)children[i]; |
|||
item.Content = FormatContent(first, panelType); |
|||
item.Tag = first; |
|||
item.IsSelected = first == selected; |
|||
first += Increment; |
|||
if (first > max) |
|||
first = min; |
|||
} |
|||
} |
|||
|
|||
private string FormatContent(int value, DateTimePickerPanelType panelType) |
|||
{ |
|||
switch (panelType) |
|||
{ |
|||
case DateTimePickerPanelType.Year: |
|||
return new DateTime(value, FormatDate.Month, FormatDate.Day).ToString(ItemFormat); |
|||
case DateTimePickerPanelType.Month: |
|||
return new DateTime(FormatDate.Year, value, FormatDate.Day).ToString(ItemFormat); |
|||
case DateTimePickerPanelType.Day: |
|||
return new DateTime(FormatDate.Year, FormatDate.Month, value).ToString(ItemFormat); |
|||
case DateTimePickerPanelType.Hour: |
|||
return new TimeSpan(value, 0, 0).ToString(ItemFormat); |
|||
case DateTimePickerPanelType.Minute: |
|||
return new TimeSpan(0, value, 0).ToString(ItemFormat); |
|||
case DateTimePickerPanelType.TimePeriod: |
|||
return value == MinimumValue ? CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator : |
|||
CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator; |
|||
default: |
|||
return ""; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Ensures the <see cref="SelectedValue"/> is within the bounds and
|
|||
/// follows the current Increment
|
|||
/// </summary>
|
|||
private int CoerceSelected(int newValue) |
|||
{ |
|||
if (newValue < MinimumValue) |
|||
return MinimumValue; |
|||
if (newValue > MaximumValue) |
|||
return MaximumValue; |
|||
|
|||
if (newValue % Increment != 0) |
|||
{ |
|||
var items = Enumerable.Range(MinimumValue, MaximumValue + 1).Where(i => i % Increment == 0).ToList(); |
|||
var nearest = items.Aggregate((x, y) => Math.Abs(x - newValue) > Math.Abs(y - newValue) ? y : x); |
|||
return items.IndexOf(nearest) * Increment; |
|||
} |
|||
return newValue; |
|||
} |
|||
|
|||
private void OnItemPointerDown(object sender, PointerPressedEventArgs e) |
|||
{ |
|||
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) |
|||
{ |
|||
_pressedItem = GetItemFromSource((IVisual)e.Source); |
|||
e.Handled = true; |
|||
} |
|||
} |
|||
|
|||
private void OnItemPointerUp(object sender, PointerReleasedEventArgs e) |
|||
{ |
|||
if (e.GetCurrentPoint(this).Properties.PointerUpdateKind == PointerUpdateKind.LeftButtonReleased && |
|||
_pressedItem != null) |
|||
{ |
|||
SelectedValue = (int)GetItemFromSource((IVisual)e.Source).Tag; |
|||
_pressedItem = null; |
|||
e.Handled = true; |
|||
} |
|||
} |
|||
|
|||
//Helper to get ListBoxItem from pointerevent source
|
|||
private ListBoxItem GetItemFromSource(IVisual src) |
|||
{ |
|||
var item = src; |
|||
while (item != null && !(item is ListBoxItem)) |
|||
{ |
|||
item = item.VisualParent; |
|||
} |
|||
return (ListBoxItem)item; |
|||
} |
|||
|
|||
public bool BringIntoView(IControl target, Rect targetRect) { return false; } |
|||
|
|||
public IControl GetControlInDirection(NavigationDirection direction, IControl from) { return null; } |
|||
|
|||
public void RaiseScrollInvalidated(EventArgs e) |
|||
{ |
|||
ScrollInvalidated?.Invoke(this, e); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the base class for Date and Time PickerPresenters
|
|||
/// </summary>
|
|||
public abstract class PickerPresenterBase : TemplatedControl |
|||
{ |
|||
protected virtual void OnConfirmed() |
|||
{ |
|||
Confirmed?.Invoke(this, EventArgs.Empty); |
|||
} |
|||
|
|||
protected virtual void OnDismiss() |
|||
{ |
|||
Dismissed.Invoke(this, EventArgs.Empty); |
|||
} |
|||
|
|||
public event EventHandler Confirmed; |
|||
public event EventHandler Dismissed; |
|||
} |
|||
} |
|||
@ -0,0 +1,292 @@ |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Controls.Shapes; |
|||
using Avalonia.Controls.Templates; |
|||
using System; |
|||
using System.Globalization; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// A control to allow the user to select a time
|
|||
/// </summary>
|
|||
public class TimePicker : TemplatedControl |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="MinuteIncrement"/> property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<TimePicker, int> MinuteIncrementProperty = |
|||
AvaloniaProperty.RegisterDirect<TimePicker, int>(nameof(MinuteIncrement), |
|||
x => x.MinuteIncrement, (x, v) => x.MinuteIncrement = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Header"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<object> HeaderProperty = |
|||
AvaloniaProperty.Register<DatePicker, object>(nameof(Header)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="HeaderTemplate"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty = |
|||
AvaloniaProperty.Register<DatePicker, IDataTemplate>(nameof(HeaderTemplate)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ClockIdentifier"/> property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<TimePicker, string> ClockIdentifierProperty = |
|||
AvaloniaProperty.RegisterDirect<TimePicker, string>(nameof(ClockIdentifier), |
|||
x => x.ClockIdentifier, (x, v) => x.ClockIdentifier = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="SelectedTime"/> property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<TimePicker, TimeSpan?> SelectedTimeProperty = |
|||
AvaloniaProperty.RegisterDirect<TimePicker, TimeSpan?>(nameof(SelectedTime), |
|||
x => x.SelectedTime, (x, v) => x.SelectedTime = v); |
|||
|
|||
//Template Items
|
|||
private TimePickerPresenter _presenter; |
|||
private Button _flyoutButton; |
|||
private Border _firstPickerHost; |
|||
private Border _secondPickerHost; |
|||
private Border _thirdPickerHost; |
|||
private TextBlock _hourText; |
|||
private TextBlock _minuteText; |
|||
public TextBlock _periodText; |
|||
private Rectangle _firstSplitter; |
|||
private Rectangle _secondSplitter; |
|||
private Grid _contentGrid; |
|||
private Popup _popup; |
|||
|
|||
private TimeSpan? _selectedTime; |
|||
private int _minuteIncrement = 1; |
|||
private string _clockIdentifier = "12HourClock"; |
|||
|
|||
public TimePicker() |
|||
{ |
|||
PseudoClasses.Set(":hasnotime", true); |
|||
|
|||
var timePattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern; |
|||
if (timePattern.IndexOf("H") != -1) |
|||
_clockIdentifier = "24HourClock"; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the minute increment in the picker
|
|||
/// </summary>
|
|||
public int MinuteIncrement |
|||
{ |
|||
get => _minuteIncrement; |
|||
set |
|||
{ |
|||
if (value < 1 || value > 59) |
|||
throw new ArgumentOutOfRangeException("1 >= MinuteIncrement <= 59"); |
|||
SetAndRaise(MinuteIncrementProperty, ref _minuteIncrement, value); |
|||
SetSelectedTimeText(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the header
|
|||
/// </summary>
|
|||
public object Header |
|||
{ |
|||
get => GetValue(HeaderProperty); |
|||
set => SetValue(HeaderProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the header template
|
|||
/// </summary>
|
|||
public IDataTemplate HeaderTemplate |
|||
{ |
|||
get => GetValue(HeaderTemplateProperty); |
|||
set => SetValue(HeaderTemplateProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the clock identifier, either 12HourClock or 24HourClock
|
|||
/// </summary>
|
|||
public string ClockIdentifier |
|||
{ |
|||
get => _clockIdentifier; |
|||
set |
|||
{ |
|||
if (!(string.IsNullOrEmpty(value) || value == "" || value == "12HourClock" || value == "24HourClock")) |
|||
throw new ArgumentException("Invalid ClockIdentifier"); |
|||
SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value); |
|||
SetGrid(); |
|||
SetSelectedTimeText(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the selected time. Can be null.
|
|||
/// </summary>
|
|||
public TimeSpan? SelectedTime |
|||
{ |
|||
get => _selectedTime; |
|||
set |
|||
{ |
|||
var old = _selectedTime; |
|||
SetAndRaise(SelectedTimeProperty, ref _selectedTime, value); |
|||
OnSelectedTimeChanged(old, value); |
|||
SetSelectedTimeText(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Raised when the <see cref="SelectedTime"/> property changes
|
|||
/// </summary>
|
|||
public event EventHandler<TimePickerSelectedValueChangedEventArgs> SelectedTimeChanged; |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
if (_flyoutButton != null) |
|||
_flyoutButton.Click -= OnFlyoutButtonClicked; |
|||
|
|||
if(_presenter != null) |
|||
{ |
|||
_presenter.Confirmed -= OnConfirmed; |
|||
_presenter.Dismissed -= OnDismissPicker; |
|||
} |
|||
base.OnApplyTemplate(e); |
|||
|
|||
_flyoutButton = e.NameScope.Find<Button>("FlyoutButton"); |
|||
|
|||
_firstPickerHost = e.NameScope.Find<Border>("FirstPickerHost"); |
|||
_secondPickerHost = e.NameScope.Find<Border>("SecondPickerHost"); |
|||
_thirdPickerHost = e.NameScope.Find<Border>("ThirdPickerHost"); |
|||
|
|||
_hourText = e.NameScope.Find<TextBlock>("HourTextBlock"); |
|||
_minuteText = e.NameScope.Find<TextBlock>("MinuteTextBlock"); |
|||
_periodText = e.NameScope.Find<TextBlock>("PeriodTextBlock"); |
|||
|
|||
_firstSplitter = e.NameScope.Find<Rectangle>("FirstColumnDivider"); |
|||
_secondSplitter = e.NameScope.Find<Rectangle>("SecondColumnDivider"); |
|||
|
|||
_contentGrid = e.NameScope.Find<Grid>("FlyoutButtonContentGrid"); |
|||
|
|||
_popup = e.NameScope.Find<Popup>("Popup"); |
|||
_presenter = e.NameScope.Find<TimePickerPresenter>("PickerPresenter"); |
|||
|
|||
|
|||
if (_flyoutButton != null) |
|||
_flyoutButton.Click += OnFlyoutButtonClicked; |
|||
|
|||
SetGrid(); |
|||
SetSelectedTimeText(); |
|||
|
|||
if (_presenter != null) |
|||
{ |
|||
_presenter.Confirmed += OnConfirmed; |
|||
_presenter.Dismissed += OnDismissPicker; |
|||
|
|||
_presenter[!TimePickerPresenter.MinuteIncrementProperty] = this[!MinuteIncrementProperty]; |
|||
_presenter[!TimePickerPresenter.ClockIdentifierProperty] = this[!ClockIdentifierProperty]; |
|||
} |
|||
|
|||
} |
|||
|
|||
private void SetGrid() |
|||
{ |
|||
if (_contentGrid == null) |
|||
return; |
|||
|
|||
bool use24HourClock = ClockIdentifier == "24HourClock"; |
|||
|
|||
if (!use24HourClock) |
|||
{ |
|||
_contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*"); |
|||
_thirdPickerHost.IsVisible = true; |
|||
_secondSplitter.IsVisible = true; |
|||
|
|||
Grid.SetColumn(_firstPickerHost, 0); |
|||
Grid.SetColumn(_secondPickerHost, 2); |
|||
Grid.SetColumn(_thirdPickerHost, 4); |
|||
|
|||
Grid.SetColumn(_firstSplitter, 1); |
|||
Grid.SetColumn(_secondSplitter, 3); |
|||
} |
|||
else |
|||
{ |
|||
_contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*"); |
|||
_thirdPickerHost.IsVisible = false; |
|||
_secondSplitter.IsVisible = false; |
|||
|
|||
Grid.SetColumn(_firstPickerHost, 0); |
|||
Grid.SetColumn(_secondPickerHost, 2); |
|||
|
|||
Grid.SetColumn(_firstSplitter, 1); |
|||
} |
|||
} |
|||
|
|||
private void SetSelectedTimeText() |
|||
{ |
|||
if (_hourText == null || _minuteText == null || _periodText == null) |
|||
return; |
|||
|
|||
var time = SelectedTime; |
|||
if (time.HasValue) |
|||
{ |
|||
var newTime = SelectedTime.Value; |
|||
|
|||
if (ClockIdentifier == "12HourClock") |
|||
{ |
|||
var hr = newTime.Hours; |
|||
hr = hr > 12 ? hr - 12 : hr == 0 ? 12 : hr; |
|||
newTime = new TimeSpan(hr, newTime.Minutes, 0); |
|||
} |
|||
_hourText.Text = newTime.ToString("%h"); |
|||
|
|||
_minuteText.Text = newTime.ToString("mm"); |
|||
PseudoClasses.Set(":hasnotime", false); |
|||
|
|||
_periodText.Text = time.Value.Hours >= 12 ? CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator : |
|||
CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator; |
|||
|
|||
} |
|||
else |
|||
{ |
|||
_hourText.Text = "hour"; |
|||
_minuteText.Text = "minute"; |
|||
PseudoClasses.Set(":hasnotime", true); |
|||
|
|||
_periodText.Text = DateTime.Now.Hour >= 12 ? CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator : |
|||
CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator; |
|||
} |
|||
} |
|||
|
|||
protected virtual void OnSelectedTimeChanged(TimeSpan? oldTime, TimeSpan? newTime) |
|||
{ |
|||
SelectedTimeChanged?.Invoke(this, new TimePickerSelectedValueChangedEventArgs(oldTime, newTime)); |
|||
} |
|||
|
|||
private void OnFlyoutButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) |
|||
{ |
|||
_presenter.Time = SelectedTime ?? DateTime.Now.TimeOfDay; |
|||
|
|||
_popup.IsOpen = true; |
|||
|
|||
var deltaY = _presenter.GetOffsetForPopup(); |
|||
|
|||
//The extra 5 px I think is related to default popup placement behavior
|
|||
_popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5), |
|||
Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom, |
|||
Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY); |
|||
} |
|||
|
|||
private void OnDismissPicker(object sender, EventArgs e) |
|||
{ |
|||
_popup.Close(); |
|||
Focus(); |
|||
} |
|||
|
|||
private void OnConfirmed(object sender, EventArgs e) |
|||
{ |
|||
_popup.Close(); |
|||
SelectedTime = _presenter.Time; |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,262 @@ |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Controls.Shapes; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the presenter used for selecting a time. Intended for use with
|
|||
/// <see cref="TimePicker"/> but can be used independently
|
|||
/// </summary>
|
|||
public class TimePickerPresenter : PickerPresenterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="MinuteIncrement"/> property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<TimePickerPresenter, int> MinuteIncrementProperty = |
|||
TimePicker.MinuteIncrementProperty.AddOwner<TimePickerPresenter>(x => x.MinuteIncrement, |
|||
(x, v) => x.MinuteIncrement = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="ClockIdentifier"/> property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<TimePickerPresenter, string> ClockIdentifierProperty = |
|||
TimePicker.ClockIdentifierProperty.AddOwner<TimePickerPresenter>(x => x.ClockIdentifier, |
|||
(x, v) => x.ClockIdentifier = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Time"/> property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<TimePickerPresenter, TimeSpan> TimeProperty = |
|||
AvaloniaProperty.RegisterDirect<TimePickerPresenter, TimeSpan>(nameof(Time), |
|||
x => x.Time, (x, v) => x.Time = v); |
|||
|
|||
public TimePickerPresenter() |
|||
{ |
|||
Time = DateTime.Now.TimeOfDay; |
|||
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle); |
|||
} |
|||
|
|||
//TemplateItems
|
|||
private Grid _pickerContainer; |
|||
private Button _acceptButton; |
|||
private Button _dismissButton; |
|||
private Rectangle _spacer2; |
|||
private Panel _periodHost; |
|||
private DateTimePickerPanel _hourSelector; |
|||
private DateTimePickerPanel _minuteSelector; |
|||
private DateTimePickerPanel _periodSelector; |
|||
private Button _hourUpButton; |
|||
private Button _minuteUpButton; |
|||
private Button _periodUpButton; |
|||
private Button _hourDownButton; |
|||
private Button _minuteDownButton; |
|||
private Button _periodDownButton; |
|||
|
|||
//Backing Fields
|
|||
private TimeSpan _Time; |
|||
private int _minuteIncrement = 1; |
|||
private string _clockIdentifier = "12HourClock"; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the minute increment in the selector
|
|||
/// </summary>
|
|||
public int MinuteIncrement |
|||
{ |
|||
get => _minuteIncrement; |
|||
set |
|||
{ |
|||
if (value < 1 || value > 59) |
|||
throw new ArgumentOutOfRangeException("1 >= MinuteIncrement <= 59"); |
|||
SetAndRaise(MinuteIncrementProperty, ref _minuteIncrement, value); |
|||
InitPicker(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the current clock identifier, either 12HourClock or 24HourClock
|
|||
/// </summary>
|
|||
public string ClockIdentifier |
|||
{ |
|||
get => _clockIdentifier; |
|||
set |
|||
{ |
|||
if (string.IsNullOrEmpty(value) || value == "" || !(value == "12HourClock" || value == "24HourClock")) |
|||
throw new ArgumentException("Invalid ClockIdentifier"); |
|||
SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value); |
|||
InitPicker(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the current time
|
|||
/// </summary>
|
|||
public TimeSpan Time |
|||
{ |
|||
get => _Time; |
|||
set |
|||
{ |
|||
SetAndRaise(TimeProperty, ref _Time, value); |
|||
InitPicker(); |
|||
} |
|||
} |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
base.OnApplyTemplate(e); |
|||
|
|||
_pickerContainer = e.NameScope.Get<Grid>("PickerContainer"); |
|||
_periodHost = e.NameScope.Get<Panel>("PeriodHost"); |
|||
|
|||
_hourSelector = e.NameScope.Get<DateTimePickerPanel>("HourSelector"); |
|||
_minuteSelector = e.NameScope.Get<DateTimePickerPanel>("MinuteSelector"); |
|||
_periodSelector = e.NameScope.Get<DateTimePickerPanel>("PeriodSelector"); |
|||
|
|||
_spacer2 = e.NameScope.Get<Rectangle>("SecondSpacer"); |
|||
|
|||
_acceptButton = e.NameScope.Get<Button>("AcceptButton"); |
|||
_acceptButton.Click += OnAcceptButtonClicked; |
|||
|
|||
_hourUpButton = e.NameScope.Find<RepeatButton>("HourUpButton"); |
|||
if (_hourUpButton != null) |
|||
_hourUpButton.Click += OnSelectorButtonClick; |
|||
_hourDownButton = e.NameScope.Find<RepeatButton>("HourDownButton"); |
|||
if (_hourDownButton != null) |
|||
_hourDownButton.Click += OnSelectorButtonClick; |
|||
|
|||
_minuteUpButton = e.NameScope.Find<RepeatButton>("MinuteUpButton"); |
|||
if (_minuteUpButton != null) |
|||
_minuteUpButton.Click += OnSelectorButtonClick; |
|||
_minuteDownButton = e.NameScope.Find<RepeatButton>("MinuteDownButton"); |
|||
if (_minuteDownButton != null) |
|||
_minuteDownButton.Click += OnSelectorButtonClick; |
|||
|
|||
_periodUpButton = e.NameScope.Find<RepeatButton>("PeriodUpButton"); |
|||
if (_periodUpButton != null) |
|||
_periodUpButton.Click += OnSelectorButtonClick; |
|||
_periodDownButton = e.NameScope.Find<RepeatButton>("PeriodDownButton"); |
|||
if (_periodDownButton != null) |
|||
_periodDownButton.Click += OnSelectorButtonClick; |
|||
|
|||
_dismissButton = e.NameScope.Find<Button>("DismissButton"); |
|||
if (_dismissButton != null) |
|||
_dismissButton.Click += OnDismissButtonClicked; |
|||
|
|||
InitPicker(); |
|||
} |
|||
|
|||
protected override void OnKeyDown(KeyEventArgs e) |
|||
{ |
|||
switch (e.Key) |
|||
{ |
|||
case Key.Escape: |
|||
OnDismiss(); |
|||
e.Handled = true; |
|||
break; |
|||
case Key.Tab: |
|||
var nextFocus = KeyboardNavigationHandler.GetNext(FocusManager.Instance.Current, NavigationDirection.Next); |
|||
KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None); |
|||
e.Handled = true; |
|||
break; |
|||
case Key.Enter: |
|||
OnConfirmed(); |
|||
e.Handled = true; |
|||
break; |
|||
} |
|||
base.OnKeyDown(e); |
|||
} |
|||
|
|||
protected override void OnConfirmed() |
|||
{ |
|||
var hr = _hourSelector.SelectedValue; |
|||
var min = _minuteSelector.SelectedValue; |
|||
var per = _periodSelector.SelectedValue; |
|||
|
|||
if (ClockIdentifier == "12HourClock") |
|||
{ |
|||
hr = per == 1 ? hr + 12 : per == 0 && hr == 12 ? 0 : hr; |
|||
} |
|||
|
|||
Time = new TimeSpan(hr, min, 0); |
|||
|
|||
base.OnConfirmed(); |
|||
} |
|||
|
|||
private void InitPicker() |
|||
{ |
|||
if (_pickerContainer == null) |
|||
return; |
|||
|
|||
bool clock12 = ClockIdentifier == "12HourClock"; |
|||
_hourSelector.MaximumValue = clock12 ? 12 : 23; |
|||
_hourSelector.MinimumValue = clock12 ? 1 : 0; |
|||
_hourSelector.ItemFormat = "%h"; |
|||
var hr = Time.Hours; |
|||
_hourSelector.SelectedValue = !clock12 ? hr : |
|||
hr > 12 ? hr - 12 : hr == 0 ? 12 : hr; |
|||
|
|||
_minuteSelector.MaximumValue = 59; |
|||
_minuteSelector.MinimumValue = 0; |
|||
_minuteSelector.Increment = MinuteIncrement; |
|||
_minuteSelector.SelectedValue = Time.Minutes; |
|||
_minuteSelector.ItemFormat = "mm"; |
|||
|
|||
_periodSelector.MaximumValue = 1; |
|||
_periodSelector.MinimumValue = 0; |
|||
_periodSelector.SelectedValue = hr >= 12 ? 1 : 0; |
|||
|
|||
SetGrid(); |
|||
KeyboardDevice.Instance?.SetFocusedElement(_hourSelector, NavigationMethod.Pointer, KeyModifiers.None); |
|||
} |
|||
|
|||
private void SetGrid() |
|||
{ |
|||
if (ClockIdentifier == "12HourClock") |
|||
{ |
|||
_pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*"); |
|||
_spacer2.IsVisible = true; |
|||
_periodHost.IsVisible = true; |
|||
} |
|||
else |
|||
{ |
|||
_pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*"); |
|||
_spacer2.IsVisible = false; |
|||
_periodHost.IsVisible = false; |
|||
} |
|||
} |
|||
|
|||
private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) |
|||
{ |
|||
OnDismiss(); |
|||
} |
|||
|
|||
private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) |
|||
{ |
|||
OnConfirmed(); |
|||
} |
|||
|
|||
private void OnSelectorButtonClick(object sender, RoutedEventArgs e) |
|||
{ |
|||
if (sender == _hourUpButton) |
|||
_hourSelector.ScrollUp(); |
|||
else if (sender == _hourDownButton) |
|||
_hourSelector.ScrollDown(); |
|||
else if (sender == _minuteUpButton) |
|||
_minuteSelector.ScrollUp(); |
|||
else if (sender == _minuteDownButton) |
|||
_minuteSelector.ScrollDown(); |
|||
else if (sender == _periodUpButton) |
|||
_periodSelector.ScrollUp(); |
|||
else if (sender == _periodDownButton) |
|||
_periodSelector.ScrollDown(); |
|||
} |
|||
|
|||
internal double GetOffsetForPopup() |
|||
{ |
|||
var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41; |
|||
return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_hourSelector.ItemHeight / 2); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class TimePickerSelectedValueChangedEventArgs |
|||
{ |
|||
public TimeSpan? OldTime { get; } |
|||
public TimeSpan? NewTime { get; } |
|||
public TimePickerSelectedValueChangedEventArgs(TimeSpan? old, TimeSpan? newT) |
|||
{ |
|||
OldTime = old; |
|||
NewTime = newT; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,487 @@ |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Media; |
|||
using Avalonia.Metadata; |
|||
using Avalonia.Platform; |
|||
using Avalonia.VisualTree; |
|||
using System; |
|||
using System.Reactive.Disposables; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Defines constants for how the SplitView Pane should display
|
|||
/// </summary>
|
|||
public enum SplitViewDisplayMode |
|||
{ |
|||
/// <summary>
|
|||
/// Pane is displayed next to content, and does not auto collapse
|
|||
/// when tapped outside
|
|||
/// </summary>
|
|||
Inline, |
|||
/// <summary>
|
|||
/// Pane is displayed next to content. When collapsed, pane is still
|
|||
/// visible according to CompactPaneLength. Pane does not auto collapse
|
|||
/// when tapped outside
|
|||
/// </summary>
|
|||
CompactInline, |
|||
/// <summary>
|
|||
/// Pane is displayed above content. Pane collapses when tapped outside
|
|||
/// </summary>
|
|||
Overlay, |
|||
/// <summary>
|
|||
/// Pane is displayed above content. When collapsed, pane is still
|
|||
/// visible according to CompactPaneLength. Pane collapses when tapped outside
|
|||
/// </summary>
|
|||
CompactOverlay |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Defines constants for where the Pane should appear
|
|||
/// </summary>
|
|||
public enum SplitViewPanePlacement |
|||
{ |
|||
Left, |
|||
Right |
|||
} |
|||
|
|||
public class SplitViewTemplateSettings : AvaloniaObject |
|||
{ |
|||
internal SplitViewTemplateSettings() { } |
|||
|
|||
public static readonly StyledProperty<double> ClosedPaneWidthProperty = |
|||
AvaloniaProperty.Register<SplitViewTemplateSettings, double>(nameof(ClosedPaneWidth), 0d); |
|||
|
|||
public static readonly StyledProperty<GridLength> PaneColumnGridLengthProperty = |
|||
AvaloniaProperty.Register<SplitViewTemplateSettings, GridLength>(nameof(PaneColumnGridLength)); |
|||
|
|||
public double ClosedPaneWidth |
|||
{ |
|||
get => GetValue(ClosedPaneWidthProperty); |
|||
internal set => SetValue(ClosedPaneWidthProperty, value); |
|||
} |
|||
|
|||
public GridLength PaneColumnGridLength |
|||
{ |
|||
get => GetValue(PaneColumnGridLengthProperty); |
|||
internal set => SetValue(PaneColumnGridLengthProperty, value); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A control with two views: A collapsible pane and an area for content
|
|||
/// </summary>
|
|||
public class SplitView : TemplatedControl |
|||
{ |
|||
/* |
|||
Pseudo classes & combos |
|||
:open / :closed |
|||
:compactoverlay :compactinline :overlay :inline |
|||
:left :right |
|||
*/ |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Content"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IControl> ContentProperty = |
|||
AvaloniaProperty.Register<SplitView, IControl>(nameof(Content)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="CompactPaneLength"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<double> CompactPaneLengthProperty = |
|||
AvaloniaProperty.Register<SplitView, double>(nameof(CompactPaneLength), defaultValue: 48); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="DisplayMode"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<SplitViewDisplayMode> DisplayModeProperty = |
|||
AvaloniaProperty.Register<SplitView, SplitViewDisplayMode>(nameof(DisplayMode), defaultValue: SplitViewDisplayMode.Overlay); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="IsPaneOpen"/> property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<SplitView, bool> IsPaneOpenProperty = |
|||
AvaloniaProperty.RegisterDirect<SplitView, bool>(nameof(IsPaneOpen), |
|||
x => x.IsPaneOpen, (x, v) => x.IsPaneOpen = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="OpenPaneLength"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<double> OpenPaneLengthProperty = |
|||
AvaloniaProperty.Register<SplitView, double>(nameof(OpenPaneLength), defaultValue: 320); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="PaneBackground"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IBrush> PaneBackgroundProperty = |
|||
AvaloniaProperty.Register<SplitView, IBrush>(nameof(PaneBackground)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="PanePlacement"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<SplitViewPanePlacement> PanePlacementProperty = |
|||
AvaloniaProperty.Register<SplitView, SplitViewPanePlacement>(nameof(PanePlacement)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Pane"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IControl> PaneProperty = |
|||
AvaloniaProperty.Register<SplitView, IControl>(nameof(Pane)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="UseLightDismissOverlayMode"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<bool> UseLightDismissOverlayModeProperty = |
|||
AvaloniaProperty.Register<SplitView, bool>(nameof(UseLightDismissOverlayMode)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="TemplateSettings"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<SplitViewTemplateSettings> TemplateSettingsProperty = |
|||
AvaloniaProperty.Register<SplitView, SplitViewTemplateSettings>(nameof(TemplateSettings)); |
|||
|
|||
private bool _isPaneOpen; |
|||
private Panel _pane; |
|||
private CompositeDisposable _pointerDisposables; |
|||
|
|||
public SplitView() |
|||
{ |
|||
PseudoClasses.Add(":overlay"); |
|||
PseudoClasses.Add(":left"); |
|||
|
|||
TemplateSettings = new SplitViewTemplateSettings(); |
|||
} |
|||
|
|||
static SplitView() |
|||
{ |
|||
UseLightDismissOverlayModeProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnUseLightDismissChanged(v)); |
|||
CompactPaneLengthProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnCompactPaneLengthChanged(v)); |
|||
PanePlacementProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnPanePlacementChanged(v)); |
|||
DisplayModeProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnDisplayModeChanged(v)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the content of the SplitView
|
|||
/// </summary>
|
|||
[Content] |
|||
public IControl Content |
|||
{ |
|||
get => GetValue(ContentProperty); |
|||
set => SetValue(ContentProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the length of the pane when in <see cref="SplitViewDisplayMode.CompactOverlay"/>
|
|||
/// or <see cref="SplitViewDisplayMode.CompactInline"/> mode
|
|||
/// </summary>
|
|||
public double CompactPaneLength |
|||
{ |
|||
get => GetValue(CompactPaneLengthProperty); |
|||
set => SetValue(CompactPaneLengthProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the <see cref="SplitViewDisplayMode"/> for the SplitView
|
|||
/// </summary>
|
|||
public SplitViewDisplayMode DisplayMode |
|||
{ |
|||
get => GetValue(DisplayModeProperty); |
|||
set => SetValue(DisplayModeProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether the pane is open or closed
|
|||
/// </summary>
|
|||
public bool IsPaneOpen |
|||
{ |
|||
get => _isPaneOpen; |
|||
set |
|||
{ |
|||
if (value == _isPaneOpen) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (value) |
|||
{ |
|||
OnPaneOpening(this, null); |
|||
SetAndRaise(IsPaneOpenProperty, ref _isPaneOpen, value); |
|||
|
|||
PseudoClasses.Add(":open"); |
|||
PseudoClasses.Remove(":closed"); |
|||
OnPaneOpened(this, null); |
|||
} |
|||
else |
|||
{ |
|||
SplitViewPaneClosingEventArgs args = new SplitViewPaneClosingEventArgs(false); |
|||
OnPaneClosing(this, args); |
|||
if (!args.Cancel) |
|||
{ |
|||
SetAndRaise(IsPaneOpenProperty, ref _isPaneOpen, value); |
|||
|
|||
PseudoClasses.Add(":closed"); |
|||
PseudoClasses.Remove(":open"); |
|||
OnPaneClosed(this, null); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the length of the pane when open
|
|||
/// </summary>
|
|||
public double OpenPaneLength |
|||
{ |
|||
get => GetValue(OpenPaneLengthProperty); |
|||
set => SetValue(OpenPaneLengthProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the background of the pane
|
|||
/// </summary>
|
|||
public IBrush PaneBackground |
|||
{ |
|||
get => GetValue(PaneBackgroundProperty); |
|||
set => SetValue(PaneBackgroundProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the <see cref="SplitViewPanePlacement"/> for the SplitView
|
|||
/// </summary>
|
|||
public SplitViewPanePlacement PanePlacement |
|||
{ |
|||
get => GetValue(PanePlacementProperty); |
|||
set => SetValue(PanePlacementProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the Pane for the SplitView
|
|||
/// </summary>
|
|||
public IControl Pane |
|||
{ |
|||
get => GetValue(PaneProperty); |
|||
set => SetValue(PaneProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether WinUI equivalent LightDismissOverlayMode is enabled
|
|||
/// <para>When enabled, and the pane is open in Overlay or CompactOverlay mode,
|
|||
/// the contents of the splitview are darkened to visually separate the open pane
|
|||
/// and the rest of the SplitView</para>
|
|||
/// </summary>
|
|||
public bool UseLightDismissOverlayMode |
|||
{ |
|||
get => GetValue(UseLightDismissOverlayModeProperty); |
|||
set => SetValue(UseLightDismissOverlayModeProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the TemplateSettings for the SplitView
|
|||
/// </summary>
|
|||
public SplitViewTemplateSettings TemplateSettings |
|||
{ |
|||
get => GetValue(TemplateSettingsProperty); |
|||
set => SetValue(TemplateSettingsProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fired when the pane is closed
|
|||
/// </summary>
|
|||
public event EventHandler<EventArgs> PaneClosed; |
|||
|
|||
/// <summary>
|
|||
/// Fired when the pane is closing
|
|||
/// </summary>
|
|||
public event EventHandler<SplitViewPaneClosingEventArgs> PaneClosing; |
|||
|
|||
/// <summary>
|
|||
/// Fired when the pane is opened
|
|||
/// </summary>
|
|||
public event EventHandler<EventArgs> PaneOpened; |
|||
|
|||
/// <summary>
|
|||
/// Fired when the pane is opening
|
|||
/// </summary>
|
|||
public event EventHandler<EventArgs> PaneOpening; |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
base.OnApplyTemplate(e); |
|||
_pane = e.NameScope.Find<Panel>("PART_PaneRoot"); |
|||
} |
|||
|
|||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnAttachedToVisualTree(e); |
|||
|
|||
var topLevel = this.VisualRoot; |
|||
if (topLevel is Window window) |
|||
{ |
|||
//Logic adapted from Popup
|
|||
//Basically if we're using an overlay DisplayMode, close the pane if we don't click on the pane
|
|||
IDisposable subscribeToEventHandler<T, TEventHandler>(T target, TEventHandler handler, |
|||
Action<T, TEventHandler> subscribe, Action<T, TEventHandler> unsubscribe) |
|||
{ |
|||
subscribe(target, handler); |
|||
return Disposable.Create((unsubscribe, target, handler), state => state.unsubscribe(state.target, state.handler)); |
|||
} |
|||
|
|||
_pointerDisposables = new CompositeDisposable( |
|||
window.AddDisposableHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel), |
|||
InputManager.Instance?.Process.Subscribe(OnNonClientClick), |
|||
subscribeToEventHandler<Window, EventHandler>(window, Window_Deactivated, |
|||
(x, handler) => x.Deactivated += handler, (x, handler) => x.Deactivated -= handler), |
|||
subscribeToEventHandler<IWindowImpl, Action>(window.PlatformImpl, OnWindowLostFocus, |
|||
(x, handler) => x.LostFocus += handler, (x, handler) => x.LostFocus -= handler)); |
|||
} |
|||
} |
|||
|
|||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnDetachedFromVisualTree(e); |
|||
_pointerDisposables?.Dispose(); |
|||
} |
|||
|
|||
private void OnWindowLostFocus() |
|||
{ |
|||
if (IsPaneOpen && ShouldClosePane()) |
|||
{ |
|||
IsPaneOpen = false; |
|||
} |
|||
} |
|||
|
|||
private void PointerPressedOutside(object sender, PointerPressedEventArgs e) |
|||
{ |
|||
if (!IsPaneOpen) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
//If we click within the Pane, don't do anything
|
|||
//Otherwise, ClosePane if open & using an overlay display mode
|
|||
bool closePane = ShouldClosePane(); |
|||
if (!closePane) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var src = e.Source as IVisual; |
|||
while (src != null) |
|||
{ |
|||
if (src == _pane) |
|||
{ |
|||
closePane = false; |
|||
break; |
|||
} |
|||
|
|||
src = src.VisualParent; |
|||
} |
|||
if (closePane) |
|||
{ |
|||
IsPaneOpen = false; |
|||
e.Handled = true; |
|||
} |
|||
} |
|||
|
|||
private void OnNonClientClick(RawInputEventArgs obj) |
|||
{ |
|||
if (!IsPaneOpen) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var mouse = obj as RawPointerEventArgs; |
|||
if (mouse?.Type == RawPointerEventType.NonClientLeftButtonDown) |
|||
|
|||
{ |
|||
if (ShouldClosePane()) |
|||
IsPaneOpen = false; |
|||
} |
|||
} |
|||
|
|||
private void Window_Deactivated(object sender, EventArgs e) |
|||
{ |
|||
if (IsPaneOpen && ShouldClosePane()) |
|||
{ |
|||
IsPaneOpen = false; |
|||
} |
|||
} |
|||
|
|||
private bool ShouldClosePane() |
|||
{ |
|||
return (DisplayMode == SplitViewDisplayMode.CompactOverlay || DisplayMode == SplitViewDisplayMode.Overlay); |
|||
} |
|||
|
|||
protected virtual void OnPaneOpening(SplitView sender, EventArgs args) |
|||
{ |
|||
PaneOpening?.Invoke(sender, args); |
|||
} |
|||
|
|||
protected virtual void OnPaneOpened(SplitView sender, EventArgs args) |
|||
{ |
|||
PaneOpened?.Invoke(sender, args); |
|||
} |
|||
|
|||
protected virtual void OnPaneClosing(SplitView sender, SplitViewPaneClosingEventArgs args) |
|||
{ |
|||
PaneClosing?.Invoke(sender, args); |
|||
} |
|||
|
|||
protected virtual void OnPaneClosed(SplitView sender, EventArgs args) |
|||
{ |
|||
PaneClosed?.Invoke(sender, args); |
|||
} |
|||
|
|||
private void OnCompactPaneLengthChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
var newLen = (double)e.NewValue; |
|||
var displayMode = DisplayMode; |
|||
if (displayMode == SplitViewDisplayMode.CompactInline) |
|||
{ |
|||
TemplateSettings.ClosedPaneWidth = newLen; |
|||
} |
|||
else if (displayMode == SplitViewDisplayMode.CompactOverlay) |
|||
{ |
|||
TemplateSettings.ClosedPaneWidth = newLen; |
|||
TemplateSettings.PaneColumnGridLength = new GridLength(newLen, GridUnitType.Pixel); |
|||
} |
|||
} |
|||
|
|||
private void OnPanePlacementChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
var oldState = e.OldValue.ToString().ToLower(); |
|||
var newState = e.NewValue.ToString().ToLower(); |
|||
PseudoClasses.Remove($":{oldState}"); |
|||
PseudoClasses.Add($":{newState}"); |
|||
} |
|||
|
|||
private void OnDisplayModeChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
var oldState = e.OldValue.ToString().ToLower(); |
|||
var newState = e.NewValue.ToString().ToLower(); |
|||
|
|||
PseudoClasses.Remove($":{oldState}"); |
|||
PseudoClasses.Add($":{newState}"); |
|||
|
|||
var (closedPaneWidth, paneColumnGridLength) = (SplitViewDisplayMode)e.NewValue switch |
|||
{ |
|||
SplitViewDisplayMode.Overlay => (0, new GridLength(0, GridUnitType.Pixel)), |
|||
SplitViewDisplayMode.CompactOverlay => (CompactPaneLength, new GridLength(CompactPaneLength, GridUnitType.Pixel)), |
|||
SplitViewDisplayMode.Inline => (0, new GridLength(0, GridUnitType.Auto)), |
|||
SplitViewDisplayMode.CompactInline => (CompactPaneLength, new GridLength(0, GridUnitType.Auto)), |
|||
_ => throw new NotImplementedException(), |
|||
}; |
|||
TemplateSettings.ClosedPaneWidth = closedPaneWidth; |
|||
TemplateSettings.PaneColumnGridLength = paneColumnGridLength; |
|||
} |
|||
|
|||
private void OnUseLightDismissChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
var mode = (bool)e.NewValue; |
|||
PseudoClasses.Set(":lightdismiss", mode); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class SplitViewPaneClosingEventArgs : EventArgs |
|||
{ |
|||
public bool Cancel { get; set; } |
|||
|
|||
public SplitViewPaneClosingEventArgs(bool cancel) |
|||
{ |
|||
Cancel = cancel; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,338 @@ |
|||
<!-- |
|||
// (c) Copyright Microsoft Corporation. |
|||
// This source is subject to the Microsoft Public License (Ms-PL). |
|||
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. |
|||
// All other rights reserved. |
|||
--> |
|||
|
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:sys="clr-namespace:System;assembly=netstandard"> |
|||
<Styles.Resources> |
|||
<Thickness x:Key="DatePickerTopHeaderMargin">0,0,0,4</Thickness> |
|||
<x:Double x:Key="DatePickerFlyoutPresenterHighlightHeight">40</x:Double> |
|||
<x:Double x:Key="DatePickerFlyoutPresenterItemHeight">40</x:Double> |
|||
<x:Double x:Key="DatePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double> |
|||
<x:Double x:Key="DatePickerThemeMinWidth">296</x:Double> |
|||
<x:Double x:Key="DatePickerThemeMaxWidth">456</x:Double> |
|||
<Thickness x:Key="DatePickerFlyoutPresenterItemPadding">0,3,0,6</Thickness> |
|||
<Thickness x:Key="DatePickerFlyoutPresenterMonthPadding">9,3,0,6</Thickness> |
|||
<Thickness x:Key="DatePickerHostPadding">0,3,0,6</Thickness> |
|||
<Thickness x:Key="DatePickerHostMonthPadding">9,3,0,6</Thickness> |
|||
<x:Double x:Key="DatePickerSpacerThemeWidth">1</x:Double> |
|||
</Styles.Resources> |
|||
|
|||
<!-- Styles for the items displayed in the selectors --> |
|||
<Style Selector="ListBoxItem.DateTimePickerItem"> |
|||
<Setter Property="Padding" Value="{DynamicResource DatePickerFlyoutPresenterItemPadding}"/> |
|||
<Setter Property="VerticalContentAlignment" Value="Center" /> |
|||
<Setter Property="HorizontalContentAlignment" Value="Center" /> |
|||
</Style> |
|||
<Style Selector="ListBoxItem.DateTimePickerItem:selected"> |
|||
<Setter Property="IsHitTestVisible" Value="False"/> |
|||
</Style> |
|||
<Style Selector="ListBoxItem.DateTimePickerItem:selected /template/ Rectangle#PressedBackground"> |
|||
<Setter Property="Fill" Value="Transparent"/> |
|||
</Style> |
|||
<Style Selector="ListBoxItem.DateTimePickerItem:selected /template/ ContentPresenter"> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/> |
|||
</Style> |
|||
<Style Selector="ListBoxItem.DateTimePickerItem.MonthItem"> |
|||
<Setter Property="Padding" Value="{DynamicResource DatePickerFlyoutPresenterMonthPadding}"/> |
|||
<Setter Property="VerticalContentAlignment" Value="Center" /> |
|||
<Setter Property="HorizontalContentAlignment" Value="Left" /> |
|||
</Style> |
|||
|
|||
|
|||
<!-- This is used for both the accept/dismiss & repeatbuttons in the presenter--> |
|||
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle"> |
|||
<Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackground}" /> |
|||
<Setter Property="HorizontalContentAlignment" Value="Center"/> |
|||
<Setter Property="VerticalContentAlignment" Value="Center"/> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<!-- |
|||
The background is doubled here for the loopingselector up/down repeat buttons |
|||
that appear opaque. Not sure how MS does it though I suspect this is it |
|||
but source isn't MIT yet, so this is my solution --> |
|||
<Border Background="{TemplateBinding Background}"> |
|||
<ContentPresenter x:Name="ContentPresenter" |
|||
Background="{TemplateBinding Background}" |
|||
BorderBrush="{DynamicResource DateTimePickerFlyoutButtonBorderBrush}" |
|||
BorderThickness="{DynamicResource DateTimeFlyoutButtonBorderThickness}" |
|||
Content="{TemplateBinding Content}" |
|||
TextBlock.Foreground="{DynamicResource SystemControlHighlightAltBaseHighBrush}" |
|||
ContentTemplate="{TemplateBinding ContentTemplate}" |
|||
Padding="{TemplateBinding Padding}" |
|||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" |
|||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" |
|||
CornerRadius="{DynamicResource ControlCornerRadius}"/> |
|||
</Border> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle:pointerover /template/ ContentPresenter"> |
|||
<Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackgroundPointerOver}"/> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource DateTimePickerFlyoutButtonBorderBrushPointerOver}"/> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPointerOver}"/> |
|||
</Style> |
|||
|
|||
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle:pressed /template/ ContentPresenter"> |
|||
<Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackgroundPressed}"/> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource DateTimePickerFlyoutButtonBorderBrushPressed}"/> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPressed}"/> |
|||
</Style> |
|||
|
|||
|
|||
<Style Selector="RepeatButton.UpButton"> |
|||
<Setter Property="VerticalAlignment" Value="Top"/> |
|||
<Setter Property="Height" Value="22" /> |
|||
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
|||
<Setter Property="Focusable" Value="False" /> |
|||
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}" /> |
|||
<Setter Property="Content"> |
|||
<Template> |
|||
<Viewbox Height="10" Width="10" HorizontalAlignment="Center" VerticalAlignment="Center"> |
|||
<Path Stroke="{Binding $parent[RepeatButton].Foreground}" StrokeThickness="1" Data="M 0,9 L 9,0 L 18,9"/> |
|||
</Viewbox> |
|||
</Template> |
|||
</Setter> |
|||
</Style> |
|||
<Style Selector="RepeatButton.DownButton"> |
|||
<Setter Property="VerticalAlignment" Value="Bottom"/> |
|||
<Setter Property="Height" Value="22" /> |
|||
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
|||
<Setter Property="Focusable" Value="False" /> |
|||
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}" /> |
|||
<Setter Property="Content"> |
|||
<Template> |
|||
<Viewbox Height="10" Width="10" HorizontalAlignment="Center" VerticalAlignment="Center"> |
|||
<Path Stroke="{Binding $parent[RepeatButton].Foreground}" StrokeThickness="1" Data="M 0,0 L 9,9 L 18,0"/> |
|||
</Viewbox> |
|||
</Template> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="DatePicker"> |
|||
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" /> |
|||
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" /> |
|||
<Setter Property="Foreground" Value="{DynamicResource DatePickerButtonForeground}" /> |
|||
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackground}"/> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrush}"/> |
|||
<Setter Property="BorderThickness" Value="1"/> |
|||
<Setter Property="HorizontalAlignment" Value="Left" /> |
|||
<Setter Property="VerticalAlignment" Value="Center" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Grid Name="LayoutRoot" Margin="{TemplateBinding Padding}" RowDefinitions="Auto,*"> |
|||
<ContentPresenter Name="HeaderContentPresenter" Grid.Row="0" |
|||
Content="{TemplateBinding Header}" |
|||
ContentTemplate="{TemplateBinding HeaderTemplate}" |
|||
Margin="{DynamicResource DatePickerTopHeaderMargin}" |
|||
MaxWidth="{DynamicResource DatePickerThemeMaxWidth}" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Top"/> |
|||
|
|||
<Button Name="FlyoutButton" Grid.Row="1" |
|||
Foreground="{TemplateBinding Foreground}" |
|||
Background="{TemplateBinding Background}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
IsEnabled="{TemplateBinding IsEnabled}" |
|||
MinWidth="{StaticResource DatePickerThemeMinWidth}" |
|||
MaxWidth="{StaticResource DatePickerThemeMaxWidth}" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Stretch" |
|||
HorizontalContentAlignment="Stretch" |
|||
VerticalContentAlignment="Stretch" |
|||
TemplatedControl.IsTemplateFocusTarget="True"> |
|||
<Button.Template> |
|||
<ControlTemplate> |
|||
<ContentPresenter Name="ContentPresenter" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
Background="{TemplateBinding Background}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
Content="{TemplateBinding Content}" |
|||
TextBlock.Foreground="{TemplateBinding Foreground}" |
|||
HorizontalContentAlignment="Stretch" |
|||
VerticalContentAlignment="Stretch" |
|||
CornerRadius="{DynamicResource ControlCornerRadius}"/> |
|||
</ControlTemplate> |
|||
</Button.Template> |
|||
<Grid Name="ButtonContentGrid" ColumnDefinitions="78*,Auto,132*,Auto,78*"> |
|||
<TextBlock Name="DayText" Text="day" HorizontalAlignment="Center" |
|||
Padding="{DynamicResource DatePickerHostPadding}" |
|||
FontFamily="{TemplateBinding FontFamily}" |
|||
FontWeight="{TemplateBinding FontWeight}" |
|||
FontSize="{TemplateBinding FontSize}"/> |
|||
<TextBlock Name="MonthText" Text="month" TextAlignment="Left" |
|||
Padding="{DynamicResource DatePickerHostMonthPadding}" |
|||
FontFamily="{TemplateBinding FontFamily}" |
|||
FontWeight="{TemplateBinding FontWeight}" |
|||
FontSize="{TemplateBinding FontSize}"/> |
|||
<TextBlock Name="YearText" Text="year" HorizontalAlignment="Center" |
|||
Padding="{DynamicResource DatePickerHostPadding}" |
|||
FontFamily="{TemplateBinding FontFamily}" |
|||
FontWeight="{TemplateBinding FontWeight}" |
|||
FontSize="{TemplateBinding FontSize}"/> |
|||
<Rectangle x:Name="FirstSpacer" |
|||
Fill="{DynamicResource DatePickerSpacerFill}" |
|||
HorizontalAlignment="Center" |
|||
Width="1" |
|||
Grid.Column="1" /> |
|||
<Rectangle x:Name="SecondSpacer" |
|||
Fill="{DynamicResource DatePickerSpacerFill}" |
|||
HorizontalAlignment="Center" |
|||
Width="1" |
|||
Grid.Column="3" /> |
|||
</Grid> |
|||
</Button> |
|||
|
|||
<Popup Name="Popup" WindowManagerAddShadowHint="False" |
|||
StaysOpen="False" PlacementTarget="{TemplateBinding}" |
|||
PlacementMode="Bottom"> |
|||
<DatePickerPresenter Name="PickerPresenter" /> |
|||
</Popup> |
|||
|
|||
</Grid> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
<Style Selector="DatePicker /template/ ContentPresenter#HeaderContentPresenter"> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerHeaderForeground}"/> |
|||
</Style> |
|||
<Style Selector="DatePicker:disabled /template/ Rectangle"> |
|||
<Setter Property="Fill" Value="{DynamicResource DatePickerSpacerFillDisabled}"/> |
|||
</Style> |
|||
|
|||
<Style Selector="DatePicker /template/ Button#FlyoutButton:pointerover /template/ ContentPresenter"> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushPointerOver}"/> |
|||
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundPointerOver}"/> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundPointerOver}"/> |
|||
</Style> |
|||
|
|||
<Style Selector="DatePicker /template/ Button#FlyoutButton:pressed /template/ ContentPresenter"> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushPressed}"/> |
|||
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundPressed}"/> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundPressed}"/> |
|||
</Style> |
|||
|
|||
<Style Selector="DatePicker /template/ Button#FlyoutButton:disabled /template/ ContentPresenter"> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushDisabled}"/> |
|||
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundDisabled}"/> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundDisabled}"/> |
|||
</Style> |
|||
|
|||
<!-- Changes foreground for watermark text when SelectedDate is null--> |
|||
<Style Selector="DatePicker:hasnodate /template/ Button#FlyoutButton TextBlock"> |
|||
<Setter Property="Foreground" Value="{DynamicResource TextControlPlaceholderForeground}"/> |
|||
</Style> |
|||
|
|||
<!--WinUI: DatePickerFlyoutPresenter--> |
|||
<Style Selector="DatePickerPresenter"> |
|||
<Setter Property="Width" Value="296" /> |
|||
<Setter Property="MinWidth" Value="296" /> |
|||
<Setter Property="MaxHeight" Value="398" /> |
|||
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" /> |
|||
<Setter Property="FontWeight" Value="Normal" /> |
|||
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" /> |
|||
<Setter Property="Background" Value="{DynamicResource DatePickerFlyoutPresenterBackground}" /> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerFlyoutPresenterBorderBrush}" /> |
|||
<Setter Property="BorderThickness" Value="{DynamicResource DateTimeFlyoutBorderThickness}" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Border Name="Background" Background="{TemplateBinding Background}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
Padding="{DynamicResource DateTimeFlyoutBorderPadding}" |
|||
MaxHeight="398" CornerRadius="{DynamicResource OverlayCornerRadius}"> |
|||
<Grid Name="ContentRoot" RowDefinitions="*,Auto"> |
|||
<Grid Name="PickerContainer"> |
|||
<!--Column Definitions set in code, ignore here--> |
|||
<Panel Name="MonthHost"> |
|||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" |
|||
VerticalScrollBarVisibility="Hidden"> |
|||
<DateTimePickerPanel Name="MonthSelector" |
|||
PanelType="Month" |
|||
ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}" |
|||
ShouldLoop="True" /> |
|||
</ScrollViewer> |
|||
<RepeatButton Name="MonthUpButton" |
|||
Classes="DateTimeFlyoutButtonStyle UpButton"/> |
|||
<RepeatButton Name="MonthDownButton" |
|||
Classes="DateTimeFlyoutButtonStyle DownButton"/> |
|||
</Panel> |
|||
<Panel Name="DayHost"> |
|||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" |
|||
VerticalScrollBarVisibility="Hidden"> |
|||
<DateTimePickerPanel Name="DaySelector" |
|||
PanelType="Day" |
|||
ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}" |
|||
ShouldLoop="True" /> |
|||
</ScrollViewer> |
|||
<RepeatButton Name="DayUpButton" |
|||
Classes="DateTimeFlyoutButtonStyle UpButton"/> |
|||
<RepeatButton Name="DayDownButton" |
|||
Classes="DateTimeFlyoutButtonStyle DownButton"/> |
|||
</Panel> |
|||
<Panel Name="YearHost"> |
|||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" |
|||
VerticalScrollBarVisibility="Hidden"> |
|||
<DateTimePickerPanel Name="YearSelector" |
|||
PanelType="Year" |
|||
ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}" |
|||
ShouldLoop="False" /> |
|||
</ScrollViewer> |
|||
<RepeatButton Name="YearUpButton" |
|||
Classes="DateTimeFlyoutButtonStyle UpButton"/> |
|||
<RepeatButton Name="YearDownButton" |
|||
Classes="DateTimeFlyoutButtonStyle DownButton"/> |
|||
</Panel> |
|||
<Rectangle Name="HighlightRect" IsHitTestVisible="False" ZIndex="-1" |
|||
Fill="{DynamicResource DatePickerFlyoutPresenterHighlightFill}" |
|||
Grid.Column="0" Grid.ColumnSpan="5" VerticalAlignment="Center" |
|||
Height="{DynamicResource DatePickerFlyoutPresenterHighlightHeight}" /> |
|||
<Rectangle Name="FirstSpacer" |
|||
Fill="{DynamicResource DatePickerFlyoutPresenterSpacerFill}" |
|||
HorizontalAlignment="Center" |
|||
Width="{DynamicResource DatePickerSpacerThemeWidth}" |
|||
Grid.Column="1" /> |
|||
<Rectangle Name="SecondSpacer" |
|||
Fill="{DynamicResource DatePickerFlyoutPresenterSpacerFill}" |
|||
HorizontalAlignment="Center" |
|||
Width="{DynamicResource DatePickerSpacerThemeWidth}" |
|||
Grid.Column="3" /> |
|||
</Grid> |
|||
<Grid Grid.Row="1" Height="{DynamicResource DatePickerFlyoutPresenterAcceptDismissHostGridHeight}" |
|||
Name="AcceptDismissGrid" ColumnDefinitions="*,*"> |
|||
<Rectangle Height="{DynamicResource DatePickerSpacerThemeWidth}" VerticalAlignment="Top" |
|||
Fill="{DynamicResource DatePickerFlyoutPresenterSpacerFill}" |
|||
Grid.ColumnSpan="2"/> |
|||
<Button Name="AcceptButton" Grid.Column="0" Classes="DateTimeFlyoutButtonStyle" |
|||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> |
|||
<Path Stroke="{Binding $parent[Button].Foreground}" StrokeLineCap="Round" |
|||
StrokeThickness="0.75" Data="M0.5,8.5 5,13.5 15.5,3" /> |
|||
</Button> |
|||
<Button Name="DismissButton" Grid.Column="1" Classes="DateTimeFlyoutButtonStyle" |
|||
FontSize="16" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> |
|||
<Path Stroke="{Binding $parent[Button].Foreground}" StrokeLineCap="Round" |
|||
StrokeThickness="0.75" Data="M2,2 14,14 M2,14 14 2" /> |
|||
</Button> |
|||
</Grid> |
|||
</Grid> |
|||
</Border> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="DatePickerPresenter /template/ Panel RepeatButton"> |
|||
<Setter Property="IsVisible" Value="False" /> |
|||
</Style> |
|||
|
|||
<Style Selector="DatePickerPresenter /template/ Panel:pointerover RepeatButton"> |
|||
<Setter Property="IsVisible" Value="True" /> |
|||
</Style> |
|||
|
|||
</Styles> |
|||
@ -0,0 +1,219 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
|
|||
<Styles.Resources> |
|||
<x:Double x:Key="SplitViewOpenPaneThemeLength">320</x:Double> |
|||
<x:Double x:Key="SplitViewCompactPaneThemeLength">48</x:Double> |
|||
|
|||
<!-- Not used here (directly) since they're strings, but preserving for reference |
|||
<x:String x:Key="SplitViewPaneAnimationOpenDuration">00:00:00.2</x:String> |
|||
<x:String x:Key="SplitViewPaneAnimationOpenPreDuration">00:00:00.19999</x:String> |
|||
<x:String x:Key="SplitViewPaneAnimationCloseDuration">00:00:00.1</x:String>--> |
|||
</Styles.Resources> |
|||
|
|||
<Style Selector="SplitView"> |
|||
<Setter Property="OpenPaneLength" Value="{DynamicResource SplitViewOpenPaneThemeLength}" /> |
|||
<Setter Property="CompactPaneLength" Value="{DynamicResource SplitViewCompactPaneThemeLength}" /> |
|||
<Setter Property="PaneBackground" Value="{DynamicResource SystemControlPageBackgroundChromeLowBrush}" /> |
|||
</Style> |
|||
|
|||
<!-- Left --> |
|||
<Style Selector="SplitView:left"> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Grid Name="Container" Background="{TemplateBinding Background}"> |
|||
<Grid.ColumnDefinitions> |
|||
<!-- why is this throwing a binding error? --> |
|||
<ColumnDefinition Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.PaneColumnGridLength}"/> |
|||
<ColumnDefinition Width="*"/> |
|||
</Grid.ColumnDefinitions> |
|||
|
|||
<Panel Name="PART_PaneRoot" Background="{TemplateBinding PaneBackground}" |
|||
ClipToBounds="True" |
|||
HorizontalAlignment="Left" |
|||
ZIndex="100"> |
|||
<Border Child="{TemplateBinding Pane}"/> |
|||
<Rectangle Name="HCPaneBorder" Fill="{DynamicResource SystemControlForegroundTransparentBrush}" Width="1" HorizontalAlignment="Right" /> |
|||
</Panel> |
|||
|
|||
<Panel Name="ContentRoot"> |
|||
<Border Child="{TemplateBinding Content}" /> |
|||
<Rectangle Name="LightDismissLayer"/> |
|||
</Panel> |
|||
|
|||
</Grid> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<!-- Overlay --> |
|||
<Style Selector="SplitView:overlay:left /template/ Panel#PART_PaneRoot"> |
|||
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
|||
<!-- ColumnSpan should be 2 --> |
|||
<Setter Property="Grid.ColumnSpan" Value="1"/> |
|||
<Setter Property="Grid.Column" Value="0"/> |
|||
</Style> |
|||
<Style Selector="SplitView:overlay:left /template/ Panel#ContentRoot"> |
|||
<Setter Property="Grid.Column" Value="1"/> |
|||
<Setter Property="Grid.ColumnSpan" Value="2"/> |
|||
</Style> |
|||
|
|||
<!-- CompactInline --> |
|||
<Style Selector="SplitView:compactinline:left /template/ Panel#PART_PaneRoot"> |
|||
<Setter Property="Grid.ColumnSpan" Value="1"/> |
|||
<Setter Property="Grid.Column" Value="0"/> |
|||
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
|||
</Style> |
|||
<Style Selector="SplitView:compactinline:left /template/ Panel#ContentRoot"> |
|||
<Setter Property="Grid.Column" Value="1"/> |
|||
<Setter Property="Grid.ColumnSpan" Value="1"/> |
|||
</Style> |
|||
|
|||
<!-- CompactOverlay --> |
|||
<Style Selector="SplitView:compactoverlay:left /template/ Panel#PART_PaneRoot"> |
|||
<!-- ColumnSpan should be 2 --> |
|||
<Setter Property="Grid.ColumnSpan" Value="1"/> |
|||
<Setter Property="Grid.Column" Value="0"/> |
|||
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
|||
</Style> |
|||
<Style Selector="SplitView:compactoverlay:left /template/ Panel#ContentRoot"> |
|||
<Setter Property="Grid.Column" Value="1"/> |
|||
<Setter Property="Grid.ColumnSpan" Value="1"/> |
|||
</Style> |
|||
|
|||
<!-- Inline --> |
|||
<Style Selector="SplitView:inline:left /template/ Panel#PART_PaneRoot"> |
|||
<Setter Property="Grid.ColumnSpan" Value="1"/> |
|||
<Setter Property="Grid.Column" Value="0"/> |
|||
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
|||
</Style> |
|||
<Style Selector="SplitView:inline:left /template/ Panel#ContentRoot"> |
|||
<Setter Property="Grid.Column" Value="1"/> |
|||
<Setter Property="Grid.ColumnSpan" Value="1"/> |
|||
</Style> |
|||
|
|||
<!-- Right --> |
|||
<Style Selector="SplitView:right"> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Grid Name="Container" Background="{TemplateBinding Background}"> |
|||
<Grid.ColumnDefinitions> |
|||
<ColumnDefinition Width="*"/> |
|||
<ColumnDefinition Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.PaneColumnGridLength}"/> |
|||
</Grid.ColumnDefinitions> |
|||
|
|||
<Panel Name="PART_PaneRoot" Background="{TemplateBinding PaneBackground}" |
|||
ClipToBounds="True" |
|||
HorizontalAlignment="Right" |
|||
ZIndex="100"> |
|||
<Border Child="{TemplateBinding Pane}"/> |
|||
<Rectangle Name="HCPaneBorder" |
|||
Fill="{DynamicResource SystemControlForegroundTransparentBrush}" |
|||
Width="1" HorizontalAlignment="Left" /> |
|||
</Panel> |
|||
|
|||
<Panel Name="ContentRoot"> |
|||
<Border Child="{TemplateBinding Content}" /> |
|||
<Rectangle Name="LightDismissLayer"/> |
|||
</Panel> |
|||
|
|||
</Grid> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<!-- Overlay --> |
|||
<Style Selector="SplitView:overlay:right /template/ Panel#PART_PaneRoot"> |
|||
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
|||
<Setter Property="Grid.ColumnSpan" Value="2"/> |
|||
<Setter Property="Grid.Column" Value="1"/> |
|||
</Style> |
|||
<Style Selector="SplitView:overlay:right /template/ Panel#ContentRoot"> |
|||
<Setter Property="Grid.Column" Value="0"/> |
|||
<Setter Property="Grid.ColumnSpan" Value="2"/> |
|||
</Style> |
|||
|
|||
<!-- CompactInline --> |
|||
<Style Selector="SplitView:compactinline:right /template/ Panel#PART_PaneRoot"> |
|||
<Setter Property="Grid.ColumnSpan" Value="1"/> |
|||
<Setter Property="Grid.Column" Value="1"/> |
|||
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
|||
</Style> |
|||
<Style Selector="SplitView:compactinline:right /template/ Panel#ContentRoot"> |
|||
<Setter Property="Grid.Column" Value="0"/> |
|||
<Setter Property="Grid.ColumnSpan" Value="1"/> |
|||
</Style> |
|||
|
|||
<!-- CompactOverlay --> |
|||
<Style Selector="SplitView:compactoverlay:right /template/ Panel#PART_PaneRoot"> |
|||
<Setter Property="Grid.ColumnSpan" Value="2"/> |
|||
<Setter Property="Grid.Column" Value="1"/> |
|||
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
|||
</Style> |
|||
<Style Selector="SplitView:compactoverlay:right /template/ Panel#ContentRoot"> |
|||
<Setter Property="Grid.Column" Value="0"/> |
|||
<Setter Property="Grid.ColumnSpan" Value="1"/> |
|||
</Style> |
|||
|
|||
<!-- Inline --> |
|||
<Style Selector="SplitView:inline:right /template/ Panel#PART_PaneRoot"> |
|||
<Setter Property="Grid.ColumnSpan" Value="1"/> |
|||
<Setter Property="Grid.Column" Value="1"/> |
|||
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
|||
</Style> |
|||
<Style Selector="SplitView:inline:right /template/ Panel#ContentRoot"> |
|||
<Setter Property="Grid.Column" Value="0"/> |
|||
<Setter Property="Grid.ColumnSpan" Value="1"/> |
|||
</Style> |
|||
|
|||
<!-- Open/Close Pane animation --> |
|||
<Style Selector="SplitView:open /template/ Panel#PART_PaneRoot"> |
|||
<Setter Property="Transitions"> |
|||
<Transitions> |
|||
<DoubleTransition Property="Width" Duration="00:00:00.2" Easing="0.1,0.9,0.2,1.0" /> |
|||
</Transitions> |
|||
</Setter> |
|||
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=OpenPaneLength}" /> |
|||
</Style> |
|||
<Style Selector="SplitView:open /template/ Rectangle#LightDismissLayer"> |
|||
<Setter Property="Transitions"> |
|||
<Transitions> |
|||
<DoubleTransition Property="Opacity" Duration="00:00:00.2" Easing="0.1,0.9,0.2,1.0" /> |
|||
</Transitions> |
|||
</Setter> |
|||
<Setter Property="Opacity" Value="1.0"/> |
|||
</Style> |
|||
|
|||
<Style Selector="SplitView:closed /template/ Panel#PART_PaneRoot"> |
|||
<Setter Property="Transitions"> |
|||
<Transitions> |
|||
<DoubleTransition Property="Width" Duration="00:00:00.1" Easing="0.1,0.9,0.2,1.0" /> |
|||
</Transitions> |
|||
</Setter> |
|||
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" /> |
|||
</Style> |
|||
<Style Selector="SplitView:closed /template/ Rectangle#LightDismissLayer"> |
|||
<Setter Property="Transitions"> |
|||
<Transitions> |
|||
<DoubleTransition Property="Opacity" Duration="00:00:00.2" Easing="0.1,0.9,0.2,1.0" /> |
|||
</Transitions> |
|||
</Setter> |
|||
<Setter Property="Opacity" Value="0.0"/> |
|||
</Style> |
|||
|
|||
<Style Selector="SplitView /template/ Rectangle#LightDismissLayer"> |
|||
<Setter Property="IsVisible" Value="False"/> |
|||
<Setter Property="Fill" Value="Transparent" /> |
|||
</Style> |
|||
<Style Selector="SplitView:lightdismiss /template/ Rectangle#LightDismissLayer"> |
|||
<Setter Property="Fill" Value="{DynamicResource SplitViewLightDismissOverlayBackground}" /> |
|||
</Style> |
|||
|
|||
<Style Selector="SplitView:overlay:open /template/ Rectangle#LightDismissLayer"> |
|||
<Setter Property="IsVisible" Value="True"/> |
|||
</Style> |
|||
<Style Selector="SplitView:compactoverlay:open /template/ Rectangle#LightDismissLayer"> |
|||
<Setter Property="IsVisible" Value="True"/> |
|||
</Style> |
|||
|
|||
</Styles> |
|||
@ -0,0 +1,284 @@ |
|||
<!-- |
|||
// (c) Copyright Microsoft Corporation. |
|||
// This source is subject to the Microsoft Public License (Ms-PL). |
|||
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. |
|||
// All other rights reserved. |
|||
--> |
|||
|
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:sys="clr-namespace:System;assembly=netstandard"> |
|||
<Styles.Resources> |
|||
<x:Double x:Key="TimePickerFlyoutPresenterItemHeight">40</x:Double> |
|||
<x:Double x:Key="TimePickerSpacerThemeWidth">1</x:Double> |
|||
<Thickness x:Key="TimePickerBorderThemeThickness">1</Thickness> |
|||
<Thickness x:Key="TimePickerTopHeaderMargin">0,0,0,4</Thickness> |
|||
<x:Double x:Key="TimePickerFlyoutPresenterHighlightHeight">40</x:Double> |
|||
<x:Double x:Key="TimePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double> |
|||
<x:Double x:Key="TimePickerThemeMinWidth">242</x:Double> |
|||
<x:Double x:Key="TimePickerThemeMaxWidth">456</x:Double> |
|||
<Thickness x:Key="TimePickerFlyoutPresenterItemPadding">0,3,0,6</Thickness> |
|||
<Thickness x:Key="TimePickerHostPadding">0,3,0,6</Thickness> |
|||
</Styles.Resources> |
|||
|
|||
<Style Selector="ListBoxItem.DateTimePickerItem.HourItem"> |
|||
<Setter Property="Padding" Value="{DynamicResource TimePickerFlyoutPresenterItemPadding}" /> |
|||
</Style> |
|||
<Style Selector="ListBoxItem.DateTimePickerItem.MinuteItem"> |
|||
<Setter Property="Padding" Value="{DynamicResource TimePickerFlyoutPresenterItemPadding}" /> |
|||
</Style> |
|||
<Style Selector="ListBoxItem.DateTimePickerItem.TimePeriodItem"> |
|||
<Setter Property="Padding" Value="{DynamicResource TimePickerFlyoutPresenterItemPadding}" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TimePicker"> |
|||
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" /> |
|||
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" /> |
|||
<Setter Property="Foreground" Value="{DynamicResource TimePickerButtonForeground}" /> |
|||
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackground}"/> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrush}"/> |
|||
<Setter Property="BorderThickness" Value="{DynamicResource TimePickerBorderThemeThickness}"/> |
|||
<Setter Property="HorizontalAlignment" Value="Left" /> |
|||
<Setter Property="VerticalAlignment" Value="Center" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Grid Name="LayoutRoot" Margin="{TemplateBinding Padding}" RowDefinitions="Auto,*"> |
|||
<ContentPresenter x:Name="HeaderContentPresenter" |
|||
Grid.Row="0" |
|||
Content="{TemplateBinding Header}" |
|||
ContentTemplate="{TemplateBinding HeaderTemplate}" |
|||
Margin="{DynamicResource TimePickerTopHeaderMargin}" |
|||
MaxWidth="{DynamicResource TimePickerThemeMaxWidth}" |
|||
TextBlock.Foreground="{DynamicResource TimePickerHeaderForeground}" |
|||
HorizontalAlignment="Stretch" |
|||
VerticalAlignment="Top" /> |
|||
|
|||
<Button x:Name="FlyoutButton" |
|||
Grid.Row="1" |
|||
Foreground="{TemplateBinding Foreground}" |
|||
Background="{TemplateBinding Background}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
IsEnabled="{TemplateBinding IsEnabled}" |
|||
MinWidth="{StaticResource TimePickerThemeMinWidth}" |
|||
MaxWidth="{StaticResource TimePickerThemeMaxWidth}" |
|||
HorizontalAlignment="Stretch" |
|||
HorizontalContentAlignment="Stretch" |
|||
VerticalAlignment="Top" |
|||
VerticalContentAlignment="Stretch"> |
|||
<Button.Template> |
|||
<ControlTemplate> |
|||
<ContentPresenter Name="ContentPresenter" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
Background="{TemplateBinding Background}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
Content="{TemplateBinding Content}" |
|||
TextBlock.Foreground="{TemplateBinding Foreground}" |
|||
HorizontalContentAlignment="Stretch" |
|||
VerticalContentAlignment="Stretch" |
|||
CornerRadius="{DynamicResource ControlCornerRadius}" /> |
|||
</ControlTemplate> |
|||
</Button.Template> |
|||
|
|||
<Grid Name="FlyoutButtonContentGrid"> |
|||
<!--Ignore col defs here, set in code--> |
|||
<Border x:Name="FirstPickerHost" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> |
|||
<TextBlock x:Name="HourTextBlock" |
|||
HorizontalAlignment="Center" |
|||
Padding="{DynamicResource TimePickerHostPadding}" |
|||
FontFamily="{TemplateBinding FontFamily}" |
|||
FontWeight="{TemplateBinding FontWeight}" |
|||
FontSize="{TemplateBinding FontSize}" /> |
|||
</Border> |
|||
|
|||
<Rectangle Name="FirstColumnDivider" |
|||
Fill="{DynamicResource TimePickerSpacerFill}" |
|||
HorizontalAlignment="Center" |
|||
Width="{DynamicResource TimePickerSpacerThemeWidth}" |
|||
Grid.Column="1" /> |
|||
|
|||
<Border x:Name="SecondPickerHost" Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> |
|||
<TextBlock x:Name="MinuteTextBlock" |
|||
HorizontalAlignment="Center" |
|||
Padding="{DynamicResource TimePickerHostPadding}" |
|||
FontFamily="{TemplateBinding FontFamily}" |
|||
FontWeight="{TemplateBinding FontWeight}" |
|||
FontSize="{TemplateBinding FontSize}"/> |
|||
</Border> |
|||
|
|||
<Rectangle Name="SecondColumnDivider" |
|||
Fill="{DynamicResource TimePickerSpacerFill}" |
|||
HorizontalAlignment="Center" |
|||
Width="{DynamicResource TimePickerSpacerThemeWidth}" |
|||
Grid.Column="3" /> |
|||
|
|||
<Border x:Name="ThirdPickerHost" Grid.Column="4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> |
|||
<TextBlock x:Name="PeriodTextBlock" |
|||
HorizontalAlignment="Center" |
|||
Padding="{DynamicResource TimePickerHostPadding}" |
|||
FontFamily="{TemplateBinding FontFamily}" |
|||
FontWeight="{TemplateBinding FontWeight}" |
|||
FontSize="{TemplateBinding FontSize}" /> |
|||
</Border> |
|||
</Grid> |
|||
</Button> |
|||
|
|||
<Popup Name="Popup" WindowManagerAddShadowHint="False" |
|||
StaysOpen="False" PlacementTarget="{TemplateBinding}" |
|||
PlacementMode="Bottom"> |
|||
<TimePickerPresenter Name="PickerPresenter" /> |
|||
</Popup> |
|||
|
|||
</Grid> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="TimePicker:disabled /template/ ContentPresenter#HeaderContentPresenter"> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TimePickerHeaderForegroundDisabled}"/> |
|||
</Style> |
|||
<Style Selector="TimePicker:disabled /template/ Rectangle"> |
|||
<Setter Property="Fill" Value="{DynamicResource TimePickerSpacerFillDisabled}"/> |
|||
</Style> |
|||
|
|||
<Style Selector="TimePicker /template/ Button#FlyoutButton:pointerover /template/ ContentPresenter"> |
|||
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackgroundPointerOver}"/> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrushPointerOver}"/> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TimePickerButtonForegroundPointerOver}"/> |
|||
</Style> |
|||
|
|||
<Style Selector="TimePicker /template/ Button:pressed /template/ ContentPresenter"> |
|||
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackgroundPressed}"/> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrushPressed}"/> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TimePickerButtonForegroundPressed}"/> |
|||
</Style> |
|||
|
|||
<Style Selector="TimePicker /template/ Button:disabled /template/ ContentPresenter"> |
|||
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackgroundDisabled}"/> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrushDisabled}"/> |
|||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TimePickerButtonForegroundDisabled}"/> |
|||
</Style> |
|||
|
|||
<Style Selector="TimePicker:hasnotime /template/ Button#FlyoutButton TextBlock"> |
|||
<Setter Property="Foreground" Value="{DynamicResource TextControlPlaceholderForeground}"/> |
|||
</Style> |
|||
|
|||
<Style Selector="TimePickerPresenter"> |
|||
<Setter Property="Width" Value="242" /> |
|||
<Setter Property="MinWidth" Value="242" /> |
|||
<Setter Property="MaxHeight" Value="398" /> |
|||
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" /> |
|||
<Setter Property="FontWeight" Value="Normal" /> |
|||
<Setter Property="Background" Value="{DynamicResource TimePickerFlyoutPresenterBackground}" /> |
|||
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerFlyoutPresenterBorderBrush}" /> |
|||
<Setter Property="BorderThickness" Value="{DynamicResource DateTimeFlyoutBorderThickness}" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Border Name="Background" |
|||
Background="{TemplateBinding Background}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
CornerRadius="{DynamicResource OverlayCornerRadius}" |
|||
Padding="{DynamicResource DateTimeFlyoutBorderPadding}" |
|||
MaxHeight="398"> |
|||
<Grid Name="ContentPanel" RowDefinitions="*,Auto"> |
|||
<Grid Name="PickerContainer"> |
|||
<!--Ignore col defs here, set in code--> |
|||
<Panel Name="HourHost" Grid.Column="0"> |
|||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" |
|||
VerticalScrollBarVisibility="Hidden"> |
|||
<DateTimePickerPanel Name="HourSelector" |
|||
PanelType="Hour" |
|||
ShouldLoop="True" |
|||
ItemHeight="{DynamicResource TimePickerFlyoutPresenterItemHeight}"/> |
|||
</ScrollViewer> |
|||
<RepeatButton Name="HourUpButton" |
|||
Classes="DateTimeFlyoutButtonStyle UpButton"/> |
|||
<RepeatButton Name="HourDownButton" |
|||
Classes="DateTimeFlyoutButtonStyle DownButton"/> |
|||
|
|||
</Panel> |
|||
|
|||
<Panel Name="MinuteHost" Grid.Column="2"> |
|||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" |
|||
VerticalScrollBarVisibility="Hidden"> |
|||
<DateTimePickerPanel Name="MinuteSelector" |
|||
PanelType="Minute" |
|||
ShouldLoop="True" |
|||
ItemHeight="{DynamicResource TimePickerFlyoutPresenterItemHeight}"/> |
|||
</ScrollViewer> |
|||
<RepeatButton Name="MinuteUpButton" |
|||
Classes="DateTimeFlyoutButtonStyle UpButton"/> |
|||
<RepeatButton Name="MinuteDownButton" |
|||
Classes="DateTimeFlyoutButtonStyle DownButton"/> |
|||
|
|||
</Panel> |
|||
|
|||
<Panel Name="PeriodHost" Grid.Column="4"> |
|||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" |
|||
VerticalScrollBarVisibility="Hidden"> |
|||
<DateTimePickerPanel Name="PeriodSelector" |
|||
PanelType="TimePeriod" |
|||
ShouldLoop="False" |
|||
ItemHeight="{DynamicResource TimePickerFlyoutPresenterItemHeight}"/> |
|||
</ScrollViewer> |
|||
<RepeatButton Name="PeriodUpButton" |
|||
Classes="DateTimeFlyoutButtonStyle UpButton"/> |
|||
<RepeatButton Name="PeriodDownButton" |
|||
Classes="DateTimeFlyoutButtonStyle DownButton"/> |
|||
|
|||
</Panel> |
|||
|
|||
<Rectangle x:Name="HighlightRect" ZIndex="-1" |
|||
Fill="{DynamicResource TimePickerFlyoutPresenterHighlightFill}" |
|||
Grid.Column="0" |
|||
Grid.ColumnSpan="5" |
|||
VerticalAlignment="Center" |
|||
Height="{DynamicResource TimePickerFlyoutPresenterHighlightHeight}" /> |
|||
|
|||
<Rectangle Name="FirstSpacer" |
|||
Fill="{DynamicResource TimePickerFlyoutPresenterSpacerFill}" |
|||
HorizontalAlignment="Center" |
|||
Width="{DynamicResource TimePickerSpacerThemeWidth}" |
|||
Grid.Column="1" /> |
|||
|
|||
<Rectangle Name="SecondSpacer" |
|||
Fill="{DynamicResource TimePickerFlyoutPresenterSpacerFill}" |
|||
HorizontalAlignment="Center" |
|||
Width="{DynamicResource TimePickerSpacerThemeWidth}" |
|||
Grid.Column="3" /> |
|||
</Grid> |
|||
|
|||
<Grid Grid.Row="1" Height="{DynamicResource TimePickerFlyoutPresenterAcceptDismissHostGridHeight}" |
|||
Name="AcceptDismissHostGrid" ColumnDefinitions="*,*"> |
|||
<Rectangle Height="{DynamicResource TimePickerSpacerThemeWidth}" |
|||
VerticalAlignment="Top" |
|||
Fill="{DynamicResource TimePickerFlyoutPresenterSpacerFill}" |
|||
Grid.ColumnSpan="2" /> |
|||
<Button Name="AcceptButton" Grid.Column="0" Classes="DateTimeFlyoutButtonStyle" |
|||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> |
|||
<Path Stroke="{Binding $parent[Button].Foreground}" StrokeLineCap="Round" |
|||
StrokeThickness="0.75" Data="M0.5,8.5 5,13.5 15.5,3" /> |
|||
</Button> |
|||
<Button Name="DismissButton" Grid.Column="1" Classes="DateTimeFlyoutButtonStyle" |
|||
FontSize="16" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> |
|||
<Path Stroke="{Binding $parent[Button].Foreground}" StrokeLineCap="Round" |
|||
StrokeThickness="0.75" Data="M2,2 14,14 M2,14 14 2" /> |
|||
</Button> |
|||
</Grid> |
|||
|
|||
</Grid> |
|||
</Border> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="TimePickerPresenter /template/ Panel RepeatButton"> |
|||
<Setter Property="IsVisible" Value="False" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TimePickerPresenter /template/ Panel:pointerover RepeatButton"> |
|||
<Setter Property="IsVisible" Value="True" /> |
|||
</Style> |
|||
|
|||
</Styles> |
|||
@ -0,0 +1,253 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using Avalonia.Controls.Shapes; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Platform; |
|||
using Avalonia.UnitTests; |
|||
using Avalonia.VisualTree; |
|||
using Moq; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Controls.UnitTests |
|||
{ |
|||
public class DatePickerTests |
|||
{ |
|||
[Fact] |
|||
public void SelectedDateChanged_Should_Fire_When_SelectedDate_Set() |
|||
{ |
|||
using (UnitTestApplication.Start(Services)) |
|||
{ |
|||
bool handled = false; |
|||
DatePicker datePicker = new DatePicker(); |
|||
datePicker.SelectedDateChanged += (s, e) => |
|||
{ |
|||
handled = true; |
|||
}; |
|||
DateTimeOffset value = new DateTimeOffset(2000, 10, 10, 0, 0, 0, TimeSpan.Zero); |
|||
datePicker.SelectedDate = value; |
|||
Threading.Dispatcher.UIThread.RunJobs(); |
|||
Assert.True(handled); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void DayVisible_False_Should_Hide_Day() |
|||
{ |
|||
using (UnitTestApplication.Start(Services)) |
|||
{ |
|||
DatePicker datePicker = new DatePicker |
|||
{ |
|||
Template = CreateTemplate(), |
|||
DayVisible = false |
|||
}; |
|||
datePicker.ApplyTemplate(); |
|||
Threading.Dispatcher.UIThread.RunJobs(); |
|||
|
|||
var desc = datePicker.GetVisualDescendants(); |
|||
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
|
|||
TextBlock dayText = null; |
|||
Grid container = null; |
|||
|
|||
Assert.True(desc.ElementAt(1) is Button); |
|||
|
|||
container = (desc.ElementAt(1) as Button).Content as Grid; |
|||
Assert.True(container != null); |
|||
|
|||
for(int i = 0; i < container.Children.Count; i++) |
|||
{ |
|||
if(container.Children[i] is TextBlock tb && tb.Name == "DayText") |
|||
{ |
|||
dayText = tb; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
Assert.True(dayText != null); |
|||
Assert.True(!dayText.IsVisible); |
|||
Assert.True(container.ColumnDefinitions.Count == 3); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void MonthVisible_False_Should_Hide_Month() |
|||
{ |
|||
using (UnitTestApplication.Start(Services)) |
|||
{ |
|||
DatePicker datePicker = new DatePicker |
|||
{ |
|||
Template = CreateTemplate(), |
|||
MonthVisible = false |
|||
}; |
|||
datePicker.ApplyTemplate(); |
|||
Threading.Dispatcher.UIThread.RunJobs(); |
|||
|
|||
var desc = datePicker.GetVisualDescendants(); |
|||
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
|
|||
TextBlock monthText = null; |
|||
Grid container = null; |
|||
|
|||
Assert.True(desc.ElementAt(1) is Button); |
|||
|
|||
container = (desc.ElementAt(1) as Button).Content as Grid; |
|||
Assert.True(container != null); |
|||
|
|||
for (int i = 0; i < container.Children.Count; i++) |
|||
{ |
|||
if (container.Children[i] is TextBlock tb && tb.Name == "MonthText") |
|||
{ |
|||
monthText = tb; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
Assert.True(monthText != null); |
|||
Assert.True(!monthText.IsVisible); |
|||
Assert.True(container.ColumnDefinitions.Count == 3); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void YearVisible_False_Should_Hide_Year() |
|||
{ |
|||
using (UnitTestApplication.Start(Services)) |
|||
{ |
|||
DatePicker datePicker = new DatePicker |
|||
{ |
|||
Template = CreateTemplate(), |
|||
YearVisible = false |
|||
}; |
|||
datePicker.ApplyTemplate(); |
|||
Threading.Dispatcher.UIThread.RunJobs(); |
|||
|
|||
var desc = datePicker.GetVisualDescendants(); |
|||
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
|
|||
TextBlock yearText = null; |
|||
Grid container = null; |
|||
|
|||
Assert.True(desc.ElementAt(1) is Button); |
|||
|
|||
container = (desc.ElementAt(1) as Button).Content as Grid; |
|||
Assert.True(container != null); |
|||
|
|||
for (int i = 0; i < container.Children.Count; i++) |
|||
{ |
|||
if (container.Children[i] is TextBlock tb && tb.Name == "YearText") |
|||
{ |
|||
yearText = tb; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
Assert.True(yearText != null); |
|||
Assert.True(!yearText.IsVisible); |
|||
Assert.True(container.ColumnDefinitions.Count == 3); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void SelectedDate_null_Should_Use_Placeholders() |
|||
{ |
|||
using (UnitTestApplication.Start(Services)) |
|||
{ |
|||
DatePicker datePicker = new DatePicker |
|||
{ |
|||
Template = CreateTemplate(), |
|||
YearVisible = false |
|||
}; |
|||
datePicker.ApplyTemplate(); |
|||
Threading.Dispatcher.UIThread.RunJobs(); |
|||
|
|||
var desc = datePicker.GetVisualDescendants(); |
|||
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
|
|||
TextBlock yearText = null; |
|||
TextBlock monthText = null; |
|||
TextBlock dayText = null; |
|||
Grid container = null; |
|||
|
|||
Assert.True(desc.ElementAt(1) is Button); |
|||
|
|||
container = (desc.ElementAt(1) as Button).Content as Grid; |
|||
Assert.True(container != null); |
|||
|
|||
for (int i = 0; i < container.Children.Count; i++) |
|||
{ |
|||
if (container.Children[i] is TextBlock tb && tb.Name == "YearText") |
|||
{ |
|||
yearText = tb; |
|||
} |
|||
else if (container.Children[i] is TextBlock tb1 && tb1.Name == "MonthText") |
|||
{ |
|||
monthText = tb1; |
|||
} |
|||
else if (container.Children[i] is TextBlock tb2 && tb2.Name == "DayText") |
|||
{ |
|||
dayText = tb2; |
|||
} |
|||
} |
|||
|
|||
DateTimeOffset value = new DateTimeOffset(2000, 10, 10, 0, 0, 0, TimeSpan.Zero); |
|||
datePicker.SelectedDate = value; |
|||
|
|||
Assert.False(dayText.Text == "day"); |
|||
Assert.False(monthText.Text == "month"); |
|||
Assert.False(yearText.Text == "year"); |
|||
Assert.False(datePicker.Classes.Contains(":hasnodate")); |
|||
|
|||
datePicker.SelectedDate = null; |
|||
|
|||
Assert.True(dayText.Text == "day"); |
|||
Assert.True(monthText.Text == "month"); |
|||
Assert.True(yearText.Text == "year"); |
|||
Assert.True(datePicker.Classes.Contains(":hasnodate")); |
|||
} |
|||
} |
|||
|
|||
private static TestServices Services => TestServices.MockThreadingInterface.With( |
|||
standardCursorFactory: Mock.Of<IStandardCursorFactory>()); |
|||
|
|||
private IControlTemplate CreateTemplate() |
|||
{ |
|||
return new FuncControlTemplate((control, scope) => |
|||
{ |
|||
var layoutRoot = new Grid |
|||
{ |
|||
Name = "LayoutRoot" |
|||
}.RegisterInNameScope(scope); |
|||
//Skip contentpresenter
|
|||
var flyoutButton = new Button |
|||
{ |
|||
Name = "FlyoutButton" |
|||
}.RegisterInNameScope(scope); |
|||
var contentGrid = new Grid |
|||
{ |
|||
Name = "ButtonContentGrid" |
|||
}.RegisterInNameScope(scope); |
|||
var dayText = new TextBlock |
|||
{ |
|||
Name = "DayText" |
|||
}.RegisterInNameScope(scope); |
|||
var monthText = new TextBlock |
|||
{ |
|||
Name = "MonthText" |
|||
}.RegisterInNameScope(scope); |
|||
var yearText = new TextBlock |
|||
{ |
|||
Name = "YearText" |
|||
}.RegisterInNameScope(scope); |
|||
var firstSpacer = new Rectangle |
|||
{ |
|||
Name = "FirstSpacer" |
|||
}.RegisterInNameScope(scope); |
|||
var secondSpacer = new Rectangle |
|||
{ |
|||
Name = "SecondSpacer" |
|||
}.RegisterInNameScope(scope); |
|||
|
|||
contentGrid.Children.AddRange(new IControl[] { dayText, monthText, yearText, firstSpacer, secondSpacer }); |
|||
flyoutButton.Content = contentGrid; |
|||
layoutRoot.Children.Add(flyoutButton); |
|||
return layoutRoot; |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Controls.UnitTests |
|||
{ |
|||
|
|||
public class SplitViewTests |
|||
{ |
|||
[Fact] |
|||
public void SplitView_PaneOpening_Should_Fire_Before_PaneOpened() |
|||
{ |
|||
var splitView = new SplitView(); |
|||
|
|||
bool handledOpening = false; |
|||
splitView.PaneOpening += (x, e) => |
|||
{ |
|||
handledOpening = true; |
|||
}; |
|||
|
|||
splitView.PaneOpened += (x, e) => |
|||
{ |
|||
Assert.True(handledOpening); |
|||
}; |
|||
|
|||
splitView.IsPaneOpen = true; |
|||
} |
|||
|
|||
[Fact] |
|||
public void SplitView_PaneClosing_Should_Fire_Before_PaneClosed() |
|||
{ |
|||
var splitView = new SplitView(); |
|||
splitView.IsPaneOpen = true; |
|||
|
|||
bool handledClosing = false; |
|||
splitView.PaneClosing += (x, e) => |
|||
{ |
|||
handledClosing = true; |
|||
}; |
|||
|
|||
splitView.PaneClosed += (x, e) => |
|||
{ |
|||
Assert.True(handledClosing); |
|||
}; |
|||
|
|||
splitView.IsPaneOpen = false; |
|||
} |
|||
|
|||
[Fact] |
|||
public void SplitView_Cancel_Close_Should_Prevent_Pane_From_Closing() |
|||
{ |
|||
var splitView = new SplitView(); |
|||
splitView.IsPaneOpen = true; |
|||
|
|||
splitView.PaneClosing += (x, e) => |
|||
{ |
|||
e.Cancel = true; |
|||
}; |
|||
|
|||
splitView.IsPaneOpen = false; |
|||
|
|||
Assert.True(splitView.IsPaneOpen); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,172 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using Avalonia.Controls.Shapes; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Platform; |
|||
using Avalonia.UnitTests; |
|||
using Avalonia.VisualTree; |
|||
using Moq; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Controls.UnitTests |
|||
{ |
|||
public class TimePickerTests |
|||
{ |
|||
[Fact(Skip = "FIX ME ASAP")] |
|||
public void SelectedTimeChanged_Should_Fire_When_SelectedTime_Set() |
|||
{ |
|||
using (UnitTestApplication.Start(Services)) |
|||
{ |
|||
bool handled = false; |
|||
TimePicker timePicker = new TimePicker(); |
|||
timePicker.SelectedTimeChanged += (s, e) => |
|||
{ |
|||
handled = true; |
|||
}; |
|||
TimeSpan value = TimeSpan.FromHours(10); |
|||
timePicker.SelectedTime = value; |
|||
Threading.Dispatcher.UIThread.RunJobs(); |
|||
Assert.True(handled); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Using_24HourClock_Should_Hide_Period() |
|||
{ |
|||
using (UnitTestApplication.Start(Services)) |
|||
{ |
|||
TimePicker timePicker = new TimePicker() |
|||
{ |
|||
ClockIdentifier = "12HourClock", |
|||
Template = CreateTemplate() |
|||
}; |
|||
timePicker.ApplyTemplate(); |
|||
|
|||
var desc = timePicker.GetVisualDescendants(); |
|||
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
|
|||
Grid container = null; |
|||
|
|||
Assert.True(desc.ElementAt(1) is Button); |
|||
|
|||
container = (desc.ElementAt(1) as Button).Content as Grid; |
|||
Assert.True(container != null); |
|||
|
|||
var periodTextHost = container.Children[4] as Border; |
|||
Assert.True(periodTextHost != null); |
|||
Assert.True(periodTextHost.IsVisible); |
|||
|
|||
timePicker.ClockIdentifier = "24HourClock"; |
|||
Assert.False(periodTextHost.IsVisible); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void SelectedTime_null_Should_Use_Placeholders() |
|||
{ |
|||
using (UnitTestApplication.Start(Services)) |
|||
{ |
|||
TimePicker timePicker = new TimePicker() |
|||
{ |
|||
Template = CreateTemplate() |
|||
}; |
|||
timePicker.ApplyTemplate(); |
|||
|
|||
var desc = timePicker.GetVisualDescendants(); |
|||
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
|
|||
Grid container = null; |
|||
|
|||
Assert.True(desc.ElementAt(1) is Button); |
|||
|
|||
container = (desc.ElementAt(1) as Button).Content as Grid; |
|||
Assert.True(container != null); |
|||
|
|||
var hourTextHost = container.Children[0] as Border; |
|||
Assert.True(hourTextHost != null); |
|||
var hourText = hourTextHost.Child as TextBlock; |
|||
var minuteTextHost = container.Children[2] as Border; |
|||
Assert.True(minuteTextHost != null); |
|||
var minuteText = minuteTextHost.Child as TextBlock; |
|||
|
|||
TimeSpan ts = TimeSpan.FromHours(10); |
|||
timePicker.SelectedTime = ts; |
|||
Assert.False(hourText.Text == "hour"); |
|||
Assert.False(minuteText.Text == "minute"); |
|||
|
|||
timePicker.SelectedTime = null; |
|||
Assert.True(hourText.Text == "hour"); |
|||
Assert.True(minuteText.Text == "minute"); |
|||
} |
|||
} |
|||
|
|||
|
|||
private static TestServices Services => TestServices.MockThreadingInterface.With( |
|||
standardCursorFactory: Mock.Of<IStandardCursorFactory>()); |
|||
|
|||
private IControlTemplate CreateTemplate() |
|||
{ |
|||
return new FuncControlTemplate((control, scope) => |
|||
{ |
|||
var layoutRoot = new Grid |
|||
{ |
|||
Name = "LayoutRoot" |
|||
}.RegisterInNameScope(scope); |
|||
//Skip contentpresenter
|
|||
var flyoutButton = new Button |
|||
{ |
|||
Name = "FlyoutButton" |
|||
}.RegisterInNameScope(scope); |
|||
var contentGrid = new Grid |
|||
{ |
|||
Name = "FlyoutButtonContentGrid" |
|||
}.RegisterInNameScope(scope); |
|||
|
|||
var firstPickerHost = new Border |
|||
{ |
|||
Name = "FirstPickerHost", |
|||
Child = new TextBlock |
|||
{ |
|||
Name = "HourTextBlock" |
|||
}.RegisterInNameScope(scope) |
|||
}.RegisterInNameScope(scope); |
|||
Grid.SetColumn(firstPickerHost, 0); |
|||
|
|||
var secondPickerHost = new Border |
|||
{ |
|||
Name = "SecondPickerHost", |
|||
Child = new TextBlock |
|||
{ |
|||
Name = "MinuteTextBlock" |
|||
}.RegisterInNameScope(scope) |
|||
}.RegisterInNameScope(scope); |
|||
Grid.SetColumn(secondPickerHost, 2); |
|||
|
|||
var thirdPickerHost = new Border |
|||
{ |
|||
Name = "ThirdPickerHost", |
|||
Child = new TextBlock |
|||
{ |
|||
Name = "PeriodTextBlock" |
|||
}.RegisterInNameScope(scope) |
|||
}.RegisterInNameScope(scope); |
|||
Grid.SetColumn(thirdPickerHost, 4); |
|||
|
|||
var firstSpacer = new Rectangle |
|||
{ |
|||
Name = "FirstColumnDivider" |
|||
}.RegisterInNameScope(scope); |
|||
Grid.SetColumn(firstSpacer, 1); |
|||
|
|||
var secondSpacer = new Rectangle |
|||
{ |
|||
Name = "SecondColumnDivider" |
|||
}.RegisterInNameScope(scope); |
|||
Grid.SetColumn(secondSpacer, 3); |
|||
|
|||
contentGrid.Children.AddRange(new IControl[] { firstPickerHost, firstSpacer, secondPickerHost, secondSpacer, thirdPickerHost }); |
|||
flyoutButton.Content = contentGrid; |
|||
layoutRoot.Children.Add(flyoutButton); |
|||
return layoutRoot; |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue