From c65c9fb7c69936430b96cc10711bc6439773c5be Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 7 Jun 2015 21:02:23 +0200 Subject: [PATCH] Show access keys in menus. Currently always shown, whether user pressed alt/f10 or not. --- Perspex.Controls/Perspex.Controls.csproj | 1 + Perspex.Controls/Presenters/TextPresenter.cs | 4 +- Perspex.Controls/Primitives/AccessText.cs | 85 +++++++++++++++++ Perspex.Controls/TextBlock.cs | 98 ++++++++++++++++++-- Perspex.Themes.Default/MenuItemStyle.cs | 14 ++- TestApplication/Program.cs | 26 +++--- 6 files changed, 206 insertions(+), 22 deletions(-) create mode 100644 Perspex.Controls/Primitives/AccessText.cs diff --git a/Perspex.Controls/Perspex.Controls.csproj b/Perspex.Controls/Perspex.Controls.csproj index 2dcb64d1b5..062bf4a636 100644 --- a/Perspex.Controls/Perspex.Controls.csproj +++ b/Perspex.Controls/Perspex.Controls.csproj @@ -36,6 +36,7 @@ 4 + diff --git a/Perspex.Controls/Presenters/TextPresenter.cs b/Perspex.Controls/Presenters/TextPresenter.cs index a0776dad02..f6ff4abd46 100644 --- a/Perspex.Controls/Presenters/TextPresenter.cs +++ b/Perspex.Controls/Presenters/TextPresenter.cs @@ -140,9 +140,9 @@ namespace Perspex.Controls.Presenters } } - protected override FormattedText CreateFormattedText() + protected override FormattedText CreateFormattedText(Size constraint) { - var result = base.CreateFormattedText(); + var result = base.CreateFormattedText(constraint); var selectionStart = this.SelectionStart; var selectionEnd = this.SelectionEnd; var start = Math.Min(selectionStart, selectionEnd); diff --git a/Perspex.Controls/Primitives/AccessText.cs b/Perspex.Controls/Primitives/AccessText.cs new file mode 100644 index 0000000000..2e74bfcc4d --- /dev/null +++ b/Perspex.Controls/Primitives/AccessText.cs @@ -0,0 +1,85 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls.Primitives +{ + using Perspex.Media; + + /// + /// A text block that displays a character prefixed with an underscore as an access key. + /// + public class AccessText : TextBlock + { + /// + /// Renders the to a drawing context. + /// + /// The drawing context. + public override void Render(IDrawingContext context) + { + base.Render(context); + + int underscore = this.Text?.IndexOf('_') ?? -1; + + if (underscore != -1) + { + var rect = this.FormattedText.HitTestTextPosition(underscore); + var offset = new Vector(0, -0.5); + context.DrawLine( + new Pen(this.Foreground, 1), + rect.BottomLeft + offset, + rect.BottomRight + offset); + } + } + + /// + /// Creates the used to render the text. + /// + /// The constraint of the text. + /// A object. + protected override FormattedText CreateFormattedText(Size constraint) + { + var result = new FormattedText( + this.StripAccessKey(this.Text), + this.FontFamily, + this.FontSize, + this.FontStyle, + this.TextAlignment, + this.FontWeight); + result.Constraint = constraint; + return result; + } + + /// + /// Measures the control. + /// + /// The available size for the control. + /// The desired size. + protected override Size MeasureOverride(Size availableSize) + { + var result = base.MeasureOverride(availableSize); + return result.WithHeight(result.Height + 1); + } + + /// + /// Returns a string with the first underscore stripped. + /// + /// The text. + /// The text with the first underscore stripped. + private string StripAccessKey(string text) + { + var position = text.IndexOf('_'); + + if (position == -1) + { + return text; + } + else + { + return text.Substring(0, position) + text.Substring(position + 1); + } + } + } +} diff --git a/Perspex.Controls/TextBlock.cs b/Perspex.Controls/TextBlock.cs index 7c03c3fdd7..911ed1727e 100644 --- a/Perspex.Controls/TextBlock.cs +++ b/Perspex.Controls/TextBlock.cs @@ -11,39 +11,78 @@ namespace Perspex.Controls using System.Reactive.Linq; using Perspex.Media; + /// + /// A control that displays a block of text. + /// public class TextBlock : Control { + /// + /// Defines the property. + /// public static readonly PerspexProperty BackgroundProperty = Border.BackgroundProperty.AddOwner(); + /// + /// Defines the property. + /// public static readonly PerspexProperty FontFamilyProperty = PerspexProperty.RegisterAttached("FontFamily", inherits: true); + /// + /// Defines the property. + /// public static readonly PerspexProperty FontSizeProperty = PerspexProperty.RegisterAttached("FontSize", inherits: true); + /// + /// Defines the property. + /// public static readonly PerspexProperty FontStyleProperty = PerspexProperty.RegisterAttached("FontStyle", inherits: true); + /// + /// Defines the property. + /// public static readonly PerspexProperty FontWeightProperty = PerspexProperty.RegisterAttached("FontWeight", inherits: true, defaultValue: FontWeight.Normal); + /// + /// Defines the property. + /// public static readonly PerspexProperty ForegroundProperty = PerspexProperty.RegisterAttached("Foreground", new SolidColorBrush(0xff000000), inherits: true); + /// + /// Defines the property. + /// public static readonly PerspexProperty TextProperty = PerspexProperty.Register("Text"); - public static readonly PerspexProperty TextWrappingProperty = - PerspexProperty.Register("TextWrapping"); - + /// + /// Defines the property. + /// public static readonly PerspexProperty TextAlignmentProperty = PerspexProperty.Register("TextAlignment"); + /// + /// Defines the property. + /// + public static readonly PerspexProperty TextWrappingProperty = + PerspexProperty.Register("TextWrapping"); + + /// + /// The formatted text used for rendering. + /// private FormattedText formattedText; + /// + /// Stores the last constraint passed to MeasureOverride. + /// private Size constraint; + /// + /// Initializes a new instance of the class. + /// public TextBlock() { Observable.Merge( @@ -57,73 +96,107 @@ namespace Perspex.Controls }); } + /// + /// Gets or sets a brush used to paint the control's background. + /// public Brush Background { get { return this.GetValue(BackgroundProperty); } set { this.SetValue(BackgroundProperty, value); } } + /// + /// Gets or sets the text. + /// public string Text { get { return this.GetValue(TextProperty); } set { this.SetValue(TextProperty, value); } } + /// + /// Gets or sets the font family. + /// public string FontFamily { get { return this.GetValue(FontFamilyProperty); } set { this.SetValue(FontFamilyProperty, value); } } + /// + /// Gets or sets the font size. + /// public double FontSize { get { return this.GetValue(FontSizeProperty); } set { this.SetValue(FontSizeProperty, value); } } + /// + /// Gets or sets the font style. + /// public FontStyle FontStyle { get { return this.GetValue(FontStyleProperty); } set { this.SetValue(FontStyleProperty, value); } } + /// + /// Gets or sets the font weight. + /// public FontWeight FontWeight { get { return this.GetValue(FontWeightProperty); } set { this.SetValue(FontWeightProperty, value); } } + /// + /// Gets or sets a brush used to paint the text. + /// public Brush Foreground { get { return this.GetValue(ForegroundProperty); } set { this.SetValue(ForegroundProperty, value); } } + /// + /// Gets the used to render the text. + /// public FormattedText FormattedText { get { if (this.formattedText == null) { - this.formattedText = this.CreateFormattedText(); + this.formattedText = this.CreateFormattedText(this.constraint); } return this.formattedText; } } + /// + /// Gets or sets the control's text wrapping mode. + /// public TextWrapping TextWrapping { get { return this.GetValue(TextWrappingProperty); } set { this.SetValue(TextWrappingProperty, value); } } + /// + /// Gets or sets the text alignment. + /// public TextAlignment TextAlignment { get { return this.GetValue(TextAlignmentProperty); } set { this.SetValue(TextAlignmentProperty, value); } } + /// + /// Renders the to a drawing context. + /// + /// The drawing context. public override void Render(IDrawingContext context) { Brush background = this.Background; @@ -137,7 +210,12 @@ namespace Perspex.Controls context.DrawText(this.Foreground, new Point(), this.FormattedText); } - protected virtual FormattedText CreateFormattedText() + /// + /// Creates the used to render the text. + /// + /// The constraint of the text. + /// A object. + protected virtual FormattedText CreateFormattedText(Size constraint) { var result = new FormattedText( this.Text, @@ -146,10 +224,13 @@ namespace Perspex.Controls this.FontStyle, this.TextAlignment, this.FontWeight); - result.Constraint = this.constraint; + result.Constraint = constraint; return result; } + /// + /// Invalidates . + /// protected void InvalidateFormattedText() { if (this.formattedText != null) @@ -162,6 +243,11 @@ namespace Perspex.Controls this.InvalidateMeasure(); } + /// + /// Measures the control. + /// + /// The available size for the control. + /// The desired size. protected override Size MeasureOverride(Size availableSize) { if (!string.IsNullOrEmpty(this.Text)) diff --git a/Perspex.Themes.Default/MenuItemStyle.cs b/Perspex.Themes.Default/MenuItemStyle.cs index 3c2241218e..4733b60987 100644 --- a/Perspex.Themes.Default/MenuItemStyle.cs +++ b/Perspex.Themes.Default/MenuItemStyle.cs @@ -6,17 +6,21 @@ namespace Perspex.Themes.Default { + using System.Linq; using Perspex.Controls; using Perspex.Controls.Presenters; + using Perspex.Controls.Primitives; using Perspex.Controls.Shapes; using Perspex.Input; using Perspex.Layout; using Perspex.Media; using Perspex.Styling; - using System.Linq; public class MenuItemStyle : Styles { + private static readonly DataTemplate AccessKeyDataTemplate = + new DataTemplate(x => new AccessText { Text = x }); + public MenuItemStyle() { this.AddRange(new[] @@ -79,6 +83,10 @@ namespace Perspex.Themes.Default { new ContentPresenter { + DataTemplates = new DataTemplates + { + AccessKeyDataTemplate, + }, [~ContentPresenter.ContentProperty] = control[~MenuItem.HeaderProperty], [~ContentPresenter.MarginProperty] = control[~MenuItem.PaddingProperty], [Grid.ColumnProperty] = 1, @@ -171,6 +179,10 @@ namespace Perspex.Themes.Default }, new ContentPresenter { + DataTemplates = new DataTemplates + { + AccessKeyDataTemplate, + }, VerticalAlignment = VerticalAlignment.Center, [~ContentPresenter.ContentProperty] = control[~MenuItem.HeaderProperty], [~ContentPresenter.MarginProperty] = control[~MenuItem.PaddingProperty], diff --git a/TestApplication/Program.cs b/TestApplication/Program.cs index 1813d0db38..09f2a6f01e 100644 --- a/TestApplication/Program.cs +++ b/TestApplication/Program.cs @@ -144,12 +144,12 @@ namespace TestApplication { new MenuItem { - Header = "File", + Header = "_File", Items = new[] { new MenuItem { - Header = "Open...", + Header = "_Open...", Icon = new Image { Source = new Bitmap("github_icon.png"), @@ -157,57 +157,57 @@ namespace TestApplication }, new MenuItem { - Header = "Save", + Header = "_Save", Items = new[] { new MenuItem { - Header = "Sub Item 1", + Header = "Sub Item _1", }, new MenuItem { - Header = "Sub Item 2", + Header = "Sub Item _2", }, } }, new MenuItem { - Header = "Save As", + Header = "Save _As", Items = new[] { new MenuItem { - Header = "Sub Item 1", + Header = "Sub Item _1", }, new MenuItem { - Header = "Sub Item 2", + Header = "Sub Item _2", }, } }, new MenuItem { - Header = "Exit", + Header = "E_xit", Command = testCommand, }, } }, new MenuItem { - Header = "Edit", + Header = "_Edit", Items = new[] { new MenuItem { - Header = "Cut", + Header = "Cu_t", }, new MenuItem { - Header = "Copy", + Header = "_Copy", }, new MenuItem { - Header = "Paste", + Header = "_Paste", }, } }