diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..585d15e64 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,7 @@ +-- +### Environment + +- Fluent.Ribbon __v?.?.?__ +- Theme __?__ +- Windows __?__ +- .NET Framework __?.?__ diff --git a/.gitignore b/.gitignore index ed296cee0..5e1c8efc9 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ publish/ # Generated files Fluent.Ribbon/Themes/**/Generic.xaml packages/ +.tmp # Allowed files !Fluent.Ribbon/Themes/Generic.xaml diff --git a/Changelog.md b/Changelog.md index 850626692..fe4cae278 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,9 @@ ## 4.0.0 (preview) +- ### Notices + - **As of now the Office 2010 and Windows 8 themes will be removed in version 5.0. You can vote for this at [Future direction of this library](../../issues/282).** + - ### Breaking changes - New nuget dependency: [ControlzEx](https://www.nuget.org/packages/ControlzEx) - BorderlessWindowBehavior was replaced by WindowChromeBehavior from [ControlzEx](https://github.com/ControlzEx/ControlzEx). @@ -9,7 +12,7 @@ This behavior is initialized in code behind (InitializeWindowChromeBehavior) and shows which properties of RibbonWindow can be used to control the behavior. - SaveWindowPosition and WindowSettingBehavior were removed [#196](../../issues/196) - GlassBorderThickness was renamed to GlassFrameThickness to be consitent with WindowChrome and WindowChromeBehavior [#209](../../issues/209) - - FluentTest project was renamed to Fluent.Showcase [#212](../../issues/212) + - FluentTest project was renamed to Fluent.Ribbon.Showcase [#212](../../issues/212) - ### Development/Contributing changes - We switched to Visual Studio 2015 so we can use nameof etc. [#219](../../issues/219) @@ -35,20 +38,31 @@ - [#240](../../issues/240) - Backstage closes when popup is dismissed inside backstage - [#241](../../issues/241) - Keytips should be cancelled if Alt+Num0 is pressed - [#244](../../issues/244) - KeyTip not working for childs of ContentPresenter - - [#246](../../issues/246) - Bind RibbonGroupBox.DataContext on QuickAccessToolBar - - [#251](../../issues/251) - Changing RibbonStatusBar height to 23 and RibbonStatusBarItem foreground to BackstageFontBrush + - [#246](../../issues/246) - Bind RibbonGroupBox.DataContext on QuickAccessToolBar (thanks to @nishy2000) + - [#251](../../issues/251) - Changing RibbonStatusBar height to 23 and RibbonStatusBarItem foreground to BackstageFontBrush (thanks to @maurosampietro) - [#254](../../issues/254) - Basic fix for KeyTips not working when focus is inside a WinForms control - [#256](../../issues/256) - ComboBox items don't update properly on ItemsSource binding source collection changes + - [#255](../../issues/255) - Submenus don't show scroll viewer if items exceed the available space on screen (thanks to @floele-sp) + - [#257](../../issues/257) - Windows8 RibbonWindowTitleTextGlowBackground was missing (thanks to @maurosampietro) + - [#263](../../issues/263) - Changing theme from backstage is broken + - [#267](../../issues/267) - maximizing in Win 10 + - [#269](../../issues/269) - Show underscore of header text on RibbonTabItem + - [#272](../../issues/272) - Changing RibbonThemeColorBrush does not change background of ItemsPanel in Backstage + - [#274](../../issues/274) - RadioButton Icon and LargeIcon + - [#280](../../issues/280) - Keytips of the Ribbon overlay StartScreen + - [#284](../../issues/284) - Overriding width of button does not work as it should + - [#285](../../issues/285) - MaterialDesign DialogHost issue with FluentRibbon - OpenBackstage command was not acting on the correct backstage in a multiple backstage scenario (thanks to @maurosampietro) - ### Enhancements - - [#120](../../issues/120) - Adding short-cuts or additional information to Application Menu Item (How to improve) + - [#120](../../issues/120) - Adding short-cuts or additional information to Application Menu Item - [#185](../../issues/185) - Major refactoring of how WindowChrome is used - [#194](../../issues/194) - There should be an option to disable animations in the whole control - [#205](../../issues/205) - Fluent Spinner handles Format="P0" incorrectly. - [#207](../../issues/207) - Enable DragMove on unused RibbonTabControl space like in Office 2013 - - [#230](../../issues/230) - Option to disable the "Minimize"-Ribbon Button & Behavior + - [#230](../../issues/230) - Option to disable the "Minimize"-Ribbon Button & Behavior (thanks to @robertmuehsig) - [#242](../../issues/242) - Add start screen like in office 2013 and upwards + - [#258](../../issues/258) - Refactoring of KeyTipService and KeyTipAdorner (merged with [#264](../../issues/264)) ## 3.6.1 diff --git a/Dev.NuGet.Config b/Dev.NuGet.Config new file mode 100644 index 000000000..3d6a5a7bf --- /dev/null +++ b/Dev.NuGet.Config @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Fluent.Ribbon.Showcase/TestContent.xaml b/Fluent.Ribbon.Showcase/TestContent.xaml index 97f773cd8..c04eb542f 100644 --- a/Fluent.Ribbon.Showcase/TestContent.xaml +++ b/Fluent.Ribbon.Showcase/TestContent.xaml @@ -42,6 +42,17 @@ + + @@ -143,6 +154,47 @@ + + + + + Office 2010 Silver + Office 2010 Black + Office 2010 Blue + Office 2013 White + Windows8 White + + + + + + + + + + + + + + + + + + + + + + + - - - - @@ -1249,6 +1292,7 @@ Icon="Images\Green.png" LargeIcon="Images\GreenLarge.png"> - Test System.Windows.Controls.TextBox + + + Test System.Windows.Controls.TextBox + diff --git a/Fluent.Ribbon.Showcase/TestContent.xaml.cs b/Fluent.Ribbon.Showcase/TestContent.xaml.cs index ebd69b48c..c0c4867d2 100644 --- a/Fluent.Ribbon.Showcase/TestContent.xaml.cs +++ b/Fluent.Ribbon.Showcase/TestContent.xaml.cs @@ -88,8 +88,8 @@ public Button CreateRibbonButton() { Command = fooCommand1.ItemCommand, Header = "Foo", - Icon = new BitmapImage(new Uri(@"Images\Green.png", UriKind.Relative)), - LargeIcon = new BitmapImage(new Uri(@"Images\GreenLarge.png", UriKind.Relative)), + Icon = new BitmapImage(new Uri(@"pack://application:,,,/Fluent.Ribbon.Showcase;component/Images/Green.png", UriKind.Absolute)), + LargeIcon = new BitmapImage(new Uri(@"pack://application:,,,/Fluent.Ribbon.Showcase;component/Images/GreenLarge.png", UriKind.Absolute)), }; this.CommandBindings.Add(fooCommand1.ItemCommandBinding); diff --git a/Fluent.Ribbon.Showcase/ViewModels/ColorViewModel.cs b/Fluent.Ribbon.Showcase/ViewModels/ColorViewModel.cs index f023769e9..a2b6c37f4 100644 --- a/Fluent.Ribbon.Showcase/ViewModels/ColorViewModel.cs +++ b/Fluent.Ribbon.Showcase/ViewModels/ColorViewModel.cs @@ -1,5 +1,6 @@ namespace FluentTest.ViewModels { + using System.Windows; using System.Windows.Media; public class ColorViewModel : ViewModel @@ -7,14 +8,10 @@ public class ColorViewModel : ViewModel private Color standardColor; private Color highlightColor; - private readonly Color[] themeColors = { Colors.Red, Colors.Green, Colors.Blue, Colors.White, Colors.Black, Colors.Purple }; - private Color themeColor; - public ColorViewModel() { this.StandardColor = Colors.Black; this.HighlightColor = Colors.Yellow; - this.ThemeColor = Colors.Blue; } public Color StandardColor @@ -23,7 +20,7 @@ public Color StandardColor set { this.standardColor = value; - this.OnPropertyChanged("StandardColor"); + this.OnPropertyChanged(nameof(this.StandardColor)); } } @@ -33,22 +30,19 @@ public Color HighlightColor set { this.highlightColor = value; - this.OnPropertyChanged("HighlightColor"); + this.OnPropertyChanged(nameof(this.HighlightColor)); } } - public Color[] ThemeColors - { - get { return this.themeColors; } - } + public Color[] ThemeColors { get; } = { Colors.Red, Colors.Green, Colors.Blue, Colors.White, Colors.Black, Colors.Purple }; public Color ThemeColor { - get { return this.themeColor; } + get { return ((SolidColorBrush)Application.Current.Resources["RibbonThemeColorBrush"])?.Color ?? Colors.Pink; } set { - this.themeColor = value; - this.OnPropertyChanged("ThemeColor"); + Application.Current.Resources["RibbonThemeColorBrush"] = new SolidColorBrush(value); + this.OnPropertyChanged(nameof(this.ThemeColor)); } } } diff --git a/Fluent.Ribbon.Showcase/ViewModels/GalleryItemViewModel.cs b/Fluent.Ribbon.Showcase/ViewModels/GalleryItemViewModel.cs index ff18d965a..abe9b1e97 100644 --- a/Fluent.Ribbon.Showcase/ViewModels/GalleryItemViewModel.cs +++ b/Fluent.Ribbon.Showcase/ViewModels/GalleryItemViewModel.cs @@ -1,15 +1,44 @@ namespace FluentTest.ViewModels { - public class GalleryItemViewModel + public class GalleryItemViewModel : ViewModel { + private string text; + private string group; + public GalleryItemViewModel(string group, string text) { this.Group = group; this.Text = text; } - public string Text { get; set; } + public string Text + { + get { return this.text; } + + set + { + if (value == this.text) + { + return; + } + this.text = value; + this.OnPropertyChanged(nameof(this.Text)); + } + } - public string Group { get; set; } + public string Group + { + get { return this.group; } + + set + { + if (value == this.group) + { + return; + } + this.group = value; + this.OnPropertyChanged(nameof(this.Group)); + } + } } } \ No newline at end of file diff --git a/Fluent.Ribbon.Showcase/ViewModels/ViewModel.cs b/Fluent.Ribbon.Showcase/ViewModels/ViewModel.cs index d6c113f4a..8c2b0757b 100644 --- a/Fluent.Ribbon.Showcase/ViewModels/ViewModel.cs +++ b/Fluent.Ribbon.Showcase/ViewModels/ViewModel.cs @@ -10,10 +10,7 @@ public class ViewModel : INotifyPropertyChanged [NotifyPropertyChangedInvocator] protected void OnPropertyChanged(string propertyName) { - if (this.PropertyChanged != null) - { - this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); - } + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } } \ No newline at end of file diff --git a/Fluent.Ribbon.nuspec b/Fluent.Ribbon.nuspec index 8712be561..946a9bd38 100644 --- a/Fluent.Ribbon.nuspec +++ b/Fluent.Ribbon.nuspec @@ -14,7 +14,7 @@ false ribbon fluent ribbon ribbonwindow office2010 office2013 windows8 backstage - + @@ -28,4 +28,4 @@ - + \ No newline at end of file diff --git a/Fluent.Ribbon/Adorners/KeyTipAdorner.cs b/Fluent.Ribbon/Adorners/KeyTipAdorner.cs index b1ad10722..c19685423 100644 --- a/Fluent.Ribbon/Adorners/KeyTipAdorner.cs +++ b/Fluent.Ribbon/Adorners/KeyTipAdorner.cs @@ -1,19 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Windows; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Interop; -using System.Windows.Media; -using System.Windows.Threading; - -namespace Fluent +namespace Fluent { + using System; + using System.Collections.Generic; using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Windows; using System.Windows.Controls; + using System.Windows.Data; + using System.Windows.Documents; + using System.Windows.Media; using Fluent.Internal; /// @@ -299,58 +295,17 @@ public void Terminate() this.Detach(); - if (this.parentAdorner != null) - { - this.parentAdorner.Terminate(); - } + this.parentAdorner?.Terminate(); - if (this.childAdorner != null) - { - this.childAdorner.Terminate(); - } + this.childAdorner?.Terminate(); - if (this.Terminated != null) - { - this.Terminated(this, EventArgs.Empty); - } + this.Terminated?.Invoke(this, EventArgs.Empty); this.Log("Termination"); } #endregion - #region Event Handlers - - //[SuppressMessage("Microsoft.Maintainability", "CA1502")] - //private void OnPreviewKeyDown(object sender, KeyEventArgs e) - //{ - // this.Log("Key Down {0} ({1})", e.Key, e.OriginalSource); - - // if (e.IsRepeat - // || this.Visibility == Visibility.Hidden) - // { - // return; - // } - - // if ((this.AdornedElement is ContextMenu == false && this.AdornedElement is MenuItem == false) - // && ((e.Key == Key.Left) - // || (e.Key == Key.Right) - // || (e.Key == Key.Up) - // || (e.Key == Key.Down) - // || (e.Key == Key.Enter) - // || (e.Key == Key.Tab))) - // { - // this.Visibility = Visibility.Hidden; - // } - // else if (e.Key == Key.Escape) - // { - // this.Back(); - // e.Handled = true; - // } - //} - - #endregion - #region Static Methods private static AdornerLayer GetAdornerLayer(UIElement element) @@ -398,10 +353,7 @@ private static UIElement GetTopLevelElement(UIElement element) public void Back() { var control = this.keyTipElementContainer as IKeyTipedControl; - if (control != null) - { - control.OnKeyTipBack(); - } + control?.OnKeyTipBack(); if (this.parentAdorner != null) { @@ -435,16 +387,10 @@ public bool Forward(string keys, bool click) return true; } - // Forward to the next element - private void Forward(UIElement element) - { - this.Forward(element, true); - } - // Forward to the next element private void Forward(UIElement element, bool click) { - this.Log("Forwarding"); + this.Log("Forwarding to {0}", element); this.Detach(); @@ -529,7 +475,7 @@ public bool ContainsKeyTipStartingWith(string keys) // Hide / unhide keytips relative matching to entered keys internal void FilterKeyTips(string keys) { - this.Log("FilterKeyTips"); + this.Log("FilterKeyTips with {0}", keys); // Backup current visibility of key tips for (var i = 0; i < this.backupedVisibilities.Length; i++) @@ -788,25 +734,17 @@ private void UpdateKeyTipPositions() elementSize.Height / 2.0 + 2, elementSize.Height / 2.0 + 2), this.AdornedElement); } - else if (this.associatedElements[i] is BackstageTabItem) - { - // BackstageButton Exclusive Placement - var keyTipSize = this.keyTips[i].DesiredSize; - var elementSize = this.associatedElements[i].DesiredSize; - this.keyTipPositions[i] = this.associatedElements[i].TranslatePoint( - new Point( - 5, - elementSize.Height / 2.0 - keyTipSize.Height), this.AdornedElement); - } else if (((FrameworkElement)this.associatedElements[i]).Parent is BackstageTabControl) { // Backstage Items Exclusive Placement var keyTipSize = this.keyTips[i].DesiredSize; var elementSize = this.associatedElements[i].DesiredSize; - this.keyTipPositions[i] = this.associatedElements[i].TranslatePoint( + var parent = (UIElement)((FrameworkElement)this.associatedElements[i]).Parent; + var positionInParent = this.associatedElements[i].TranslatePoint(default(Point), parent); + this.keyTipPositions[i] = parent.TranslatePoint( new Point( - 20, - elementSize.Height / 2.0 - keyTipSize.Height), this.AdornedElement); + 5, + positionInParent.Y + (elementSize.Height / 2.0 - keyTipSize.Height)), this.AdornedElement); } else { @@ -939,10 +877,11 @@ private void Log(string format, params object[] args) var headeredControl = this.AdornedElement as IHeaderedControl; if (headeredControl != null) { - name += string.Format(" ({0})", headeredControl.Header); + name += $" ({headeredControl.Header})"; } - Debug.WriteLine("[" + name + "] " + string.Format(format, args), "KeyTipAdorner"); + var formatted = string.Format(format, args); + Debug.WriteLine($"{$"[{name}] "}{formatted}", "KeyTipAdorner"); } #endregion diff --git a/Fluent.Ribbon/AttachedProperties/RibbonProperties.cs b/Fluent.Ribbon/AttachedProperties/RibbonProperties.cs index 381ae511d..a8a5ec029 100644 --- a/Fluent.Ribbon/AttachedProperties/RibbonProperties.cs +++ b/Fluent.Ribbon/AttachedProperties/RibbonProperties.cs @@ -18,7 +18,7 @@ public class RibbonProperties /// public static readonly DependencyProperty TitleBarHeightProperty = DependencyProperty.RegisterAttached("TitleBarHeight", typeof(double), typeof(RibbonProperties), - new FrameworkPropertyMetadata(25D, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.Inherits)); + new FrameworkPropertyMetadata(27D, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.Inherits)); /// /// Sets TitleBarHeight for element diff --git a/Fluent.Ribbon/Controls/Backstage.cs b/Fluent.Ribbon/Controls/Backstage.cs index edc0a281a..2ceefd262 100644 --- a/Fluent.Ribbon/Controls/Backstage.cs +++ b/Fluent.Ribbon/Controls/Backstage.cs @@ -14,7 +14,7 @@ namespace Fluent using System.Windows.Media; using System.Windows.Threading; using System.Threading; - using System.Threading.Tasks; + using System.Threading.Tasks; using Fluent.Extensions; using Fluent.Internal; @@ -65,7 +65,7 @@ public Duration HideAnimationDuration /// Using a DependencyProperty as the backing store for HideAnimationDuration. /// This enables animation, styling, binding, etc... /// - public static readonly DependencyProperty HideAnimationDurationProperty = + public static readonly DependencyProperty HideAnimationDurationProperty = DependencyProperty.Register(nameof(HideAnimationDuration), typeof(Duration), typeof(Backstage), new PropertyMetadata(null)); /// @@ -96,7 +96,7 @@ public bool IsOpenAnimationEnabled /// Using a DependencyProperty as the backing store for HideContextTabsOnOpen. /// This enables animation, styling, binding, etc... /// - public static readonly DependencyProperty HideContextTabsOnOpenProperty = + public static readonly DependencyProperty HideContextTabsOnOpenProperty = DependencyProperty.Register(nameof(HideContextTabsOnOpen), typeof(bool), typeof(Backstage), new PropertyMetadata(false)); /// @@ -112,7 +112,7 @@ public bool CloseOnEsc /// Using a DependencyProperty as the backing store for CloseOnEsc. /// This enables animation, styling, binding, etc... /// - public static readonly DependencyProperty CloseOnEscProperty = + public static readonly DependencyProperty CloseOnEscProperty = DependencyProperty.Register(nameof(CloseOnEsc), typeof(bool), typeof(Backstage), new PropertyMetadata(true)); private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) @@ -373,77 +373,34 @@ private void CreateAndAttachBackstageAdorner() return; } - FrameworkElement topLevelElement = null; + FrameworkElement elementToAdorn; if (DesignerProperties.GetIsInDesignMode(this)) { // TODO: in design mode it is required to use design time adorner - topLevelElement = (FrameworkElement)VisualTreeHelper.GetParent(this); + elementToAdorn = (FrameworkElement)VisualTreeHelper.GetParent(this); } else { - var mainWindow = Window.GetWindow(this); - if (mainWindow == null) - { - return; - } - - var content = mainWindow.Content; - - var fe = content as FrameworkElement; // Content may be an arbitrary .NET object when set using a databinding and using data templates. - - if (fe != null) - { - topLevelElement = fe; - } - else - { - // If Content is not a FrameworkElement we try to find the ContentPresenter - // containing the template to display the content. - var contentPresenter = UIHelper.FindVisualChild(mainWindow); - - if (contentPresenter != null - && ReferenceEquals(contentPresenter.Content, content)) - { - // set the root element of the template as the top level element. - topLevelElement = (FrameworkElement)VisualTreeHelper.GetChild(contentPresenter, 0); - } - } + elementToAdorn = UIHelper.GetParent(this); } - if (topLevelElement == null) + if (elementToAdorn == null) { return; - } - - var layer = this.GetAdornerLayer(); - - this.adorner = new BackstageAdorner(topLevelElement, this); - layer.Add(this.adorner); - - layer.CommandBindings.Add(new CommandBinding(RibbonCommands.OpenBackstage, HandleOpenBackstageCommandExecuted)); - } + } - private AdornerLayer GetAdornerLayer() - { var layer = AdornerLayer.GetAdornerLayer(this); - if (layer == null) - { - var parentVisual = this.Parent as Visual ?? this.GetParentRibbon(); - - if (parentVisual != null) - { - layer = AdornerLayer.GetAdornerLayer(parentVisual); - } - } - if (layer == null) { throw new Exception($"AdornerLayer could not be found for {this}."); } - return layer; + this.adorner = new BackstageAdorner(elementToAdorn, this); + layer.Add(this.adorner); + + layer.CommandBindings.Add(new CommandBinding(RibbonCommands.OpenBackstage, HandleOpenBackstageCommandExecuted)); } private static void HandleOpenBackstageCommandExecuted(object sender, ExecutedRoutedEventArgs args) @@ -629,7 +586,7 @@ private void CollapseWindowsFormsHosts(DependencyObject parent) if (frameworkElement != null) { - if (parent is HwndHost + if (parent is HwndHost && frameworkElement.Visibility != Visibility.Collapsed) { this.collapsedElements.Add(frameworkElement, frameworkElement.Visibility); @@ -656,7 +613,7 @@ protected override void OnKeyDown(KeyEventArgs e) return; } - if (e.Key == Key.Enter + if (e.Key == Key.Enter || e.Key == Key.Space) { if (this.IsFocused) @@ -677,6 +634,7 @@ private void HandleWindowKeyDown(object sender, KeyEventArgs e) { return; } + // only handle ESC when the backstage is open e.Handled = this.IsOpen; diff --git a/Fluent.Ribbon/Controls/BackstageTabControl.cs b/Fluent.Ribbon/Controls/BackstageTabControl.cs index 1b6e2259b..18aa387e7 100644 --- a/Fluent.Ribbon/Controls/BackstageTabControl.cs +++ b/Fluent.Ribbon/Controls/BackstageTabControl.cs @@ -10,6 +10,8 @@ namespace Fluent { + using Fluent.Internal; + /// /// Represents Backstage tab control. /// @@ -189,6 +191,23 @@ public Brush ItemsPanelBackground #endregion + /// + /// Gets or sets the + /// + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Backstage ParentBackstage + { + get { return (Backstage)this.GetValue(ParentBackstageProperty); } + set { this.SetValue(ParentBackstageProperty, value); } + } + + /// + /// for + /// + public static readonly DependencyProperty ParentBackstageProperty = + DependencyProperty.Register(nameof(ParentBackstage), typeof(Backstage), typeof(BackstageTabControl), new PropertyMetadata(null)); + #endregion #region Constructors @@ -200,11 +219,11 @@ public Brush ItemsPanelBackground static BackstageTabControl() { DefaultStyleKeyProperty.OverrideMetadata(typeof(BackstageTabControl), new FrameworkPropertyMetadata(typeof(BackstageTabControl))); - StyleProperty.OverrideMetadata(typeof(BackstageTabControl), new FrameworkPropertyMetadata(null, new CoerceValueCallback(OnCoerceStyle))); + StyleProperty.OverrideMetadata(typeof(BackstageTabControl), new FrameworkPropertyMetadata(null, OnCoerceStyle)); } // Coerce object style - static object OnCoerceStyle(DependencyObject d, object basevalue) + private static object OnCoerceStyle(DependencyObject d, object basevalue) { if (basevalue == null) { @@ -227,6 +246,19 @@ public BackstageTabControl() HasDropShadow = false }; this.ContextMenu.Opened += delegate { this.ContextMenu.IsOpen = false; }; + + this.Loaded += this.HandleLoaded; + this.Unloaded += this.HandleUnloaded; + } + + private void HandleLoaded(object sender, RoutedEventArgs e) + { + this.ParentBackstage = UIHelper.GetParent(this); + } + + private void HandleUnloaded(object sender, RoutedEventArgs e) + { + this.ParentBackstage = null; } #endregion diff --git a/Fluent.Ribbon/Controls/CheckBox.cs b/Fluent.Ribbon/Controls/CheckBox.cs index 093cd3537..6d487bb9e 100644 --- a/Fluent.Ribbon/Controls/CheckBox.cs +++ b/Fluent.Ribbon/Controls/CheckBox.cs @@ -123,9 +123,9 @@ private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedE /// /// Gets or sets button large icon /// - public ImageSource LargeIcon + public object LargeIcon { - get { return (ImageSource)this.GetValue(LargeIconProperty); } + get { return this.GetValue(LargeIconProperty); } set { this.SetValue(LargeIconProperty, value); } } @@ -133,9 +133,7 @@ public ImageSource LargeIcon /// Using a DependencyProperty as the backing store for SmallIcon. /// This enables animation, styling, binding, etc... /// - public static readonly DependencyProperty LargeIconProperty = - DependencyProperty.Register("LargeIcon", typeof(ImageSource), - typeof(CheckBox), new UIPropertyMetadata(null)); + public static readonly DependencyProperty LargeIconProperty = DependencyProperty.Register("LargeIcon", typeof(object), typeof(CheckBox), new UIPropertyMetadata(null)); #endregion diff --git a/Fluent.Ribbon/Controls/ComboBox.cs b/Fluent.Ribbon/Controls/ComboBox.cs index 61cc796dc..d25adfaa3 100644 --- a/Fluent.Ribbon/Controls/ComboBox.cs +++ b/Fluent.Ribbon/Controls/ComboBox.cs @@ -1,971 +1,971 @@ namespace Fluent { - using System; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Threading; - using System.Windows; - using System.Windows.Controls; - using System.Windows.Controls.Primitives; - using System.Windows.Data; - using System.Windows.Input; - using System.Windows.Media; - using System.Windows.Media.Imaging; - using System.Windows.Threading; - using Fluent.Internal; + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Threading; + using System.Windows; + using System.Windows.Controls; + using System.Windows.Controls.Primitives; + using System.Windows.Data; + using System.Windows.Input; + using System.Windows.Media; + using System.Windows.Media.Imaging; + using System.Windows.Threading; + using Fluent.Internal; /// /// Represents custom Fluent UI ComboBox /// [TemplatePart(Name = "PART_ResizeBothThumb", Type = typeof(Thumb))] - [TemplatePart(Name = "PART_ResizeVerticalThumb", Type = typeof(Thumb))] - public class ComboBox : System.Windows.Controls.ComboBox, IQuickAccessItemProvider, IRibbonControl, IDropDownControl - { - #region Fields + [TemplatePart(Name = "PART_ResizeVerticalThumb", Type = typeof(Thumb))] + public class ComboBox : System.Windows.Controls.ComboBox, IQuickAccessItemProvider, IRibbonControl, IDropDownControl + { + #region Fields - // Thumb to resize in both directions - private Thumb resizeBothThumb; - // Thumb to resize vertical - private Thumb resizeVerticalThumb; + // Thumb to resize in both directions + private Thumb resizeBothThumb; + // Thumb to resize vertical + private Thumb resizeVerticalThumb; - private IInputElement focusedElement; + private IInputElement focusedElement; - private Panel menuPanel; + private Panel menuPanel; - private Border dropDownBorder; - private Border contentBorder; + private Border dropDownBorder; + private Border contentBorder; - private ContentPresenter contentSite; + private ContentPresenter contentSite; - // Freezed image (created during snapping) - private Image snappedImage; + // Freezed image (created during snapping) + private Image snappedImage; - // Is visual currently snapped - private bool isSnapped; + // Is visual currently snapped + private bool isSnapped; - private GalleryPanel galleryPanel; + private GalleryPanel galleryPanel; - private ScrollViewer scrollViewer; + private ScrollViewer scrollViewer; - private bool canSizeY; + private bool canSizeY; - #endregion + #endregion - #region Properties + #region Properties - #region Size + #region Size - /// - /// Gets or sets Size for the element. - /// - public RibbonControlSize Size - { - get { return (RibbonControlSize)this.GetValue(SizeProperty); } - set { this.SetValue(SizeProperty, value); } - } - - /// - /// Using a DependencyProperty as the backing store for Size. - /// This enables animation, styling, binding, etc... - /// - public static readonly DependencyProperty SizeProperty = RibbonProperties.SizeProperty.AddOwner(typeof(ComboBox)); - - #endregion - - #region SizeDefinition - - /// - /// Gets or sets SizeDefinition for element. - /// - public RibbonControlSizeDefinition SizeDefinition - { - get { return (RibbonControlSizeDefinition)this.GetValue(SizeDefinitionProperty); } - set { this.SetValue(SizeDefinitionProperty, value); } - } - - /// - /// Using a DependencyProperty as the backing store for SizeDefinition. - /// This enables animation, styling, binding, etc... - /// - public static readonly DependencyProperty SizeDefinitionProperty = RibbonProperties.SizeDefinitionProperty.AddOwner(typeof(ComboBox)); - - #endregion - - #region KeyTip - - /// - /// Gets or sets KeyTip for element. - /// - public string KeyTip - { - get { return (string)this.GetValue(KeyTipProperty); } - set { this.SetValue(KeyTipProperty, value); } - } - - /// - /// Using a DependencyProperty as the backing store for Keys. - /// This enables animation, styling, binding, etc... - /// - public static readonly DependencyProperty KeyTipProperty = Fluent.KeyTip.KeysProperty.AddOwner(typeof(ComboBox)); - - #endregion - - /// - /// Gets drop down popup - /// - public Popup DropDownPopup { get; private set; } - - /// - /// Gets a value indicating whether context menu is opened - /// - public bool IsContextMenuOpened { get; set; } - - #region Header - - /// - /// Gets or sets element Text - /// - public object Header - { - get { return (string)this.GetValue(HeaderProperty); } - set { this.SetValue(HeaderProperty, value); } - } - - /// - /// Using a DependencyProperty as the backing store for Header. - /// This enables animation, styling, binding, etc... - /// - public static readonly DependencyProperty HeaderProperty = RibbonControl.HeaderProperty.AddOwner(typeof(ComboBox)); - - #endregion - - #region Icon - - /// - /// Gets or sets Icon for the element - /// - public object Icon - { - get { return (ImageSource)this.GetValue(IconProperty); } - set { this.SetValue(IconProperty, value); } - } - - /// - /// Using a DependencyProperty as the backing store for Icon. This enables animation, styling, binding, etc... - /// - public static readonly DependencyProperty IconProperty = RibbonControl.IconProperty.AddOwner(typeof(ComboBox), new UIPropertyMetadata(null, OnIconChanged)); - - private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var element = d as ComboBox; - var oldElement = e.OldValue as FrameworkElement; - if (oldElement != null) element.RemoveLogicalChild(oldElement); - var newElement = e.NewValue as FrameworkElement; - if (newElement != null) element.AddLogicalChild(newElement); - } - - #endregion - - #region Menu - - /// - /// Gets or sets menu to show in combo box bottom - /// - public RibbonMenu Menu - { - get { return (RibbonMenu)this.GetValue(MenuProperty); } - set { this.SetValue(MenuProperty, value); } - } - - /// - /// Using a DependencyProperty as the backing store for Menu. This enables animation, styling, binding, etc... - /// - public static readonly DependencyProperty MenuProperty = - DependencyProperty.Register("Menu", typeof(RibbonMenu), typeof(ComboBox), new UIPropertyMetadata(null)); - - #endregion - - #region InputWidth - - /// - /// Gets or sets width of the value input part of combobox - /// - public double InputWidth - { - get { return (double)this.GetValue(InputWidthProperty); } - set { this.SetValue(InputWidthProperty, value); } - } - - /// - /// Using a DependencyProperty as the backing store for InputWidth. This enables animation, styling, binding, etc... - /// - public static readonly DependencyProperty InputWidthProperty = - DependencyProperty.Register("InputWidth", typeof(double), typeof(ComboBox), new UIPropertyMetadata(double.NaN)); - - #endregion - - #region ItemHeight - - /// - /// Gets or sets items height - /// - public double ItemHeight - { - get { return (double)this.GetValue(ItemHeightProperty); } - set { this.SetValue(ItemHeightProperty, value); } - } - - /// - /// Using a DependencyProperty as the backing store for ItemHeight. This enables animation, styling, binding, etc... - /// - public static readonly DependencyProperty ItemHeightProperty = - DependencyProperty.Register("ItemHeight", typeof(double), typeof(ComboBox), new UIPropertyMetadata(double.NaN)); - - #endregion - - #region GroupBy - - /// - /// Gets or sets name of property which - /// will use to group items in the ComboBox. - /// - public string GroupBy - { - get { return (string)this.GetValue(GroupByProperty); } - set { this.SetValue(GroupByProperty, value); } - } - - /// - /// Using a DependencyProperty as the backing store for GroupBy. - /// This enables animation, styling, binding, etc... - /// - public static readonly DependencyProperty GroupByProperty = - DependencyProperty.Register("GroupBy", typeof(string), - typeof(ComboBox), new UIPropertyMetadata(null)); - - #endregion - - #region ResizeMode - - /// - /// Gets or sets context menu resize mode - /// - public ContextMenuResizeMode ResizeMode - { - get { return (ContextMenuResizeMode)this.GetValue(ResizeModeProperty); } - set { this.SetValue(ResizeModeProperty, value); } - } - - /// - /// Using a DependencyProperty as the backing store for ResizeMode. This enables animation, styling, binding, etc... - /// - public static readonly DependencyProperty ResizeModeProperty = - DependencyProperty.Register("ResizeMode", typeof(ContextMenuResizeMode), typeof(ComboBox), new UIPropertyMetadata(ContextMenuResizeMode.None)); - - #endregion - - #region Snapping - - /// - /// Snaps / Unsnaps the Visual - /// (remove visuals and substitute with freezed image) - /// - private bool IsSnapped - { - get { return this.isSnapped; } - set - { - if (value == this.isSnapped) return; - if (this.snappedImage == null) return; - if ((value) && (((int)this.contentSite.ActualWidth > 0) && ((int)this.contentSite.ActualHeight > 0))) - { - // Render the freezed image - RenderOptions.SetBitmapScalingMode(this.snappedImage, BitmapScalingMode.NearestNeighbor); - var renderTargetBitmap = new RenderTargetBitmap((int)this.contentSite.ActualWidth + (int)this.contentSite.Margin.Left + (int)this.contentSite.Margin.Right, - (int)this.contentSite.ActualHeight + (int)this.contentSite.Margin.Top + (int)this.contentSite.Margin.Bottom, 96, 96, - PixelFormats.Pbgra32); - renderTargetBitmap.Render(this.contentSite); - this.snappedImage.Source = renderTargetBitmap; - this.snappedImage.FlowDirection = this.FlowDirection; - /*snappedImage.Width = contentSite.ActualWidth; + /// + /// Gets or sets Size for the element. + /// + public RibbonControlSize Size + { + get { return (RibbonControlSize)this.GetValue(SizeProperty); } + set { this.SetValue(SizeProperty, value); } + } + + /// + /// Using a DependencyProperty as the backing store for Size. + /// This enables animation, styling, binding, etc... + /// + public static readonly DependencyProperty SizeProperty = RibbonProperties.SizeProperty.AddOwner(typeof(ComboBox)); + + #endregion + + #region SizeDefinition + + /// + /// Gets or sets SizeDefinition for element. + /// + public RibbonControlSizeDefinition SizeDefinition + { + get { return (RibbonControlSizeDefinition)this.GetValue(SizeDefinitionProperty); } + set { this.SetValue(SizeDefinitionProperty, value); } + } + + /// + /// Using a DependencyProperty as the backing store for SizeDefinition. + /// This enables animation, styling, binding, etc... + /// + public static readonly DependencyProperty SizeDefinitionProperty = RibbonProperties.SizeDefinitionProperty.AddOwner(typeof(ComboBox)); + + #endregion + + #region KeyTip + + /// + /// Gets or sets KeyTip for element. + /// + public string KeyTip + { + get { return (string)this.GetValue(KeyTipProperty); } + set { this.SetValue(KeyTipProperty, value); } + } + + /// + /// Using a DependencyProperty as the backing store for Keys. + /// This enables animation, styling, binding, etc... + /// + public static readonly DependencyProperty KeyTipProperty = Fluent.KeyTip.KeysProperty.AddOwner(typeof(ComboBox)); + + #endregion + + /// + /// Gets drop down popup + /// + public Popup DropDownPopup { get; private set; } + + /// + /// Gets a value indicating whether context menu is opened + /// + public bool IsContextMenuOpened { get; set; } + + #region Header + + /// + /// Gets or sets element Text + /// + public object Header + { + get { return (string)this.GetValue(HeaderProperty); } + set { this.SetValue(HeaderProperty, value); } + } + + /// + /// Using a DependencyProperty as the backing store for Header. + /// This enables animation, styling, binding, etc... + /// + public static readonly DependencyProperty HeaderProperty = RibbonControl.HeaderProperty.AddOwner(typeof(ComboBox)); + + #endregion + + #region Icon + + /// + /// Gets or sets Icon for the element + /// + public object Icon + { + get { return this.GetValue(IconProperty); } + set { this.SetValue(IconProperty, value); } + } + + /// + /// Using a DependencyProperty as the backing store for Icon. This enables animation, styling, binding, etc... + /// + public static readonly DependencyProperty IconProperty = RibbonControl.IconProperty.AddOwner(typeof(ComboBox), new UIPropertyMetadata(null, OnIconChanged)); + + private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var element = d as ComboBox; + var oldElement = e.OldValue as FrameworkElement; + if (oldElement != null) element.RemoveLogicalChild(oldElement); + var newElement = e.NewValue as FrameworkElement; + if (newElement != null) element.AddLogicalChild(newElement); + } + + #endregion + + #region Menu + + /// + /// Gets or sets menu to show in combo box bottom + /// + public RibbonMenu Menu + { + get { return (RibbonMenu)this.GetValue(MenuProperty); } + set { this.SetValue(MenuProperty, value); } + } + + /// + /// Using a DependencyProperty as the backing store for Menu. This enables animation, styling, binding, etc... + /// + public static readonly DependencyProperty MenuProperty = + DependencyProperty.Register("Menu", typeof(RibbonMenu), typeof(ComboBox), new UIPropertyMetadata(null)); + + #endregion + + #region InputWidth + + /// + /// Gets or sets width of the value input part of combobox + /// + public double InputWidth + { + get { return (double)this.GetValue(InputWidthProperty); } + set { this.SetValue(InputWidthProperty, value); } + } + + /// + /// Using a DependencyProperty as the backing store for InputWidth. This enables animation, styling, binding, etc... + /// + public static readonly DependencyProperty InputWidthProperty = + DependencyProperty.Register("InputWidth", typeof(double), typeof(ComboBox), new UIPropertyMetadata(double.NaN)); + + #endregion + + #region ItemHeight + + /// + /// Gets or sets items height + /// + public double ItemHeight + { + get { return (double)this.GetValue(ItemHeightProperty); } + set { this.SetValue(ItemHeightProperty, value); } + } + + /// + /// Using a DependencyProperty as the backing store for ItemHeight. This enables animation, styling, binding, etc... + /// + public static readonly DependencyProperty ItemHeightProperty = + DependencyProperty.Register("ItemHeight", typeof(double), typeof(ComboBox), new UIPropertyMetadata(double.NaN)); + + #endregion + + #region GroupBy + + /// + /// Gets or sets name of property which + /// will use to group items in the ComboBox. + /// + public string GroupBy + { + get { return (string)this.GetValue(GroupByProperty); } + set { this.SetValue(GroupByProperty, value); } + } + + /// + /// Using a DependencyProperty as the backing store for GroupBy. + /// This enables animation, styling, binding, etc... + /// + public static readonly DependencyProperty GroupByProperty = + DependencyProperty.Register("GroupBy", typeof(string), + typeof(ComboBox), new UIPropertyMetadata(null)); + + #endregion + + #region ResizeMode + + /// + /// Gets or sets context menu resize mode + /// + public ContextMenuResizeMode ResizeMode + { + get { return (ContextMenuResizeMode)this.GetValue(ResizeModeProperty); } + set { this.SetValue(ResizeModeProperty, value); } + } + + /// + /// Using a DependencyProperty as the backing store for ResizeMode. This enables animation, styling, binding, etc... + /// + public static readonly DependencyProperty ResizeModeProperty = + DependencyProperty.Register("ResizeMode", typeof(ContextMenuResizeMode), typeof(ComboBox), new UIPropertyMetadata(ContextMenuResizeMode.None)); + + #endregion + + #region Snapping + + /// + /// Snaps / Unsnaps the Visual + /// (remove visuals and substitute with freezed image) + /// + private bool IsSnapped + { + get { return this.isSnapped; } + set + { + if (value == this.isSnapped) return; + if (this.snappedImage == null) return; + if ((value) && (((int)this.contentSite.ActualWidth > 0) && ((int)this.contentSite.ActualHeight > 0))) + { + // Render the freezed image + RenderOptions.SetBitmapScalingMode(this.snappedImage, BitmapScalingMode.NearestNeighbor); + var renderTargetBitmap = new RenderTargetBitmap((int)this.contentSite.ActualWidth + (int)this.contentSite.Margin.Left + (int)this.contentSite.Margin.Right, + (int)this.contentSite.ActualHeight + (int)this.contentSite.Margin.Top + (int)this.contentSite.Margin.Bottom, 96, 96, + PixelFormats.Pbgra32); + renderTargetBitmap.Render(this.contentSite); + this.snappedImage.Source = renderTargetBitmap; + this.snappedImage.FlowDirection = this.FlowDirection; + /*snappedImage.Width = contentSite.ActualWidth; snappedImage.Height = contentSite.ActualHeight;*/ - this.snappedImage.Visibility = Visibility.Visible; - this.contentSite.Visibility = Visibility.Hidden; - this.isSnapped = value; - } - else - { - this.snappedImage.Visibility = Visibility.Collapsed; - this.contentSite.Visibility = Visibility.Visible; - this.isSnapped = value; - } - - this.InvalidateVisual(); - } - } - - #endregion - - #region DropDownHeight - - /// - /// Gets or sets initial dropdown height - /// - public double DropDownHeight - { - get { return (double)this.GetValue(DropDownHeightProperty); } - set { this.SetValue(DropDownHeightProperty, value); } - } - - /// - /// /Using a DependencyProperty as the backing store for DropDownHeight. This enables animation, styling, binding, - /// etc... - /// - public static readonly DependencyProperty DropDownHeightProperty = - DependencyProperty.Register("InitialDropDownHeight", typeof(double), typeof(ComboBox), new UIPropertyMetadata(double.NaN)); - - #endregion - - #region ShowPopupOnTop - - /// - /// Gets a value indicating whether popup is shown on top; - /// - public bool ShowPopupOnTop - { - get { return (bool)this.GetValue(ShowPopupOnTopProperty); } - private set { this.SetValue(ShowPopupOnTopPropertyKey, value); } - } - - // - private static readonly DependencyPropertyKey ShowPopupOnTopPropertyKey = DependencyProperty.RegisterReadOnly("ShowPopupOnTop", typeof(bool), typeof(ComboBox), new UIPropertyMetadata(false)); - - /// - /// Using a DependencyProperty as the backing store for ShowPopupOnTop. This enables animation, styling, binding, - /// etc... - /// - public static readonly DependencyProperty ShowPopupOnTopProperty = ShowPopupOnTopPropertyKey.DependencyProperty; - - #endregion - - #endregion - - #region Constructors - - /// - /// Static constructor - /// - [SuppressMessage("Microsoft.Performance", "CA1810")] - static ComboBox() - { - var type = typeof(ComboBox); - ToolTipService.Attach(type); - PopupService.Attach(type); - ContextMenuService.Attach(type); - DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(type)); - SelectedItemProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(OnSelectionItemChanged, CoerceSelectedItem)); - } - - private static void OnSelectionItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var combo = d as ComboBox; - if (!combo.isQuickAccessOpened && !combo.isQuickAccessFocused && (combo.quickAccessCombo != null)) combo.UpdateQuickAccessCombo(); - } - - private static object CoerceSelectedItem(DependencyObject d, object basevalue) - { - var combo = d as ComboBox; - if (combo.isQuickAccessOpened || combo.isQuickAccessFocused) return combo.selectedItem; - return basevalue; - } - - /// - /// Default Constructor - /// - public ComboBox() - { - ContextMenuService.Coerce(this); - } - - #endregion - - #region QuickAccess - - /// - /// Gets control which represents shortcut item. - /// This item MUST be syncronized with the original - /// and send command to original one control. - /// - /// Control which represents shortcut item - public virtual FrameworkElement CreateQuickAccessItem() - { - var combo = new ComboBox(); - RibbonControl.BindQuickAccessItem(this, combo); - RibbonControl.Bind(this, combo, "GroupBy", GroupByProperty, BindingMode.OneWay); - RibbonControl.Bind(this, combo, "ActualWidth", WidthProperty, BindingMode.OneWay); - RibbonControl.Bind(this, combo, "InputWidth", InputWidthProperty, BindingMode.OneWay); - RibbonControl.Bind(this, combo, "ItemHeight", ItemHeightProperty, BindingMode.OneWay); - RibbonControl.Bind(this, combo, "IsEditable", IsEditableProperty, BindingMode.OneWay); - RibbonControl.Bind(this, combo, "IsReadOnly", IsReadOnlyProperty, BindingMode.OneWay); - RibbonControl.Bind(this, combo, "ResizeMode", ResizeModeProperty, BindingMode.OneWay); - RibbonControl.Bind(this, combo, "Text", TextProperty, BindingMode.TwoWay); - - RibbonControl.Bind(this, combo, "DisplayMemberPath", DisplayMemberPathProperty, BindingMode.OneWay); - RibbonControl.Bind(this, combo, "GroupStyleSelector", GroupStyleSelectorProperty, BindingMode.OneWay); - RibbonControl.Bind(this, combo, "ItemContainerStyle", ItemContainerStyleProperty, BindingMode.OneWay); - RibbonControl.Bind(this, combo, "ItemsPanel", ItemsPanelProperty, BindingMode.OneWay); - RibbonControl.Bind(this, combo, "ItemStringFormat", ItemStringFormatProperty, BindingMode.OneWay); - RibbonControl.Bind(this, combo, "ItemTemplate", ItemTemplateProperty, BindingMode.OneWay); - RibbonControl.Bind(this, combo, "SelectedValuePath", SelectedValuePathProperty, BindingMode.OneWay); - RibbonControl.Bind(this, combo, "MaxDropDownHeight", MaxDropDownHeightProperty, BindingMode.OneWay); - combo.DropDownOpened += this.OnQuickAccessOpened; - if (this.IsEditable) combo.GotFocus += this.OnQuickAccessTextBoxGetFocus; - this.quickAccessCombo = combo; - this.UpdateQuickAccessCombo(); - return combo; - } - - private void OnQuickAccessTextBoxGetFocus(object sender, RoutedEventArgs e) - { - this.isQuickAccessFocused = true; - if (!this.isQuickAccessOpened) this.Freeze(); - this.quickAccessCombo.LostFocus += this.OnQuickAccessTextBoxLostFocus; - } - - private void OnQuickAccessTextBoxLostFocus(object sender, RoutedEventArgs e) - { - this.quickAccessCombo.LostFocus -= this.OnQuickAccessTextBoxLostFocus; - if (!this.isQuickAccessOpened) this.Unfreeze(); - this.isQuickAccessFocused = false; - } - - private bool isQuickAccessFocused; - private bool isQuickAccessOpened; - private object selectedItem; - private ComboBox quickAccessCombo; - - private void OnQuickAccessOpened(object sender, EventArgs e) - { - this.isQuickAccessOpened = true; - this.quickAccessCombo.DropDownClosed += this.OnQuickAccessMenuClosed; - this.quickAccessCombo.UpdateLayout(); - if (!this.isQuickAccessFocused) - this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, ((ThreadStart)(() => - { - this.Freeze(); - this.Dispatcher.BeginInvoke(DispatcherPriority.Input, ((ThreadStart)(() => { if (this.quickAccessCombo.SelectedItem != null) (this.quickAccessCombo.ItemContainerGenerator.ContainerFromItem(this.quickAccessCombo.SelectedItem) as ComboBoxItem).BringIntoView(); } - ))); - } - ))); - } - - private void OnQuickAccessMenuClosed(object sender, EventArgs e) - { - this.quickAccessCombo.DropDownClosed -= this.OnQuickAccessMenuClosed; - if (!this.isQuickAccessFocused) this.Unfreeze(); - this.isQuickAccessOpened = false; - } - - private void Freeze() - { - this.IsSnapped = true; - this.selectedItem = this.SelectedItem; + this.snappedImage.Visibility = Visibility.Visible; + this.contentSite.Visibility = Visibility.Hidden; + this.isSnapped = value; + } + else + { + this.snappedImage.Visibility = Visibility.Collapsed; + this.contentSite.Visibility = Visibility.Visible; + this.isSnapped = value; + } + + this.InvalidateVisual(); + } + } + + #endregion + + #region DropDownHeight + + /// + /// Gets or sets initial dropdown height + /// + public double DropDownHeight + { + get { return (double)this.GetValue(DropDownHeightProperty); } + set { this.SetValue(DropDownHeightProperty, value); } + } + + /// + /// /Using a DependencyProperty as the backing store for DropDownHeight. This enables animation, styling, binding, + /// etc... + /// + public static readonly DependencyProperty DropDownHeightProperty = + DependencyProperty.Register("InitialDropDownHeight", typeof(double), typeof(ComboBox), new UIPropertyMetadata(double.NaN)); + + #endregion + + #region ShowPopupOnTop + + /// + /// Gets a value indicating whether popup is shown on top; + /// + public bool ShowPopupOnTop + { + get { return (bool)this.GetValue(ShowPopupOnTopProperty); } + private set { this.SetValue(ShowPopupOnTopPropertyKey, value); } + } + + // + private static readonly DependencyPropertyKey ShowPopupOnTopPropertyKey = DependencyProperty.RegisterReadOnly("ShowPopupOnTop", typeof(bool), typeof(ComboBox), new UIPropertyMetadata(false)); + + /// + /// Using a DependencyProperty as the backing store for ShowPopupOnTop. This enables animation, styling, binding, + /// etc... + /// + public static readonly DependencyProperty ShowPopupOnTopProperty = ShowPopupOnTopPropertyKey.DependencyProperty; + + #endregion + + #endregion + + #region Constructors + + /// + /// Static constructor + /// + [SuppressMessage("Microsoft.Performance", "CA1810")] + static ComboBox() + { + var type = typeof(ComboBox); + ToolTipService.Attach(type); + PopupService.Attach(type); + ContextMenuService.Attach(type); + DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(type)); + SelectedItemProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(OnSelectionItemChanged, CoerceSelectedItem)); + } + + private static void OnSelectionItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var combo = d as ComboBox; + if (!combo.isQuickAccessOpened && !combo.isQuickAccessFocused && (combo.quickAccessCombo != null)) combo.UpdateQuickAccessCombo(); + } + + private static object CoerceSelectedItem(DependencyObject d, object basevalue) + { + var combo = d as ComboBox; + if (combo.isQuickAccessOpened || combo.isQuickAccessFocused) return combo.selectedItem; + return basevalue; + } + + /// + /// Default Constructor + /// + public ComboBox() + { + ContextMenuService.Coerce(this); + } + + #endregion + + #region QuickAccess + + /// + /// Gets control which represents shortcut item. + /// This item MUST be syncronized with the original + /// and send command to original one control. + /// + /// Control which represents shortcut item + public virtual FrameworkElement CreateQuickAccessItem() + { + var combo = new ComboBox(); + RibbonControl.BindQuickAccessItem(this, combo); + RibbonControl.Bind(this, combo, "GroupBy", GroupByProperty, BindingMode.OneWay); + RibbonControl.Bind(this, combo, "ActualWidth", WidthProperty, BindingMode.OneWay); + RibbonControl.Bind(this, combo, "InputWidth", InputWidthProperty, BindingMode.OneWay); + RibbonControl.Bind(this, combo, "ItemHeight", ItemHeightProperty, BindingMode.OneWay); + RibbonControl.Bind(this, combo, "IsEditable", IsEditableProperty, BindingMode.OneWay); + RibbonControl.Bind(this, combo, "IsReadOnly", IsReadOnlyProperty, BindingMode.OneWay); + RibbonControl.Bind(this, combo, "ResizeMode", ResizeModeProperty, BindingMode.OneWay); + RibbonControl.Bind(this, combo, "Text", TextProperty, BindingMode.TwoWay); + + RibbonControl.Bind(this, combo, "DisplayMemberPath", DisplayMemberPathProperty, BindingMode.OneWay); + RibbonControl.Bind(this, combo, "GroupStyleSelector", GroupStyleSelectorProperty, BindingMode.OneWay); + RibbonControl.Bind(this, combo, "ItemContainerStyle", ItemContainerStyleProperty, BindingMode.OneWay); + RibbonControl.Bind(this, combo, "ItemsPanel", ItemsPanelProperty, BindingMode.OneWay); + RibbonControl.Bind(this, combo, "ItemStringFormat", ItemStringFormatProperty, BindingMode.OneWay); + RibbonControl.Bind(this, combo, "ItemTemplate", ItemTemplateProperty, BindingMode.OneWay); + RibbonControl.Bind(this, combo, "SelectedValuePath", SelectedValuePathProperty, BindingMode.OneWay); + RibbonControl.Bind(this, combo, "MaxDropDownHeight", MaxDropDownHeightProperty, BindingMode.OneWay); + combo.DropDownOpened += this.OnQuickAccessOpened; + if (this.IsEditable) combo.GotFocus += this.OnQuickAccessTextBoxGetFocus; + this.quickAccessCombo = combo; + this.UpdateQuickAccessCombo(); + return combo; + } + + private void OnQuickAccessTextBoxGetFocus(object sender, RoutedEventArgs e) + { + this.isQuickAccessFocused = true; + if (!this.isQuickAccessOpened) this.Freeze(); + this.quickAccessCombo.LostFocus += this.OnQuickAccessTextBoxLostFocus; + } + + private void OnQuickAccessTextBoxLostFocus(object sender, RoutedEventArgs e) + { + this.quickAccessCombo.LostFocus -= this.OnQuickAccessTextBoxLostFocus; + if (!this.isQuickAccessOpened) this.Unfreeze(); + this.isQuickAccessFocused = false; + } + + private bool isQuickAccessFocused; + private bool isQuickAccessOpened; + private object selectedItem; + private ComboBox quickAccessCombo; + + private void OnQuickAccessOpened(object sender, EventArgs e) + { + this.isQuickAccessOpened = true; + this.quickAccessCombo.DropDownClosed += this.OnQuickAccessMenuClosed; + this.quickAccessCombo.UpdateLayout(); + if (!this.isQuickAccessFocused) + this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, ((ThreadStart)(() => + { + this.Freeze(); + this.Dispatcher.BeginInvoke(DispatcherPriority.Input, ((ThreadStart)(() => { if (this.quickAccessCombo.SelectedItem != null) (this.quickAccessCombo.ItemContainerGenerator.ContainerFromItem(this.quickAccessCombo.SelectedItem) as ComboBoxItem).BringIntoView(); } + ))); + } + ))); + } + + private void OnQuickAccessMenuClosed(object sender, EventArgs e) + { + this.quickAccessCombo.DropDownClosed -= this.OnQuickAccessMenuClosed; + if (!this.isQuickAccessFocused) this.Unfreeze(); + this.isQuickAccessOpened = false; + } + + private void Freeze() + { + this.IsSnapped = true; + this.selectedItem = this.SelectedItem; ItemsControlHelper.MoveItemsToDifferentControl(this, this.quickAccessCombo); this.SelectedItem = null; - this.quickAccessCombo.SelectedItem = this.selectedItem; - this.quickAccessCombo.Menu = this.Menu; - this.Menu = null; - this.quickAccessCombo.IsSnapped = false; - } - - private void Unfreeze() - { - var text = this.quickAccessCombo.Text; - this.selectedItem = this.quickAccessCombo.SelectedItem; - this.quickAccessCombo.IsSnapped = true; + this.quickAccessCombo.SelectedItem = this.selectedItem; + this.quickAccessCombo.Menu = this.Menu; + this.Menu = null; + this.quickAccessCombo.IsSnapped = false; + } + + private void Unfreeze() + { + var text = this.quickAccessCombo.Text; + this.selectedItem = this.quickAccessCombo.SelectedItem; + this.quickAccessCombo.IsSnapped = true; ItemsControlHelper.MoveItemsToDifferentControl(this.quickAccessCombo, this); - this.quickAccessCombo.SelectedItem = null; - this.SelectedItem = this.selectedItem; - this.Menu = this.quickAccessCombo.Menu; - this.quickAccessCombo.Menu = null; - this.IsSnapped = false; - this.Text = text; - this.UpdateLayout(); - } - - private void UpdateQuickAccessCombo() - { - if (this.IsLoaded == false) - { - this.Loaded += this.OnFirstLoaded; - } - - if (this.IsEditable == false) - { - this.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, (ThreadStart)(() => - { - this.quickAccessCombo.IsSnapped = true; - this.IsSnapped = true; - if (this.snappedImage != null && - this.quickAccessCombo.snappedImage != null) - { - this.quickAccessCombo.snappedImage.Source = this.snappedImage.Source; - this.quickAccessCombo.snappedImage.Visibility = Visibility.Visible; - if (this.quickAccessCombo.IsSnapped == false) - { - this.quickAccessCombo.isSnapped = true; - } - } - this.IsSnapped = false; - })); - } - } - - private void OnFirstLoaded(object sender, RoutedEventArgs e) - { - this.Loaded -= this.OnFirstLoaded; - this.UpdateQuickAccessCombo(); - } - - /// - /// Gets or sets whether control can be added to quick access toolbar - /// - public bool CanAddToQuickAccessToolBar - { - get { return (bool)this.GetValue(CanAddToQuickAccessToolBarProperty); } - set { this.SetValue(CanAddToQuickAccessToolBarProperty, value); } - } - - /// - /// Using a DependencyProperty as the backing store for CanAddToQuickAccessToolBar. This enables animation, styling, - /// binding, etc... - /// - public static readonly DependencyProperty CanAddToQuickAccessToolBarProperty = RibbonControl.CanAddToQuickAccessToolBarProperty.AddOwner(typeof(ComboBox), new UIPropertyMetadata(true, RibbonControl.OnCanAddToQuickAccessToolbarChanged)); - - #endregion - - #region Overrides - - /// - /// When overridden in a derived class, is invoked whenever application code or internal processes call - /// . - /// - public override void OnApplyTemplate() - { - this.DropDownPopup = this.GetTemplateChild("PART_Popup") as Popup; - - if (this.resizeVerticalThumb != null) - { - this.resizeVerticalThumb.DragDelta -= this.OnResizeVerticalDelta; - } - this.resizeVerticalThumb = this.GetTemplateChild("PART_ResizeVerticalThumb") as Thumb; - if (this.resizeVerticalThumb != null) - { - this.resizeVerticalThumb.DragDelta += this.OnResizeVerticalDelta; - } - - if (this.resizeBothThumb != null) - { - this.resizeBothThumb.DragDelta -= this.OnResizeBothDelta; - } - this.resizeBothThumb = this.GetTemplateChild("PART_ResizeBothThumb") as Thumb; - if (this.resizeBothThumb != null) - { - this.resizeBothThumb.DragDelta += this.OnResizeBothDelta; - } - - this.menuPanel = this.GetTemplateChild("PART_MenuPanel") as Panel; - - this.snappedImage = this.GetTemplateChild("PART_SelectedImage") as Image; - this.contentSite = this.GetTemplateChild("PART_ContentSite") as ContentPresenter; - - if (this.contentBorder != null) this.contentBorder.PreviewMouseDown -= this.OnContentBorderPreviewMouseDown; - this.contentBorder = this.GetTemplateChild("PART_ContentBorder") as Border; - if (this.contentBorder != null) this.contentBorder.PreviewMouseDown += this.OnContentBorderPreviewMouseDown; - - this.galleryPanel = this.GetTemplateChild("PART_GalleryPanel") as GalleryPanel; - this.scrollViewer = this.GetTemplateChild("PART_ScrollViewer") as ScrollViewer; - - this.dropDownBorder = this.GetTemplateChild("PART_DropDownBorder") as Border; - - base.OnApplyTemplate(); - } - - /// - /// Reports when a combo box's popup opens. - /// - /// The event data for the event. - protected override void OnDropDownOpened(EventArgs e) - { - base.OnDropDownOpened(e); + this.quickAccessCombo.SelectedItem = null; + this.SelectedItem = this.selectedItem; + this.Menu = this.quickAccessCombo.Menu; + this.quickAccessCombo.Menu = null; + this.IsSnapped = false; + this.Text = text; + this.UpdateLayout(); + } + + private void UpdateQuickAccessCombo() + { + if (this.IsLoaded == false) + { + this.Loaded += this.OnFirstLoaded; + } + + if (this.IsEditable == false) + { + this.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, (ThreadStart)(() => + { + this.quickAccessCombo.IsSnapped = true; + this.IsSnapped = true; + if (this.snappedImage != null && + this.quickAccessCombo.snappedImage != null) + { + this.quickAccessCombo.snappedImage.Source = this.snappedImage.Source; + this.quickAccessCombo.snappedImage.Visibility = Visibility.Visible; + if (this.quickAccessCombo.IsSnapped == false) + { + this.quickAccessCombo.isSnapped = true; + } + } + this.IsSnapped = false; + })); + } + } + + private void OnFirstLoaded(object sender, RoutedEventArgs e) + { + this.Loaded -= this.OnFirstLoaded; + this.UpdateQuickAccessCombo(); + } + + /// + /// Gets or sets whether control can be added to quick access toolbar + /// + public bool CanAddToQuickAccessToolBar + { + get { return (bool)this.GetValue(CanAddToQuickAccessToolBarProperty); } + set { this.SetValue(CanAddToQuickAccessToolBarProperty, value); } + } + + /// + /// Using a DependencyProperty as the backing store for CanAddToQuickAccessToolBar. This enables animation, styling, + /// binding, etc... + /// + public static readonly DependencyProperty CanAddToQuickAccessToolBarProperty = RibbonControl.CanAddToQuickAccessToolBarProperty.AddOwner(typeof(ComboBox), new UIPropertyMetadata(true, RibbonControl.OnCanAddToQuickAccessToolbarChanged)); + + #endregion + + #region Overrides + + /// + /// When overridden in a derived class, is invoked whenever application code or internal processes call + /// . + /// + public override void OnApplyTemplate() + { + this.DropDownPopup = this.GetTemplateChild("PART_Popup") as Popup; + + if (this.resizeVerticalThumb != null) + { + this.resizeVerticalThumb.DragDelta -= this.OnResizeVerticalDelta; + } + this.resizeVerticalThumb = this.GetTemplateChild("PART_ResizeVerticalThumb") as Thumb; + if (this.resizeVerticalThumb != null) + { + this.resizeVerticalThumb.DragDelta += this.OnResizeVerticalDelta; + } + + if (this.resizeBothThumb != null) + { + this.resizeBothThumb.DragDelta -= this.OnResizeBothDelta; + } + this.resizeBothThumb = this.GetTemplateChild("PART_ResizeBothThumb") as Thumb; + if (this.resizeBothThumb != null) + { + this.resizeBothThumb.DragDelta += this.OnResizeBothDelta; + } + + this.menuPanel = this.GetTemplateChild("PART_MenuPanel") as Panel; + + this.snappedImage = this.GetTemplateChild("PART_SelectedImage") as Image; + this.contentSite = this.GetTemplateChild("PART_ContentSite") as ContentPresenter; + + if (this.contentBorder != null) this.contentBorder.PreviewMouseDown -= this.OnContentBorderPreviewMouseDown; + this.contentBorder = this.GetTemplateChild("PART_ContentBorder") as Border; + if (this.contentBorder != null) this.contentBorder.PreviewMouseDown += this.OnContentBorderPreviewMouseDown; + + this.galleryPanel = this.GetTemplateChild("PART_GalleryPanel") as GalleryPanel; + this.scrollViewer = this.GetTemplateChild("PART_ScrollViewer") as ScrollViewer; + + this.dropDownBorder = this.GetTemplateChild("PART_DropDownBorder") as Border; + + base.OnApplyTemplate(); + } + + /// + /// Reports when a combo box's popup opens. + /// + /// The event data for the event. + protected override void OnDropDownOpened(EventArgs e) + { + base.OnDropDownOpened(e); Mouse.Capture(this, CaptureMode.SubTree); - if (this.SelectedItem != null) - { - Keyboard.Focus(this.ItemContainerGenerator.ContainerFromItem(this.SelectedItem) as IInputElement); - } - - this.focusedElement = Keyboard.FocusedElement; - - if (this.focusedElement != null) - { - this.focusedElement.LostKeyboardFocus += this.OnFocusedElementLostKeyboardFocus; - } - - this.canSizeY = true; - - this.galleryPanel.Width = double.NaN; - this.scrollViewer.Height = double.NaN; - - var popupChild = this.DropDownPopup.Child as FrameworkElement; - var heightDelta = popupChild.DesiredSize.Height - this.scrollViewer.DesiredSize.Height; - - var initialHeight = Math.Min(RibbonControl.GetControlWorkArea(this).Height * 2 / 3, this.MaxDropDownHeight); - - if (double.IsNaN(this.DropDownHeight) == false) - { - initialHeight = Math.Min(this.DropDownHeight, this.MaxDropDownHeight); - } - - if (this.scrollViewer.DesiredSize.Height > initialHeight) - { - this.scrollViewer.Height = initialHeight; - } - else - { - initialHeight = this.scrollViewer.DesiredSize.Height; - } - - var monitor = RibbonControl.GetControlMonitor(this); - var delta = monitor.Bottom - this.PointToScreen(new Point()).Y - this.ActualHeight - initialHeight - heightDelta; - - if (delta >= 0) - { - this.ShowPopupOnTop = false; - } - else - { - var deltaTop = this.PointToScreen(new Point()).Y - initialHeight - heightDelta - monitor.Top; - - if (deltaTop > delta) - { - this.ShowPopupOnTop = true; - } - else - { - this.ShowPopupOnTop = false; - } - - if (deltaTop < 0) - { - delta = Math.Max(Math.Abs(delta), Math.Abs(deltaTop)); - - if (delta > this.galleryPanel.GetItemSize().Height) - { - this.scrollViewer.Height = delta; - } - else - { - this.canSizeY = false; - this.scrollViewer.Height = this.galleryPanel.GetItemSize().Height; - } - } - } - - popupChild.UpdateLayout(); - } - - /// - /// Reports when a combo box's popup closes. - /// - /// The event data for the event. - protected override void OnDropDownClosed(EventArgs e) - { - base.OnDropDownClosed(e); - if (Mouse.Captured == this) Mouse.Capture(null); - if (this.focusedElement != null) this.focusedElement.LostKeyboardFocus -= this.OnFocusedElementLostKeyboardFocus; - this.focusedElement = null; - this.ShowPopupOnTop = false; - this.galleryPanel.Width = double.NaN; - this.scrollViewer.Height = double.NaN; - } - - private void OnFocusedElementLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) - { - if (this.focusedElement != null) this.focusedElement.LostKeyboardFocus -= this.OnFocusedElementLostKeyboardFocus; - this.focusedElement = Keyboard.FocusedElement; - if (this.focusedElement != null) - { - this.focusedElement.LostKeyboardFocus += this.OnFocusedElementLostKeyboardFocus; - if ((this.IsEditable) && - (this.Items.Contains(this.ItemContainerGenerator.ItemFromContainer(Keyboard.FocusedElement as DependencyObject)))) - { - this.SelectedItem = this.ItemContainerGenerator.ItemFromContainer(Keyboard.FocusedElement as DependencyObject); - } - } - } - - /// - /// Invoked when a attached routed event occurs. - /// - /// Event data. - protected override void OnPreviewKeyDown(KeyEventArgs e) - { - if ((this.IsEditable) && ((e.Key == Key.Down) || (e.Key == Key.Up)) && (!this.IsDropDownOpen)) - { - this.IsDropDownOpen = true; - e.Handled = true; - return; - } - - base.OnPreviewKeyDown(e); - } - - /// - /// Invoked when a attached routed event occurs. - /// - /// Event data. - protected override void OnKeyDown(KeyEventArgs e) - { - if (e.Key == Key.Down) - { - Debug.WriteLine("Down pressed. FocusedElement - " + Keyboard.FocusedElement); - if ((this.Menu != null) && this.Menu.Items.Contains(this.Menu.ItemContainerGenerator.ItemFromContainer(Keyboard.FocusedElement as DependencyObject))) - { - var indexOfMSelectedItem = this.Menu.ItemContainerGenerator.IndexFromContainer(Keyboard.FocusedElement as DependencyObject); - if (indexOfMSelectedItem != this.Menu.Items.Count - 1) - { - Keyboard.Focus(this.Menu.ItemContainerGenerator.ContainerFromIndex(indexOfMSelectedItem + 1) as IInputElement); - } - else - { - if ((this.Items.Count > 0) && (!this.IsEditable)) - { - Keyboard.Focus(this.ItemContainerGenerator.ContainerFromIndex(0) as IInputElement); - } - else Keyboard.Focus(this.Menu.Items[0] as IInputElement); - } - } - else if (this.Items.Contains(this.ItemContainerGenerator.ItemFromContainer(Keyboard.FocusedElement as DependencyObject))) - { - var indexOfSelectedItem = this.ItemContainerGenerator.IndexFromContainer(Keyboard.FocusedElement as DependencyObject); - if (indexOfSelectedItem != this.Items.Count - 1) - { - Keyboard.Focus(this.ItemContainerGenerator.ContainerFromIndex(indexOfSelectedItem + 1) as IInputElement); - } - else - { - if ((this.Menu != null) && (this.Menu.Items.Count > 0) && (!this.IsEditable)) Keyboard.Focus(this.Menu.ItemContainerGenerator.ContainerFromIndex(0) as IInputElement); - else - { - Keyboard.Focus(this.ItemContainerGenerator.ContainerFromIndex(0) as IInputElement); - } - } - } - else if (this.SelectedItem != null) Keyboard.Focus(this.ItemContainerGenerator.ContainerFromItem(this.SelectedItem) as IInputElement); - e.Handled = true; - Debug.WriteLine("FocusedElement - " + Keyboard.FocusedElement); - return; - } - else if (e.Key == Key.Up) - { - Debug.WriteLine("Up pressed. FocusedElement - " + Keyboard.FocusedElement); - if ((this.Menu != null) && this.Menu.Items.Contains(this.Menu.ItemContainerGenerator.ItemFromContainer(Keyboard.FocusedElement as DependencyObject))) - { - var indexOfMSelectedItem = this.Menu.ItemContainerGenerator.IndexFromContainer(Keyboard.FocusedElement as DependencyObject); - if (indexOfMSelectedItem != 0) - { - Keyboard.Focus(this.Menu.ItemContainerGenerator.ContainerFromIndex(indexOfMSelectedItem - 1) as IInputElement); - } - else - { - if ((this.Items.Count > 0) && (!this.IsEditable)) - { - Keyboard.Focus(this.ItemContainerGenerator.ContainerFromIndex(this.Items.Count - 1) as IInputElement); - } - else Keyboard.Focus(this.Menu.Items[this.Menu.Items.Count - 1] as IInputElement); - } - } - else if (this.Items.Contains(this.ItemContainerGenerator.ItemFromContainer(Keyboard.FocusedElement as DependencyObject))) - { - var indexOfSelectedItem = this.ItemContainerGenerator.IndexFromContainer(Keyboard.FocusedElement as DependencyObject); - if (indexOfSelectedItem != 0) - { - Keyboard.Focus(this.ItemContainerGenerator.ContainerFromIndex(indexOfSelectedItem - 1) as IInputElement); - } - else - { - if ((this.Menu != null) && (this.Menu.Items.Count > 0) && (!this.IsEditable)) Keyboard.Focus(this.Menu.ItemContainerGenerator.ContainerFromIndex(this.Menu.Items.Count - 1) as IInputElement); - else - { - Keyboard.Focus(this.ItemContainerGenerator.ContainerFromIndex(this.Items.Count - 1) as IInputElement); - } - } - } - else if (this.SelectedItem != null) Keyboard.Focus(this.ItemContainerGenerator.ContainerFromItem(this.SelectedItem) as IInputElement); - Debug.WriteLine("FocusedElement - " + Keyboard.FocusedElement); - e.Handled = true; - return; - } - else if ((e.Key == Key.Return) && (!this.IsEditable) && this.IsDropDownOpen) - { - var element = Keyboard.FocusedElement as DependencyObject; - - // only try to select if we got a focusedElement - if (element != null) - { - var newSelectedIndex = this.ItemContainerGenerator.IndexFromContainer(element); - - // only set the selected index if the focused element was in a container in this combobox - if (newSelectedIndex > -1) - { - this.SelectedIndex = newSelectedIndex; - } - } - } - base.OnKeyDown(e); - } - - #endregion - - #region Methods - - /// - /// Handles key tip pressed - /// - public virtual void OnKeyTipPressed() - { - this.Dispatcher.BeginInvoke( - DispatcherPriority.Normal, - (DispatcherOperationCallback)delegate(object arg) - { - var ctrl = (ComboBox)arg; - - // Edge case: Whole dropdown content is disabled - if (ctrl.IsKeyboardFocusWithin == false) - { - Keyboard.Focus(ctrl); - } - return null; - }, - this); - - if (!this.IsEditable) - { - this.IsDropDownOpen = true; - } - } - - /// - /// Handles back navigation with KeyTips - /// - public void OnKeyTipBack() - { - } - - #endregion - - #region Private methods - - // Prevent reopenning of the dropdown menu (popup) - private void OnContentBorderPreviewMouseDown(object sender, MouseButtonEventArgs e) - { - if (this.IsDropDownOpen) - { - this.IsDropDownOpen = false; - e.Handled = true; - } - } - - // Handles resize both drag - private void OnResizeBothDelta(object sender, DragDeltaEventArgs e) - { - // Set height - this.SetDragHeight(e); - - // Set width - this.menuPanel.Width = double.NaN; - if (double.IsNaN(this.galleryPanel.Width)) - { - this.galleryPanel.Width = this.galleryPanel.ActualWidth; - } - - var monitorRight = RibbonControl.GetControlMonitor(this).Right; - var popupChild = this.DropDownPopup.Child as FrameworkElement; - var delta = monitorRight - this.PointToScreen(new Point()).X - popupChild.ActualWidth - e.HorizontalChange; - var deltaX = popupChild.ActualWidth - this.galleryPanel.ActualWidth; - var deltaBorders = this.dropDownBorder.ActualWidth - this.galleryPanel.ActualWidth; - - if (delta > 0) - { - this.galleryPanel.Width = Math.Max(0, Math.Max(this.galleryPanel.Width + e.HorizontalChange, this.ActualWidth - deltaBorders)); - } - else - { - this.galleryPanel.Width = Math.Max(0, Math.Max(monitorRight - this.PointToScreen(new Point()).X - deltaX, this.ActualWidth - deltaBorders)); - } - } - - // Handles resize vertical drag - private void OnResizeVerticalDelta(object sender, DragDeltaEventArgs e) - { - this.SetDragHeight(e); - } - - private void SetDragHeight(DragDeltaEventArgs e) - { - if (!this.canSizeY) - { - return; - } - - if (double.IsNaN(this.scrollViewer.Height)) - { - this.scrollViewer.Height = this.scrollViewer.ActualHeight; - } - - if (this.ShowPopupOnTop) - { - var monitorTop = RibbonControl.GetControlMonitor(this).Top; - - // Calc shadow height - var delta = this.PointToScreen(new Point()).Y - this.dropDownBorder.ActualHeight - e.VerticalChange - monitorTop; - if (delta > 0) - { - this.scrollViewer.Height = Math.Max(0, - Math.Min(Math.Max(this.galleryPanel.GetItemSize().Height, this.scrollViewer.Height + e.VerticalChange), this.MaxDropDownHeight)); - } - else - { - delta = this.PointToScreen(new Point()).Y - this.dropDownBorder.ActualHeight - monitorTop; - this.scrollViewer.Height = Math.Max(0, - Math.Min(Math.Max(this.galleryPanel.GetItemSize().Height, this.scrollViewer.Height + delta), this.MaxDropDownHeight)); - } - } - else - { - var monitorBottom = RibbonControl.GetControlMonitor(this).Bottom; - var popupChild = this.DropDownPopup.Child as FrameworkElement; - var delta = monitorBottom - this.PointToScreen(new Point()).Y - this.ActualHeight - popupChild.ActualHeight - e.VerticalChange; - if (delta > 0) - { - this.scrollViewer.Height = Math.Max(0, - Math.Min(Math.Max(this.galleryPanel.GetItemSize().Height, this.scrollViewer.Height + e.VerticalChange), this.MaxDropDownHeight)); - } - else - { - delta = monitorBottom - this.PointToScreen(new Point()).Y - this.ActualHeight - popupChild.ActualHeight; - this.scrollViewer.Height = Math.Max(0, - Math.Min(Math.Max(this.galleryPanel.GetItemSize().Height, this.scrollViewer.Height + delta), this.MaxDropDownHeight)); - } - } - } - - #endregion - } + if (this.SelectedItem != null) + { + Keyboard.Focus(this.ItemContainerGenerator.ContainerFromItem(this.SelectedItem) as IInputElement); + } + + this.focusedElement = Keyboard.FocusedElement; + + if (this.focusedElement != null) + { + this.focusedElement.LostKeyboardFocus += this.OnFocusedElementLostKeyboardFocus; + } + + this.canSizeY = true; + + this.galleryPanel.Width = double.NaN; + this.scrollViewer.Height = double.NaN; + + var popupChild = this.DropDownPopup.Child as FrameworkElement; + var heightDelta = popupChild.DesiredSize.Height - this.scrollViewer.DesiredSize.Height; + + var initialHeight = Math.Min(RibbonControl.GetControlWorkArea(this).Height * 2 / 3, this.MaxDropDownHeight); + + if (double.IsNaN(this.DropDownHeight) == false) + { + initialHeight = Math.Min(this.DropDownHeight, this.MaxDropDownHeight); + } + + if (this.scrollViewer.DesiredSize.Height > initialHeight) + { + this.scrollViewer.Height = initialHeight; + } + else + { + initialHeight = this.scrollViewer.DesiredSize.Height; + } + + var monitor = RibbonControl.GetControlMonitor(this); + var delta = monitor.Bottom - this.PointToScreen(new Point()).Y - this.ActualHeight - initialHeight - heightDelta; + + if (delta >= 0) + { + this.ShowPopupOnTop = false; + } + else + { + var deltaTop = this.PointToScreen(new Point()).Y - initialHeight - heightDelta - monitor.Top; + + if (deltaTop > delta) + { + this.ShowPopupOnTop = true; + } + else + { + this.ShowPopupOnTop = false; + } + + if (deltaTop < 0) + { + delta = Math.Max(Math.Abs(delta), Math.Abs(deltaTop)); + + if (delta > this.galleryPanel.GetItemSize().Height) + { + this.scrollViewer.Height = delta; + } + else + { + this.canSizeY = false; + this.scrollViewer.Height = this.galleryPanel.GetItemSize().Height; + } + } + } + + popupChild.UpdateLayout(); + } + + /// + /// Reports when a combo box's popup closes. + /// + /// The event data for the event. + protected override void OnDropDownClosed(EventArgs e) + { + base.OnDropDownClosed(e); + if (Mouse.Captured == this) Mouse.Capture(null); + if (this.focusedElement != null) this.focusedElement.LostKeyboardFocus -= this.OnFocusedElementLostKeyboardFocus; + this.focusedElement = null; + this.ShowPopupOnTop = false; + this.galleryPanel.Width = double.NaN; + this.scrollViewer.Height = double.NaN; + } + + private void OnFocusedElementLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) + { + if (this.focusedElement != null) this.focusedElement.LostKeyboardFocus -= this.OnFocusedElementLostKeyboardFocus; + this.focusedElement = Keyboard.FocusedElement; + if (this.focusedElement != null) + { + this.focusedElement.LostKeyboardFocus += this.OnFocusedElementLostKeyboardFocus; + if ((this.IsEditable) && + (this.Items.Contains(this.ItemContainerGenerator.ItemFromContainer(Keyboard.FocusedElement as DependencyObject)))) + { + this.SelectedItem = this.ItemContainerGenerator.ItemFromContainer(Keyboard.FocusedElement as DependencyObject); + } + } + } + + /// + /// Invoked when a attached routed event occurs. + /// + /// Event data. + protected override void OnPreviewKeyDown(KeyEventArgs e) + { + if ((this.IsEditable) && ((e.Key == Key.Down) || (e.Key == Key.Up)) && (!this.IsDropDownOpen)) + { + this.IsDropDownOpen = true; + e.Handled = true; + return; + } + + base.OnPreviewKeyDown(e); + } + + /// + /// Invoked when a attached routed event occurs. + /// + /// Event data. + protected override void OnKeyDown(KeyEventArgs e) + { + if (e.Key == Key.Down) + { + Debug.WriteLine("Down pressed. FocusedElement - " + Keyboard.FocusedElement); + if ((this.Menu != null) && this.Menu.Items.Contains(this.Menu.ItemContainerGenerator.ItemFromContainer(Keyboard.FocusedElement as DependencyObject))) + { + var indexOfMSelectedItem = this.Menu.ItemContainerGenerator.IndexFromContainer(Keyboard.FocusedElement as DependencyObject); + if (indexOfMSelectedItem != this.Menu.Items.Count - 1) + { + Keyboard.Focus(this.Menu.ItemContainerGenerator.ContainerFromIndex(indexOfMSelectedItem + 1) as IInputElement); + } + else + { + if ((this.Items.Count > 0) && (!this.IsEditable)) + { + Keyboard.Focus(this.ItemContainerGenerator.ContainerFromIndex(0) as IInputElement); + } + else Keyboard.Focus(this.Menu.Items[0] as IInputElement); + } + } + else if (this.Items.Contains(this.ItemContainerGenerator.ItemFromContainer(Keyboard.FocusedElement as DependencyObject))) + { + var indexOfSelectedItem = this.ItemContainerGenerator.IndexFromContainer(Keyboard.FocusedElement as DependencyObject); + if (indexOfSelectedItem != this.Items.Count - 1) + { + Keyboard.Focus(this.ItemContainerGenerator.ContainerFromIndex(indexOfSelectedItem + 1) as IInputElement); + } + else + { + if ((this.Menu != null) && (this.Menu.Items.Count > 0) && (!this.IsEditable)) Keyboard.Focus(this.Menu.ItemContainerGenerator.ContainerFromIndex(0) as IInputElement); + else + { + Keyboard.Focus(this.ItemContainerGenerator.ContainerFromIndex(0) as IInputElement); + } + } + } + else if (this.SelectedItem != null) Keyboard.Focus(this.ItemContainerGenerator.ContainerFromItem(this.SelectedItem) as IInputElement); + e.Handled = true; + Debug.WriteLine("FocusedElement - " + Keyboard.FocusedElement); + return; + } + else if (e.Key == Key.Up) + { + Debug.WriteLine("Up pressed. FocusedElement - " + Keyboard.FocusedElement); + if ((this.Menu != null) && this.Menu.Items.Contains(this.Menu.ItemContainerGenerator.ItemFromContainer(Keyboard.FocusedElement as DependencyObject))) + { + var indexOfMSelectedItem = this.Menu.ItemContainerGenerator.IndexFromContainer(Keyboard.FocusedElement as DependencyObject); + if (indexOfMSelectedItem != 0) + { + Keyboard.Focus(this.Menu.ItemContainerGenerator.ContainerFromIndex(indexOfMSelectedItem - 1) as IInputElement); + } + else + { + if ((this.Items.Count > 0) && (!this.IsEditable)) + { + Keyboard.Focus(this.ItemContainerGenerator.ContainerFromIndex(this.Items.Count - 1) as IInputElement); + } + else Keyboard.Focus(this.Menu.Items[this.Menu.Items.Count - 1] as IInputElement); + } + } + else if (this.Items.Contains(this.ItemContainerGenerator.ItemFromContainer(Keyboard.FocusedElement as DependencyObject))) + { + var indexOfSelectedItem = this.ItemContainerGenerator.IndexFromContainer(Keyboard.FocusedElement as DependencyObject); + if (indexOfSelectedItem != 0) + { + Keyboard.Focus(this.ItemContainerGenerator.ContainerFromIndex(indexOfSelectedItem - 1) as IInputElement); + } + else + { + if ((this.Menu != null) && (this.Menu.Items.Count > 0) && (!this.IsEditable)) Keyboard.Focus(this.Menu.ItemContainerGenerator.ContainerFromIndex(this.Menu.Items.Count - 1) as IInputElement); + else + { + Keyboard.Focus(this.ItemContainerGenerator.ContainerFromIndex(this.Items.Count - 1) as IInputElement); + } + } + } + else if (this.SelectedItem != null) Keyboard.Focus(this.ItemContainerGenerator.ContainerFromItem(this.SelectedItem) as IInputElement); + Debug.WriteLine("FocusedElement - " + Keyboard.FocusedElement); + e.Handled = true; + return; + } + else if ((e.Key == Key.Return) && (!this.IsEditable) && this.IsDropDownOpen) + { + var element = Keyboard.FocusedElement as DependencyObject; + + // only try to select if we got a focusedElement + if (element != null) + { + var newSelectedIndex = this.ItemContainerGenerator.IndexFromContainer(element); + + // only set the selected index if the focused element was in a container in this combobox + if (newSelectedIndex > -1) + { + this.SelectedIndex = newSelectedIndex; + } + } + } + base.OnKeyDown(e); + } + + #endregion + + #region Methods + + /// + /// Handles key tip pressed + /// + public virtual void OnKeyTipPressed() + { + this.Dispatcher.BeginInvoke( + DispatcherPriority.Normal, + (DispatcherOperationCallback)delegate (object arg) + { + var ctrl = (ComboBox)arg; + + // Edge case: Whole dropdown content is disabled + if (ctrl.IsKeyboardFocusWithin == false) + { + Keyboard.Focus(ctrl); + } + return null; + }, + this); + + if (!this.IsEditable) + { + this.IsDropDownOpen = true; + } + } + + /// + /// Handles back navigation with KeyTips + /// + public void OnKeyTipBack() + { + } + + #endregion + + #region Private methods + + // Prevent reopenning of the dropdown menu (popup) + private void OnContentBorderPreviewMouseDown(object sender, MouseButtonEventArgs e) + { + if (this.IsDropDownOpen) + { + this.IsDropDownOpen = false; + e.Handled = true; + } + } + + // Handles resize both drag + private void OnResizeBothDelta(object sender, DragDeltaEventArgs e) + { + // Set height + this.SetDragHeight(e); + + // Set width + this.menuPanel.Width = double.NaN; + if (double.IsNaN(this.galleryPanel.Width)) + { + this.galleryPanel.Width = this.galleryPanel.ActualWidth; + } + + var monitorRight = RibbonControl.GetControlMonitor(this).Right; + var popupChild = this.DropDownPopup.Child as FrameworkElement; + var delta = monitorRight - this.PointToScreen(new Point()).X - popupChild.ActualWidth - e.HorizontalChange; + var deltaX = popupChild.ActualWidth - this.galleryPanel.ActualWidth; + var deltaBorders = this.dropDownBorder.ActualWidth - this.galleryPanel.ActualWidth; + + if (delta > 0) + { + this.galleryPanel.Width = Math.Max(0, Math.Max(this.galleryPanel.Width + e.HorizontalChange, this.ActualWidth - deltaBorders)); + } + else + { + this.galleryPanel.Width = Math.Max(0, Math.Max(monitorRight - this.PointToScreen(new Point()).X - deltaX, this.ActualWidth - deltaBorders)); + } + } + + // Handles resize vertical drag + private void OnResizeVerticalDelta(object sender, DragDeltaEventArgs e) + { + this.SetDragHeight(e); + } + + private void SetDragHeight(DragDeltaEventArgs e) + { + if (!this.canSizeY) + { + return; + } + + if (double.IsNaN(this.scrollViewer.Height)) + { + this.scrollViewer.Height = this.scrollViewer.ActualHeight; + } + + if (this.ShowPopupOnTop) + { + var monitorTop = RibbonControl.GetControlMonitor(this).Top; + + // Calc shadow height + var delta = this.PointToScreen(new Point()).Y - this.dropDownBorder.ActualHeight - e.VerticalChange - monitorTop; + if (delta > 0) + { + this.scrollViewer.Height = Math.Max(0, + Math.Min(Math.Max(this.galleryPanel.GetItemSize().Height, this.scrollViewer.Height + e.VerticalChange), this.MaxDropDownHeight)); + } + else + { + delta = this.PointToScreen(new Point()).Y - this.dropDownBorder.ActualHeight - monitorTop; + this.scrollViewer.Height = Math.Max(0, + Math.Min(Math.Max(this.galleryPanel.GetItemSize().Height, this.scrollViewer.Height + delta), this.MaxDropDownHeight)); + } + } + else + { + var monitorBottom = RibbonControl.GetControlMonitor(this).Bottom; + var popupChild = this.DropDownPopup.Child as FrameworkElement; + var delta = monitorBottom - this.PointToScreen(new Point()).Y - this.ActualHeight - popupChild.ActualHeight - e.VerticalChange; + if (delta > 0) + { + this.scrollViewer.Height = Math.Max(0, + Math.Min(Math.Max(this.galleryPanel.GetItemSize().Height, this.scrollViewer.Height + e.VerticalChange), this.MaxDropDownHeight)); + } + else + { + delta = monitorBottom - this.PointToScreen(new Point()).Y - this.ActualHeight - popupChild.ActualHeight; + this.scrollViewer.Height = Math.Max(0, + Math.Min(Math.Max(this.galleryPanel.GetItemSize().Height, this.scrollViewer.Height + delta), this.MaxDropDownHeight)); + } + } + } + + #endregion + } } \ No newline at end of file diff --git a/Fluent.Ribbon/Controls/GalleryGroupContainer.cs b/Fluent.Ribbon/Controls/GalleryGroupContainer.cs index 9785aae58..b67428d03 100644 --- a/Fluent.Ribbon/Controls/GalleryGroupContainer.cs +++ b/Fluent.Ribbon/Controls/GalleryGroupContainer.cs @@ -14,11 +14,11 @@ public class GalleryGroupContainer : HeaderedItemsControl { #region Fields - private Panel previousItemsPanel; private int previousItemsCount; - // Whether MaxWidth of the ItemsPanel needs to be updated - private bool maxMinWidthNeedsToBeUpdated; + // Whether MinWidth/MaxWidth of the ItemsPanel needs to be updated + private bool minMaxWidthNeedsToBeUpdated = true; + private Panel itemsPanel; #endregion @@ -41,9 +41,9 @@ public bool IsHeadered /// This enables animation, styling, binding, etc... /// public static readonly DependencyProperty IsHeaderedProperty = - DependencyProperty.Register("IsHeadered", typeof(bool), + DependencyProperty.Register("IsHeadered", typeof(bool), typeof(GalleryGroupContainer), new UIPropertyMetadata(true)); - + #endregion #region Orientation @@ -66,7 +66,7 @@ public Orientation Orientation typeof(GalleryGroupContainer), new UIPropertyMetadata(Orientation.Horizontal)); #endregion - + #region ItemWidth /// @@ -110,9 +110,9 @@ public double ItemHeight typeof(GalleryGroupContainer), new UIPropertyMetadata(double.NaN)); #endregion - + #region MinItemsInRow - + /// /// Gets or sets minimum items quantity in row /// @@ -130,14 +130,8 @@ public int MinItemsInRow DependencyProperty.Register("MinItemsInRow", typeof(int), typeof(GalleryGroupContainer), new UIPropertyMetadata(0, OnMaxMinItemsInRowChanged)); - static void OnMaxMinItemsInRowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - GalleryGroupContainer galleryGroupContainer = (GalleryGroupContainer) d; - galleryGroupContainer.maxMinWidthNeedsToBeUpdated = true; - } - #endregion - + #region MaxItemsInRow /// @@ -159,6 +153,20 @@ public int MaxItemsInRow #endregion + private Panel RealItemsPanel + { + get + { + return this.itemsPanel ?? (this.itemsPanel = FindItemsPanel(this)); + } + } + + private static void OnMaxMinItemsInRowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var galleryGroupContainer = (GalleryGroupContainer)d; + galleryGroupContainer.minMaxWidthNeedsToBeUpdated = true; + } + #endregion #region Initialization @@ -169,47 +177,57 @@ public int MaxItemsInRow static GalleryGroupContainer() { DefaultStyleKeyProperty.OverrideMetadata(typeof(GalleryGroupContainer), new FrameworkPropertyMetadata(typeof(GalleryGroupContainer))); - StyleProperty.OverrideMetadata(typeof(GalleryGroupContainer), new FrameworkPropertyMetadata(null, new CoerceValueCallback(OnCoerceStyle))); } - // Coerce object style - static object OnCoerceStyle(DependencyObject d, object basevalue) + /// + /// Invoked when the property changes. + /// + /// Old value of the property.New value of the property. + protected override void OnItemsPanelChanged(ItemsPanelTemplate oldItemsPanel, ItemsPanelTemplate newItemsPanel) { - if (basevalue == null) - { - basevalue = ((FrameworkElement)d).TryFindResource(typeof(GalleryGroupContainer)); - } + base.OnItemsPanelChanged(oldItemsPanel, newItemsPanel); - return basevalue; + this.itemsPanel = null; + this.minMaxWidthNeedsToBeUpdated = true; + this.InvalidateMeasure(); } - + #endregion #region MaxWidth Updating - + // Sets MaxWidth of the items panel based of ItemsInRow property private void UpdateMinAndMaxWidth() { - this.maxMinWidthNeedsToBeUpdated = false; + if (this.minMaxWidthNeedsToBeUpdated == false) + { + return; + } - var itemsPanel = FindItemsPanel(this); - if (itemsPanel == null) + if (this.RealItemsPanel == null) { // Item's panel is not ready now if (this.IsLoaded) { Debug.WriteLine("Panel with IsItemsHost = true is not found in GalleryGroupContainer (probably the style is not correct or haven't attached yet)"); } + else + { + // Prevent duplicate registration + this.Loaded -= this.HandleLoaded; + this.Loaded += this.HandleLoaded; + } - this.Dispatcher.BeginInvoke((Action)this.InvalidateMeasure, DispatcherPriority.ContextIdle); return; } + this.minMaxWidthNeedsToBeUpdated = false; + if (this.Orientation == Orientation.Vertical) { // Min/Max is used for Horizontal layout only - itemsPanel.MinWidth = 0; - itemsPanel.MaxWidth = double.PositiveInfinity; + this.RealItemsPanel.MinWidth = 0; + this.RealItemsPanel.MaxWidth = double.PositiveInfinity; return; } @@ -220,17 +238,29 @@ private void UpdateMinAndMaxWidth() return; } - itemsPanel.MinWidth = Math.Min(this.Items.Count, this.MinItemsInRow) * itemWidth + 0.1; - itemsPanel.MaxWidth = Math.Min(this.Items.Count, this.MaxItemsInRow) * itemWidth + 0.1; + this.RealItemsPanel.MinWidth = Math.Min(this.Items.Count, this.MinItemsInRow) * itemWidth + 0.1; + this.RealItemsPanel.MaxWidth = Math.Min(this.Items.Count, this.MaxItemsInRow) * itemWidth + 0.1; } - + + private void HandleLoaded(object sender, RoutedEventArgs e) + { + this.Loaded -= this.HandleLoaded; + + if (this.minMaxWidthNeedsToBeUpdated == false) + { + return; + } + + this.InvalidateMeasure(); + } + /// /// Determinates item's size (return Size.Empty in case of it is not possible) /// /// public Size GetItemSize() { - if (!double.IsNaN(this.ItemWidth) + if (!double.IsNaN(this.ItemWidth) && !double.IsNaN(this.ItemHeight)) { return new Size(this.ItemWidth, this.ItemHeight); @@ -290,16 +320,14 @@ private static Panel FindItemsPanel(DependencyObject obj) /// The maximum size that the method can return. protected override Size MeasureOverride(Size constraint) { - var panel = FindItemsPanel(this); - if (panel != this.previousItemsPanel - || this.previousItemsCount != this.Items.Count - || this.maxMinWidthNeedsToBeUpdated) + if (this.previousItemsCount != this.Items.Count + || this.minMaxWidthNeedsToBeUpdated) { // Track ItemsPanel changing - this.previousItemsPanel = panel; this.previousItemsCount = this.Items.Count; this.UpdateMinAndMaxWidth(); } + return base.MeasureOverride(constraint); } } diff --git a/Fluent.Ribbon/Controls/GalleryGroupIcon.cs b/Fluent.Ribbon/Controls/GalleryGroupIcon.cs deleted file mode 100644 index 6b9237b9b..000000000 --- a/Fluent.Ribbon/Controls/GalleryGroupIcon.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Windows; -using System.Windows.Media; - -namespace Fluent -{ - /// - /// Represents gallery group icon definition - /// - public class GalleryGroupIcon : DependencyObject - { - /// - /// Gets or sets group name - /// - public string GroupName - { - get { return (string)this.GetValue(GroupNameProperty); } - set { this.SetValue(GroupNameProperty, value); } - } - - /// - /// Using a DependencyProperty as the backing store for GroupName. - /// This enables animation, styling, binding, etc... - /// - public static readonly DependencyProperty GroupNameProperty = - DependencyProperty.Register("GroupName", typeof(string), - typeof(GalleryGroupIcon), new UIPropertyMetadata(null)); - - - /// - /// Gets or sets group icon - /// - public ImageSource Icon - { - get { return (ImageSource)this.GetValue(IconProperty); } - set { this.SetValue(IconProperty, value); } - } - - /// - /// Using a DependencyProperty as the backing store for Icon. - /// This enables animation, styling, binding, etc... - /// - public static readonly DependencyProperty IconProperty = - DependencyProperty.Register("Icon", typeof(ImageSource), typeof(GalleryGroupIcon), - new UIPropertyMetadata(null)); - } -} \ No newline at end of file diff --git a/Fluent.Ribbon/Controls/GalleryPanel.cs b/Fluent.Ribbon/Controls/GalleryPanel.cs index d47648ee3..43ac73538 100644 --- a/Fluent.Ribbon/Controls/GalleryPanel.cs +++ b/Fluent.Ribbon/Controls/GalleryPanel.cs @@ -25,7 +25,7 @@ public class GalleryPanel : VirtualizingStackPanel private readonly List galleryGroupContainers = new List(); // Designate that gallery panel must be refreshed its groups - private bool haveToBeRefreshed; + private bool needsRefresh = true; // Group name resolver private Func groupByAdvanced; @@ -209,7 +209,7 @@ public int MinItemsInRow /// This enables animation, styling, binding, etc... /// public static readonly DependencyProperty MinItemsInRowProperty = - DependencyProperty.Register("MinItemsInRow", typeof(int), typeof(GalleryPanel), new UIPropertyMetadata((int)1)); + DependencyProperty.Register("MinItemsInRow", typeof(int), typeof(GalleryPanel), new UIPropertyMetadata(1)); #endregion @@ -359,28 +359,24 @@ public Size GetItemSize() private void Invalidate() { - if (this.haveToBeRefreshed) + if (this.needsRefresh) { return; } - this.haveToBeRefreshed = true; - this.Dispatcher.BeginInvoke((Action)this.RefreshDispatchered, DispatcherPriority.Send); + this.needsRefresh = true; + this.Dispatcher.BeginInvoke((Action)this.Refresh, DispatcherPriority.Send); } - private void RefreshDispatchered() + private void Refresh() { - if (this.haveToBeRefreshed == false) + if (this.needsRefresh == false) { return; } - this.Refresh(); - this.haveToBeRefreshed = false; - } + this.needsRefresh = false; - private void Refresh() - { if (this.itemContainerGeneratorAction == null) { this.itemContainerGeneratorAction = new ItemContainerGeneratorAction((ItemContainerGenerator)this.ItemContainerGenerator, this.Refresh); @@ -429,21 +425,18 @@ private void Refresh() propertyValue = "Undefined"; } - // Make invisible if it is not in filter (or is not grouped) - if (this.IsGrouped == false - || (filter != null && filter.Contains(propertyValue) == false)) - { - item.Measure(new Size(0, 0)); - item.Arrange(new Rect(0, 0, 0, 0)); - } - - // Skip if it is not in filter - if (filter != null + // Make invisible if it is not in filter + if (this.IsGrouped + && filter != null && filter.Contains(propertyValue) == false) { + item.Visibility = Visibility.Collapsed; continue; } + // Make all not filtered items visible + item.Visibility = Visibility.Visible; + // To put all items in one group in case of IsGrouped = False if (this.IsGrouped == false) { @@ -502,6 +495,8 @@ protected override Size MeasureOverride(Size availableSize) { var baseSize = base.MeasureOverride(availableSize); + this.Refresh(); + if (this.galleryGroupContainers.Count == 0) { return baseSize; @@ -531,6 +526,8 @@ protected override Size ArrangeOverride(Size finalSize) { var baseSize = base.ArrangeOverride(finalSize); + this.Refresh(); + if (this.galleryGroupContainers.Count == 0) { return baseSize; diff --git a/Fluent.Ribbon/Controls/InRibbonGallery.cs b/Fluent.Ribbon/Controls/InRibbonGallery.cs index 27d4a9c53..86f3651aa 100644 --- a/Fluent.Ribbon/Controls/InRibbonGallery.cs +++ b/Fluent.Ribbon/Controls/InRibbonGallery.cs @@ -746,6 +746,19 @@ public int MaxItemsInRow public static readonly DependencyProperty MaxItemsInRowProperty = DependencyProperty.Register("MaxItemsInRow", typeof(int), typeof(InRibbonGallery), new UIPropertyMetadata(8, OnMaxItemsInRowChanged)); + private static void OnMaxItemsInRowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var gal = (InRibbonGallery)d; + var maxItemsInRow = (int)e.NewValue; + + if (gal.IsDropDownOpen == false + && gal.galleryPanel != null + && gal.galleryPanel.MaxItemsInRow < maxItemsInRow) + { + gal.galleryPanel.MaxItemsInRow = maxItemsInRow; + } + } + /// /// Gets or sets min count of items in row /// @@ -760,19 +773,18 @@ public int MinItemsInRow /// This enables animation, styling, binding, etc... /// public static readonly DependencyProperty MinItemsInRowProperty = - DependencyProperty.Register("MinItemsInRow", typeof(int), typeof(InRibbonGallery), new UIPropertyMetadata(1)); + DependencyProperty.Register("MinItemsInRow", typeof(int), typeof(InRibbonGallery), new UIPropertyMetadata(1, OnMinItemsInRowChanged)); - private static void OnMaxItemsInRowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnMinItemsInRowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var gal = (InRibbonGallery)d; var minItemsInRow = (int)e.NewValue; if (gal.IsDropDownOpen == false && gal.galleryPanel != null - && gal.galleryPanel.MinItemsInRow < minItemsInRow) + && gal.galleryPanel.MinItemsInRow > minItemsInRow) { gal.galleryPanel.MinItemsInRow = minItemsInRow; - gal.galleryPanel.MaxItemsInRow = minItemsInRow; } } @@ -1110,8 +1122,9 @@ public override void OnApplyTemplate() if (this.galleryPanel != null) { - this.galleryPanel.MinItemsInRow = this.MaxItemsInRow; + this.galleryPanel.MinItemsInRow = this.MinItemsInRow; this.galleryPanel.MaxItemsInRow = this.MaxItemsInRow; + this.galleryPanel.UpdateMinAndMaxWidth(); } this.snappedImage = this.GetTemplateChild("PART_FakeImage") as Image; @@ -1139,6 +1152,8 @@ private void ForceContentRefreshToFixLayout() this.controlPresenter.Content = null; this.controlPresenter.Content = this.galleryPanel; + + this.galleryPanel.UpdateMinAndMaxWidth(); } private void OnPopupPreviewMouseUp(object sender, MouseButtonEventArgs e) @@ -1172,14 +1187,15 @@ private void OnDropDownClick(object sender, RoutedEventArgs e) // Handles drop down opened private void OnDropDownClosed(object sender, EventArgs e) { - this.galleryPanel.Width = double.NaN; + this.popupControlPresenter.Content = null; + this.controlPresenter.Content = this.galleryPanel; + this.galleryPanel.IsGrouped = false; this.galleryPanel.MinItemsInRow = this.MinItemsInRow; this.galleryPanel.MaxItemsInRow = this.MaxItemsInRow; + this.galleryPanel.Width = double.NaN; this.galleryPanel.UpdateMinAndMaxWidth(); - this.popupControlPresenter.Content = null; - this.controlPresenter.Content = this.galleryPanel; this.Dispatcher.BeginInvoke(DispatcherPriority.SystemIdle, (ThreadStart)(() => { if (this.quickAccessGallery == null @@ -1212,16 +1228,15 @@ private void OnDropDownOpened(object sender, EventArgs e) this.controlPresenter.Content = null; this.popupControlPresenter.Content = this.galleryPanel; - this.galleryPanel.Width = double.NaN; - this.scrollViewer.Height = double.NaN; - - this.DropDownOpened?.Invoke(this, e); + this.galleryPanel.IsGrouped = true; this.galleryPanel.MinItemsInRow = this.MinItemsInDropDownRow; this.galleryPanel.MaxItemsInRow = this.MaxItemsInDropDownRow; + this.galleryPanel.Width = double.NaN; this.galleryPanel.UpdateMinAndMaxWidth(); - this.galleryPanel.IsGrouped = true; + this.DropDownOpened?.Invoke(this, e); + this.dropDownButton.IsChecked = true; this.canOpenDropDown = false; diff --git a/Fluent.Ribbon/Controls/RadioButton.cs b/Fluent.Ribbon/Controls/RadioButton.cs index 85bd66827..a783949d4 100644 --- a/Fluent.Ribbon/Controls/RadioButton.cs +++ b/Fluent.Ribbon/Controls/RadioButton.cs @@ -98,7 +98,7 @@ public object Header /// public object Icon { - get { return (ImageSource)this.GetValue(IconProperty); } + get { return this.GetValue(IconProperty); } set { this.SetValue(IconProperty, value); } } @@ -109,11 +109,19 @@ public object Icon private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - RadioButton element = d as RadioButton; - FrameworkElement oldElement = e.OldValue as FrameworkElement; - if (oldElement != null) element.RemoveLogicalChild(oldElement); - FrameworkElement newElement = e.NewValue as FrameworkElement; - if (newElement != null) element.AddLogicalChild(newElement); + var element = (RadioButton)d; + + var oldElement = e.OldValue as FrameworkElement; + if (oldElement != null) + { + element.RemoveLogicalChild(oldElement); + } + + var newElement = e.NewValue as FrameworkElement; + if (newElement != null) + { + element.AddLogicalChild(newElement); + } } #endregion @@ -123,9 +131,9 @@ private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedE /// /// Gets or sets button large icon /// - public ImageSource LargeIcon + public object LargeIcon { - get { return (ImageSource)this.GetValue(LargeIconProperty); } + get { return this.GetValue(LargeIconProperty); } set { this.SetValue(LargeIconProperty, value); } } @@ -133,9 +141,7 @@ public ImageSource LargeIcon /// Using a DependencyProperty as the backing store for SmallIcon. /// This enables animation, styling, binding, etc... /// - public static readonly DependencyProperty LargeIconProperty = - DependencyProperty.Register("LargeIcon", typeof(ImageSource), - typeof(RadioButton), new UIPropertyMetadata(null)); + public static readonly DependencyProperty LargeIconProperty = DependencyProperty.Register("LargeIcon", typeof(object), typeof(RadioButton), new UIPropertyMetadata(null)); #endregion @@ -149,23 +155,10 @@ public ImageSource LargeIcon [SuppressMessage("Microsoft.Performance", "CA1810")] static RadioButton() { - Type type = typeof(RadioButton); + var type = typeof(RadioButton); DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(type)); ContextMenuService.Attach(type); ToolTipService.Attach(type); - StyleProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(null, new CoerceValueCallback(OnCoerceStyle))); - } - - // Coerce object style - static object OnCoerceStyle(DependencyObject d, object basevalue) - { - if (basevalue == null) - { - //basevalue = (d as FrameworkElement).TryFindResource(typeof(QuickAccessToolBar)); - basevalue = (d as FrameworkElement).TryFindResource(typeof(RadioButton)); - } - - return basevalue; } /// @@ -188,7 +181,7 @@ public RadioButton() /// Control which represents shortcut item public virtual FrameworkElement CreateQuickAccessItem() { - RadioButton button = new RadioButton(); + var button = new RadioButton(); RibbonControl.Bind(this, button, "IsChecked", IsCheckedProperty, BindingMode.TwoWay); button.Click += ((sender, e) => this.RaiseEvent(e)); diff --git a/Fluent.Ribbon/Controls/Ribbon.cs b/Fluent.Ribbon/Controls/Ribbon.cs index 91a8d3598..aaf8a954b 100644 --- a/Fluent.Ribbon/Controls/Ribbon.cs +++ b/Fluent.Ribbon/Controls/Ribbon.cs @@ -21,6 +21,7 @@ namespace Fluent { using System.ComponentModel; using System.Windows.Threading; + using ControlzEx.Microsoft.Windows.Shell; using Fluent.Extensions; // TODO: improve style parts naming & using @@ -1051,8 +1052,10 @@ private static void OnIsMinimizedChanged(DependencyObject d, DependencyPropertyC private static void OnCanMinimizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ribbon = (Ribbon)d; - - ribbon.TabControl.CanMinimize = ribbon.CanMinimize; + if (ribbon.TabControl != null) + { + ribbon.TabControl.CanMinimize = ribbon.CanMinimize; + } } /// @@ -1414,6 +1417,8 @@ public Ribbon() this.VerticalAlignment = VerticalAlignment.Top; KeyboardNavigation.SetDirectionalNavigation(this, KeyboardNavigationMode.Contained); + WindowChrome.SetIsHitTestVisibleInChrome(this, true); + this.keyTipService = new KeyTipService(this); this.Loaded += this.OnLoaded; diff --git a/Fluent.Ribbon/Controls/RibbonControl.cs b/Fluent.Ribbon/Controls/RibbonControl.cs index ed66dc330..b15e589e8 100644 --- a/Fluent.Ribbon/Controls/RibbonControl.cs +++ b/Fluent.Ribbon/Controls/RibbonControl.cs @@ -73,16 +73,23 @@ public object Icon /// /// Using a DependencyProperty as the backing store for Icon. This enables animation, styling, binding, etc... /// - public static readonly DependencyProperty IconProperty = - DependencyProperty.Register("Icon", typeof(object), typeof(RibbonControl), new UIPropertyMetadata(null, OnIconChanged)); + public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(object), typeof(RibbonControl), new UIPropertyMetadata(null, OnIconChanged)); private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - RibbonControl element = d as RibbonControl; - FrameworkElement oldElement = e.OldValue as FrameworkElement; - if (oldElement != null) element.RemoveLogicalChild(oldElement); - FrameworkElement newElement = e.NewValue as FrameworkElement; - if (newElement != null) element.AddLogicalChild(newElement); + var element = (RibbonControl)d; + + var oldElement = e.OldValue as FrameworkElement; + if (oldElement != null) + { + element.RemoveLogicalChild(oldElement); + } + + var newElement = e.NewValue as FrameworkElement; + if (newElement != null) + { + element.AddLogicalChild(newElement); + } } #endregion @@ -179,7 +186,7 @@ private static void OnCommandChanged(DependencyObject d, DependencyPropertyChang newCommand.CanExecuteChanged += control.OnCommandCanExecuteChanged; var routedUiCommand = e.NewValue as RoutedUICommand; - if (routedUiCommand != null + if (routedUiCommand != null && control.Header == null) { control.Header = routedUiCommand.Text; @@ -200,7 +207,7 @@ private void OnCommandCanExecuteChanged(object sender, EventArgs e) private void UpdateCanExecute() { - var canExecute = this.Command != null + var canExecute = this.Command != null && this.CanExecuteCommand(); if (this.currentCanExecute != canExecute) @@ -371,7 +378,7 @@ public static void BindQuickAccessItem(FrameworkElement source, FrameworkElement rect.Width = 16; rect.Height = 16; rect.Fill = new VisualBrush(iconVisual); - ((IRibbonControl) element).Icon = rect; + ((IRibbonControl)element).Icon = rect; } else { diff --git a/Fluent.Ribbon/Controls/RibbonGroupBox.cs b/Fluent.Ribbon/Controls/RibbonGroupBox.cs index c42f9b01b..ccec98f62 100644 --- a/Fluent.Ribbon/Controls/RibbonGroupBox.cs +++ b/Fluent.Ribbon/Controls/RibbonGroupBox.cs @@ -25,7 +25,7 @@ namespace Fluent [TemplatePart(Name = "PART_Popup", Type = typeof(Popup))] [TemplatePart(Name = "PART_UpPanel", Type = typeof(Panel))] public class RibbonGroupBox : ItemsControl, IQuickAccessItemProvider, IDropDownControl, IKeyTipedControl, IHeaderedControl - { + { #region Fields // up part @@ -35,7 +35,7 @@ public class RibbonGroupBox : ItemsControl, IQuickAccessItemProvider, IDropDownC // Freezed image (created during snapping) private Image snappedImage; - + // Is visual currently snapped private bool isSnapped; @@ -303,7 +303,7 @@ static void OnDialogLauncherButtonKeyTipKeysChanged(DependencyObject d, Dependen /// public object LauncherIcon { - get { return (ImageSource)this.GetValue(LauncherIconProperty); } + get { return this.GetValue(LauncherIconProperty); } set { this.SetValue(LauncherIconProperty, value); } } @@ -516,7 +516,6 @@ protected override IEnumerator LogicalChildren /// Gets or sets icon /// public object Icon - //public ImageSource Icon { get { return this.GetValue(IconProperty); } set { this.SetValue(IconProperty, value); } @@ -525,8 +524,7 @@ public object Icon /// /// Using a DependencyProperty as the backing store for Icon. This enables animation, styling, binding, etc... /// - public static readonly DependencyProperty IconProperty = - RibbonControl.IconProperty.AddOwner(typeof(RibbonGroupBox), new UIPropertyMetadata(null, OnIconChanged)); + public static readonly DependencyProperty IconProperty = RibbonControl.IconProperty.AddOwner(typeof(RibbonGroupBox), new UIPropertyMetadata(null, OnIconChanged)); private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) @@ -917,11 +915,11 @@ protected override void OnChildDesiredSizeChanged(UIElement child) private StateScale GetCurrentIntermediateStateScale() { - var stateScale = new StateScale - { - Scale = this.ScaleIntermediate, - State = this.StateIntermediate - }; + var stateScale = new StateScale + { + Scale = this.ScaleIntermediate, + State = this.StateIntermediate + }; return stateScale; } @@ -996,7 +994,7 @@ public FrameworkElement CreateQuickAccessItem() groupBox.DropDownOpened += this.OnQuickAccessOpened; groupBox.DropDownClosed += this.OnQuickAccessClosed; - + groupBox.State = RibbonGroupBoxState.QuickAccess; RibbonControl.Bind(this, groupBox, "ItemTemplateSelector", ItemTemplateSelectorProperty, BindingMode.OneWay); @@ -1099,7 +1097,7 @@ public bool CanAddToQuickAccessToolBar /// Using a DependencyProperty as the backing store for CanAddToQuickAccessToolBar. This enables animation, styling, binding, etc... /// public static readonly DependencyProperty CanAddToQuickAccessToolBarProperty = - DependencyProperty.Register("CanAddToQuickAccessToolBar", typeof(bool), typeof(RibbonGroupBox), new UIPropertyMetadata(true, RibbonControl.OnCanAddToQuickAccessToolbarChanged)); + DependencyProperty.Register("CanAddToQuickAccessToolBar", typeof(bool), typeof(RibbonGroupBox), new UIPropertyMetadata(true, RibbonControl.OnCanAddToQuickAccessToolbarChanged)); #endregion diff --git a/Fluent.Ribbon/Controls/RibbonItemsControl.cs b/Fluent.Ribbon/Controls/RibbonItemsControl.cs index 316ba26af..40278a622 100644 --- a/Fluent.Ribbon/Controls/RibbonItemsControl.cs +++ b/Fluent.Ribbon/Controls/RibbonItemsControl.cs @@ -3,7 +3,6 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Markup; -using System.Windows.Media; namespace Fluent { @@ -96,7 +95,7 @@ public object Header /// public object Icon { - get { return (ImageSource)this.GetValue(IconProperty); } + get { return this.GetValue(IconProperty); } set { this.SetValue(IconProperty, value); } } diff --git a/Fluent.Ribbon/Controls/RibbonTabControl.cs b/Fluent.Ribbon/Controls/RibbonTabControl.cs index c3f718a51..51a6f4226 100644 --- a/Fluent.Ribbon/Controls/RibbonTabControl.cs +++ b/Fluent.Ribbon/Controls/RibbonTabControl.cs @@ -126,7 +126,7 @@ public bool CanMinimize /// /// Using a DependencyProperty as the backing store for . This enables animation, styling, binding, etc... /// - public static readonly DependencyProperty CanMinimizeProperty = DependencyProperty.Register("CanMinimize", typeof(bool), typeof(RibbonTabControl), new UIPropertyMetadata(true, OnCanMinimizeChanged)); + public static readonly DependencyProperty CanMinimizeProperty = DependencyProperty.Register("CanMinimize", typeof(bool), typeof(RibbonTabControl), new UIPropertyMetadata(true)); /// @@ -670,25 +670,6 @@ private void OnGeneratorStatusChanged(object sender, EventArgs e) } } - // Handles CanMinimizeChanges - private static void OnCanMinimizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var tab = (RibbonTabControl)d; - var toggleButton = tab.Template.FindName("PART_MinimizeButton", tab) as Fluent.ToggleButton; - if (toggleButton != null) - { - if (tab.CanMinimize) - { - - toggleButton.Visibility = Visibility.Visible; - } - else - { - toggleButton.Visibility = Visibility.Collapsed; - } - } - } - // Handles IsMinimized changed private static void OnMinimizedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { diff --git a/Fluent.Ribbon/Controls/RibbonWindow.cs b/Fluent.Ribbon/Controls/RibbonWindow.cs index 134a37b86..d4037a5f4 100644 --- a/Fluent.Ribbon/Controls/RibbonWindow.cs +++ b/Fluent.Ribbon/Controls/RibbonWindow.cs @@ -7,7 +7,7 @@ using System.Windows.Input; using System.Windows.Interactivity; using ControlzEx.Behaviors; - + using Fluent.Extensions; using Fluent.Helpers; @@ -58,7 +58,7 @@ public Thickness ResizeBorderThickness /// Using a DependencyProperty as the backing store for ResizeBorderTickness. This enables animation, styling, binding, etc... /// public static readonly DependencyProperty ResizeBorderThicknessProperty = - DependencyProperty.Register("ResizeBorderThickness", typeof(Thickness), typeof(RibbonWindow), new UIPropertyMetadata(WindowChromeBehavior.ResizeBorderThicknessProperty.DefaultMetadata.DefaultValue)); + DependencyProperty.Register("ResizeBorderThickness", typeof(Thickness), typeof(RibbonWindow), new UIPropertyMetadata(new Thickness(8D))); /// /// Gets or sets glass border thickness @@ -220,6 +220,9 @@ private static object OnCoerceStyle(DependencyObject d, object basevalue) public RibbonWindow() { this.SizeChanged += this.OnSizeChanged; + + // WindowChrome initialization has to occur in constructor. Otherwise the load event is fired early. + this.InitializeWindowChromeBehavior(); } #endregion @@ -235,14 +238,12 @@ protected override void OnSourceInitialized(EventArgs e) base.OnSourceInitialized(e); this.UpdateCanUseDwm(); - - this.InitializeWindowChromeBehavior(); } /// /// Initializes the WindowChromeBehavior which is needed to render the custom WindowChrome /// - protected virtual void InitializeWindowChromeBehavior() + private void InitializeWindowChromeBehavior() { var behavior = new WindowChromeBehavior(); BindingOperations.SetBinding(behavior, WindowChromeBehavior.CaptionHeightProperty, new Binding { Path = new PropertyPath(RibbonProperties.TitleBarHeightProperty), Source = this }); @@ -253,22 +254,6 @@ protected virtual void InitializeWindowChromeBehavior() Interaction.GetBehaviors(this).Add(behavior); } - /// - /// Called when the property changes. - /// - /// A reference to the root of the old content tree.A reference to the root of the new content tree. - protected override void OnContentChanged(object oldContent, object newContent) - { - base.OnContentChanged(oldContent, newContent); - - var content = newContent as IInputElement; - - if (content != null) - { - WindowChrome.SetIsHitTestVisibleInChrome(content, true); - } - } - #endregion private static void OnDontUseDwmChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) @@ -308,8 +293,6 @@ private void UpdateCanUseDwm() && this.DontUseDwm == false; } - #region Metro - /// /// When overridden in a derived class, is invoked whenever application code or internal processes call . /// @@ -329,28 +312,15 @@ public override void OnApplyTemplate() this.WindowCommands = new WindowCommands(); } - this.iconImage = this.GetTemplateChild(PART_Icon) as FrameworkElement; + this.iconImage = this.GetPart(PART_Icon); if (this.iconImage != null) { - WindowChrome.SetIsHitTestVisibleInChrome(this.iconImage, true); - this.iconImage.MouseDown += this.HandleIconMouseDown; } - var partContentPresenter = this.GetTemplateChild(PART_ContentPresenter) as UIElement; - - if (partContentPresenter != null) - { - WindowChrome.SetIsHitTestVisibleInChrome(partContentPresenter, true); - } - - var partWindowCommands = this.GetTemplateChild(PART_WindowCommands) as UIElement; - - if (partWindowCommands != null) - { - WindowChrome.SetIsHitTestVisibleInChrome(partWindowCommands, true); - } + this.GetPart(PART_Icon)?.SetValue(WindowChrome.IsHitTestVisibleInChromeProperty, true); + this.GetPart(PART_WindowCommands)?.SetValue(WindowChrome.IsHitTestVisibleInChromeProperty, true); } /// @@ -394,6 +364,15 @@ private void HandleIconMouseDown(object sender, MouseButtonEventArgs e) } } - #endregion + /// + /// Gets the template child with the given name. + /// + /// The interface type inheirted from DependencyObject. + /// The name of the template child. + internal T GetPart(string name) + where T : DependencyObject + { + return this.GetTemplateChild(name) as T; + } } } \ No newline at end of file diff --git a/Fluent.Ribbon/Controls/TwoLineLabel.cs b/Fluent.Ribbon/Controls/TwoLineLabel.cs index b93bb0810..6b7880f20 100644 --- a/Fluent.Ribbon/Controls/TwoLineLabel.cs +++ b/Fluent.Ribbon/Controls/TwoLineLabel.cs @@ -1,18 +1,22 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Documents; - -namespace Fluent +namespace Fluent { + using System; + using System.ComponentModel; + using System.Diagnostics.CodeAnalysis; + using System.Windows; + using System.Windows.Controls; + using System.Windows.Documents; + using System.Windows.Markup; + /// /// Represents specific label to use in particular ribbon controls /// + [DefaultProperty(nameof(Text))] + [ContentProperty(nameof(Text))] [TemplatePart(Name = "PART_TextRun", Type = typeof(TextBlock))] [TemplatePart(Name = "PART_TextRun2", Type = typeof(TextBlock))] [TemplatePart(Name = "PART_Glyph", Type = typeof(InlineUIContainer))] - public class TwoLineLabel: Control + public class TwoLineLabel : Control { #region Fields @@ -41,7 +45,7 @@ public bool HasTwoLines /// This enables animation, styling, binding, etc... /// public static readonly DependencyProperty HasTwoLinesProperty = - DependencyProperty.Register("HasTwoLines", typeof(bool), typeof(TwoLineLabel), new UIPropertyMetadata(true,OnHasTwoLinesChanged)); + DependencyProperty.Register("HasTwoLines", typeof(bool), typeof(TwoLineLabel), new UIPropertyMetadata(true, OnHasTwoLinesChanged)); /// /// Handles HasTwoLines property changes @@ -50,7 +54,7 @@ public bool HasTwoLines /// The event data private static void OnHasTwoLinesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - (d as TwoLineLabel).UpdateTextRun(); + ((TwoLineLabel)d).UpdateTextRun(); } /// @@ -75,7 +79,7 @@ public bool HasGlyph /// The event data private static void OnHasGlyphChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - (d as TwoLineLabel).UpdateTextRun(); + ((TwoLineLabel)d).UpdateTextRun(); } /// @@ -91,7 +95,7 @@ public string Text /// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc... /// public static readonly DependencyProperty TextProperty = - DependencyProperty.Register("Text", typeof(string), typeof(TwoLineLabel), new UIPropertyMetadata("TwoLineLabel", OnTextChanged)); + DependencyProperty.Register("Text", typeof(string), typeof(TwoLineLabel), new UIPropertyMetadata(string.Empty, OnTextChanged)); #endregion @@ -104,11 +108,11 @@ public string Text static TwoLineLabel() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TwoLineLabel), new FrameworkPropertyMetadata(typeof(TwoLineLabel))); - StyleProperty.OverrideMetadata(typeof(TwoLineLabel), new FrameworkPropertyMetadata(null, new CoerceValueCallback(OnCoerceStyle))); + StyleProperty.OverrideMetadata(typeof(TwoLineLabel), new FrameworkPropertyMetadata(null, OnCoerceStyle)); } // Coerce object style - static object OnCoerceStyle(DependencyObject d, object basevalue) + private static object OnCoerceStyle(DependencyObject d, object basevalue) { if (basevalue == null) { @@ -152,8 +156,8 @@ public override void OnApplyTemplate() /// The event data private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - TwoLineLabel label = d as TwoLineLabel; - label.UpdateTextRun(); + var label = (TwoLineLabel)d; + label?.UpdateTextRun(); } #endregion @@ -163,59 +167,69 @@ private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedE /// /// Updates text run adds newline if HasTwoLines == true /// - void UpdateTextRun() + private void UpdateTextRun() { - if ((this.textRun != null)&&(this.textRun2 != null)&&(this.Text != null)) + if (this.textRun == null + || this.textRun2 == null) + { + return; + } + + if (this.HasTwoLines == false + || string.IsNullOrEmpty(this.Text)) { this.textRun.Text = this.Text; - this.textRun2.Text = ""; - string text = this.Text.Trim(); - if (this.HasTwoLines) + this.textRun2.Text = string.Empty; + return; + } + + var text = this.Text.Trim(); + + // Find soft hyphen, break at its position and display a normal hyphen. + var hyphenIndex = text.IndexOf((char)173); + + if (hyphenIndex >= 0) + { + this.textRun.Text = text.Substring(0, hyphenIndex) + "-"; + this.textRun2.Text = text.Substring(hyphenIndex) + " "; + } + else + { + var centerIndex = this.Text.Length / 2; + // Find spaces nearest to center from left and right + var leftSpaceIndex = text.LastIndexOf(" ", centerIndex, centerIndex, StringComparison.CurrentCulture); + var rightSpaceIndex = text.IndexOf(" ", centerIndex, StringComparison.CurrentCulture); + + if (leftSpaceIndex == -1 + && rightSpaceIndex == -1) + { + this.textRun.Text = this.Text; + this.textRun2.Text = string.Empty; + } + else if (leftSpaceIndex == -1) + { + // Finds only space from right. New line adds on it + this.textRun.Text = text.Substring(0, rightSpaceIndex); + this.textRun2.Text = text.Substring(rightSpaceIndex) + " "; + } + else if (rightSpaceIndex == -1) + { + // Finds only space from left. New line adds on it + this.textRun.Text = text.Substring(0, leftSpaceIndex); + this.textRun2.Text = text.Substring(leftSpaceIndex) + " "; + } + else { - // Find soft hyphen, break at its position and display a normal hyphen. - int hyphenIndex = text.IndexOf((char)173); - if (hyphenIndex >= 0) + // Find nearest to center space and add new line on it + if (Math.Abs(centerIndex - leftSpaceIndex) < Math.Abs(centerIndex - rightSpaceIndex)) { - this.textRun.Text = text.Substring(0, hyphenIndex) + "-"; - this.textRun2.Text = text.Substring(hyphenIndex) + " "; + this.textRun.Text = text.Substring(0, leftSpaceIndex); + this.textRun2.Text = text.Substring(leftSpaceIndex) + " "; } else { - int centerIndex = this.Text.Length / 2; - // Find spaces nearest to center from left and right - int leftSpaceIndex = text.LastIndexOf(" ", centerIndex, centerIndex); - int rightSpaceIndex = text.IndexOf(" ", centerIndex, StringComparison.CurrentCulture); - if ((leftSpaceIndex == -1) && (rightSpaceIndex == -1)) - { - // The text can`t be separated. Add new line for glyph - //textRun.Text += '\u0085'; - } - else if (leftSpaceIndex == -1) - { - // Finds only space from right. New line adds on it - this.textRun.Text = text.Substring(0, rightSpaceIndex); - this.textRun2.Text = text.Substring(rightSpaceIndex) + " "; - } - else if (rightSpaceIndex == -1) - { - // Finds only space from left. New line adds on it - this.textRun.Text = text.Substring(0, leftSpaceIndex); - this.textRun2.Text = text.Substring(leftSpaceIndex) + " "; - } - else - { - // Find nearest to center space and add new line on it - if (Math.Abs(centerIndex - leftSpaceIndex) < Math.Abs(centerIndex - rightSpaceIndex)) - { - this.textRun.Text = text.Substring(0, leftSpaceIndex); - this.textRun2.Text = text.Substring(leftSpaceIndex) + " "; - } - else - { - this.textRun.Text = text.Substring(0, rightSpaceIndex); - this.textRun2.Text = text.Substring(rightSpaceIndex) + " "; - } - } + this.textRun.Text = text.Substring(0, rightSpaceIndex); + this.textRun2.Text = text.Substring(rightSpaceIndex) + " "; } } } @@ -223,4 +237,4 @@ void UpdateTextRun() #endregion } -} +} \ No newline at end of file diff --git a/Fluent.Ribbon/Converters/ApplicationMenuRightContentExtractorConverter.cs b/Fluent.Ribbon/Converters/ApplicationMenuRightScrollViewerExtractorConverter.cs similarity index 88% rename from Fluent.Ribbon/Converters/ApplicationMenuRightContentExtractorConverter.cs rename to Fluent.Ribbon/Converters/ApplicationMenuRightScrollViewerExtractorConverter.cs index ca0a7a817..2fa2f6833 100644 --- a/Fluent.Ribbon/Converters/ApplicationMenuRightContentExtractorConverter.cs +++ b/Fluent.Ribbon/Converters/ApplicationMenuRightScrollViewerExtractorConverter.cs @@ -2,13 +2,13 @@ { using System; using System.Globalization; - using System.Windows.Controls; + using System.Windows; using System.Windows.Data; /// /// Extracts right content presenter of application menu converter /// - public class ApplicationMenuRightContentExtractorConverter : IValueConverter + public class ApplicationMenuRightScrollViewerExtractorConverter : IValueConverter { #region Implementation of IValueConverter @@ -24,7 +24,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn var menu = value as ApplicationMenu; if (menu != null) { - return menu.Template.FindName("PART_RightContentPresenter", menu) as ContentPresenter; + return menu.Template.FindName("PART_ScrollViewer", menu) as UIElement; } return value; diff --git a/Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj b/Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj index 95ca4e3e9..948f76797 100644 --- a/Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj +++ b/Fluent.Ribbon/Fluent.Ribbon.NET 4.0.csproj @@ -53,7 +53,7 @@ - ..\packages\ControlzEx.2.0.0-dev025\lib\net40\ControlzEx.dll + ..\packages\ControlzEx.2.0.0-dev038\lib\net40\ControlzEx.dll True @@ -61,7 +61,7 @@ - ..\packages\ControlzEx.2.0.0-dev025\lib\net40\System.Windows.Interactivity.dll + ..\packages\ControlzEx.2.0.0-dev038\lib\net40\System.Windows.Interactivity.dll True @@ -89,12 +89,15 @@ + + + - + @@ -135,7 +138,6 @@ - @@ -208,6 +210,14 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + MSBuild:Compile Designer diff --git a/Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj b/Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj index 688fcadc2..13a21801a 100644 --- a/Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj +++ b/Fluent.Ribbon/Fluent.Ribbon.NET 4.5.csproj @@ -53,7 +53,7 @@ - ..\packages\ControlzEx.2.0.0-dev025\lib\net45\ControlzEx.dll + ..\packages\ControlzEx.2.0.0-dev038\lib\net45\ControlzEx.dll True @@ -61,7 +61,7 @@ - ..\packages\ControlzEx.2.0.0-dev025\lib\net45\System.Windows.Interactivity.dll + ..\packages\ControlzEx.2.0.0-dev038\lib\net45\System.Windows.Interactivity.dll True @@ -89,12 +89,15 @@ + + + - + @@ -135,7 +138,6 @@ - @@ -208,6 +210,14 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + MSBuild:Compile Designer diff --git a/Fluent.Ribbon/Helpers/PoupHelper.cs b/Fluent.Ribbon/Helpers/PoupHelper.cs new file mode 100644 index 000000000..312f0cdfd --- /dev/null +++ b/Fluent.Ribbon/Helpers/PoupHelper.cs @@ -0,0 +1,52 @@ +namespace Fluent.Helpers +{ + using System.Windows; + using System.Windows.Controls.Primitives; + + /// + /// Helper class to position . + /// + public static class PopupHelper + { + /// + /// Positions like would but ignores the value of . + /// + public static CustomPopupPlacementCallback SimplePlacementCallback => GetSimplePlacement; + + /// + /// Gets the values for a like would but ignores the value of . + /// + public static CustomPopupPlacement[] GetSimplePlacement(Size popupSize, Size targetSize, Point offset) + { + // Create placements which should never cover the target + return new[] + { + new CustomPopupPlacement + { + Point = new Point(0, 0), + PrimaryAxis = PopupPrimaryAxis.None + }, + new CustomPopupPlacement + { + Point = new Point(-popupSize.Width, 0), + PrimaryAxis = PopupPrimaryAxis.Horizontal + }, + new CustomPopupPlacement + { + Point = new Point(0, -popupSize.Height - targetSize.Height), + PrimaryAxis = PopupPrimaryAxis.Vertical + }, + new CustomPopupPlacement + { + Point = new Point(-popupSize.Width, -popupSize.Height), + PrimaryAxis = PopupPrimaryAxis.Vertical + }, + new CustomPopupPlacement + { + Point = new Point(targetSize.Width, -popupSize.Height), + PrimaryAxis = PopupPrimaryAxis.Horizontal + } + }; + } + } +} \ No newline at end of file diff --git a/Fluent.Ribbon/Internal/FocusWrapper.cs b/Fluent.Ribbon/Internal/FocusWrapper.cs new file mode 100644 index 000000000..3538390e6 --- /dev/null +++ b/Fluent.Ribbon/Internal/FocusWrapper.cs @@ -0,0 +1,54 @@ +namespace Fluent.Internal +{ + using System; + using System.Windows; + using System.Windows.Input; + + internal class FocusWrapper + { + private readonly IInputElement inputElement; + private readonly IntPtr handle; + + private FocusWrapper(IInputElement inputElement) + { + this.inputElement = inputElement; + } + + private FocusWrapper(IntPtr handle) + { + this.handle = handle; + } + + public void Focus() + { + if (this.inputElement != null) + { + this.inputElement.Focus(); + return; + } + + if (this.handle != IntPtr.Zero) + { + NativeMethods.SetFocus(this.handle); + return; + } + } + + public static FocusWrapper GetWrapperForCurrentFocus() + { + if (Keyboard.FocusedElement != null) + { + return new FocusWrapper(Keyboard.FocusedElement); + } + + var handle = NativeMethods.GetFocus(); + + if (handle != IntPtr.Zero) + { + return new FocusWrapper(handle); + } + + return null; + } + } +} \ No newline at end of file diff --git a/Fluent.Ribbon/Internal/KeyEventUtility.cs b/Fluent.Ribbon/Internal/KeyEventUtility.cs new file mode 100644 index 000000000..e84e023c5 --- /dev/null +++ b/Fluent.Ribbon/Internal/KeyEventUtility.cs @@ -0,0 +1,34 @@ +namespace Fluent.Internal +{ + using System.Windows.Input; + + internal static class KeyEventUtility + { + public static string GetStringFromKey(Key key) + { + var keyboardState = new byte[256]; + if (NativeMethods.GetKeyboardState(keyboardState) == false) + { + return null; + } + + var virtualKey = KeyInterop.VirtualKeyFromKey(key); + var scanCode = NativeMethods.MapVirtualKey((uint)virtualKey, NativeMethods.MapType.MAPVK_VK_TO_VSC); + var chars = new char[1]; + + var result = NativeMethods.ToUnicode((uint)virtualKey, scanCode, keyboardState, chars, chars.Length, 0); + switch (result) + { + case -1: + case 0: + return null; + + case 1: + return chars[0].ToString(); + + default: + return null; + } + } + } +} \ No newline at end of file diff --git a/Fluent.Ribbon/Internal/NativeMethods.cs b/Fluent.Ribbon/Internal/NativeMethods.cs index 4d9236a61..b99c006f8 100644 --- a/Fluent.Ribbon/Internal/NativeMethods.cs +++ b/Fluent.Ribbon/Internal/NativeMethods.cs @@ -207,6 +207,12 @@ internal static void PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lP [DllImport("user32.dll")] internal static extern int SetWindowLong(IntPtr hWnd, GWL nIndex, WS dwNewLong); + [DllImport("user32.dll")] + internal static extern IntPtr GetFocus(); + + [DllImport("user32.dll")] + internal static extern IntPtr SetFocus(IntPtr hWnd); + /// Add and remove a native WindowStyle from the HWND. /// A HWND for a window. /// The styles to be removed. These can be bitwise combined. @@ -230,6 +236,25 @@ public static bool _ModifyStyle(this IntPtr _hwnd, WS removeStyle, WS addStyle) return true; } + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + internal static extern int ToUnicode(uint virtualKey, uint scanCode, byte[] keyStates, [MarshalAs(UnmanagedType.LPArray)] [Out] char[] chars, int charMaxCount, uint flags); + + [DllImport("user32.dll")] + internal static extern bool GetKeyboardState(byte[] lpKeyState); + + [DllImport("user32.dll")] + internal static extern uint MapVirtualKey(uint uCode, MapType uMapType); + + // ReSharper disable InconsistentNaming + internal enum MapType : uint + { + MAPVK_VK_TO_VSC = 0x0, + MAPVK_VSC_TO_VK = 0x1, + MAPVK_VK_TO_CHAR = 0x2, + MAPVK_VSC_TO_VK_EX = 0x3, + } + // ReSharper restore InconsistentNaming + /// /// GetWindowLong values, GWL_* /// diff --git a/Fluent.Ribbon/Internal/UIHelper.cs b/Fluent.Ribbon/Internal/UIHelper.cs index 01ae4934e..cad9cbe28 100644 --- a/Fluent.Ribbon/Internal/UIHelper.cs +++ b/Fluent.Ribbon/Internal/UIHelper.cs @@ -60,6 +60,10 @@ public static TChildItem FindVisualChild(DependencyObject parent) wh return null; } + /// + /// Gets all visual children of . + /// + /// public static IEnumerable GetVisualChildren(DependencyObject parent) { var visualChildrenCount = VisualTreeHelper.GetChildrenCount(parent); @@ -74,5 +78,35 @@ public static IEnumerable GetVisualChildren(DependencyObject p } } } + + /// + /// Finds the parent control of type . + /// First looks at the visual tree and then at the logical tree to find the parent. + /// + /// The found visual/logical parent or null. + public static T GetParent(DependencyObject element) + where T : DependencyObject + { + var item = element; + + while (item != null + && item is T == false) + { + item = VisualTreeHelper.GetParent(item); + } + + if (item == null) + { + item = element; + + while (item != null && + item is T == false) + { + item = LogicalTreeHelper.GetParent(item); + } + } + + return (T)item; + } } } \ No newline at end of file diff --git a/Fluent.Ribbon/Services/KeyTipService.cs b/Fluent.Ribbon/Services/KeyTipService.cs index e1f7b428b..193187f37 100644 --- a/Fluent.Ribbon/Services/KeyTipService.cs +++ b/Fluent.Ribbon/Services/KeyTipService.cs @@ -24,8 +24,9 @@ internal class KeyTipService // Is KeyTips Actived now private KeyTipAdorner activeAdornerChain; - // This element must be remembered to restore it - IInputElement backUpFocusedElement; + // This element must be remembered to restore focus + private FocusWrapper backUpFocusedControl; + // Window where we attached private Window window; @@ -35,7 +36,6 @@ internal class KeyTipService // Attached HWND source private HwndSource attachedHwndSource; - private static readonly KeyConverter keyConverter = new KeyConverter(); private string currentUserInput; /// @@ -117,10 +117,7 @@ public void Attach() // Hookup non client area messages this.attachedHwndSource = (HwndSource)PresentationSource.FromVisual(this.window); - if (this.attachedHwndSource != null) - { - this.attachedHwndSource.AddHook(this.WindowProc); - } + this.attachedHwndSource?.AddHook(this.WindowProc); } /// @@ -146,12 +143,8 @@ public void Detach() this.window = null; } - // Hookup non client area messages - if (this.attachedHwndSource != null - && this.attachedHwndSource.IsDisposed == false) - { - this.attachedHwndSource.RemoveHook(this.WindowProc); - } + // Unhook non client area messages + this.attachedHwndSource?.RemoveHook(this.WindowProc); } // Window's messages hook up @@ -177,13 +170,15 @@ private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, re private void OnWindowPreviewKeyDown(object sender, KeyEventArgs e) { - if (e.IsRepeat) + if (e.IsRepeat + || e.Handled) { return; } if (this.ribbon.IsCollapsed - || this.ribbon.IsEnabled == false) + || this.ribbon.IsEnabled == false + || this.window.IsActive == false) { return; } @@ -195,7 +190,6 @@ private void OnWindowPreviewKeyDown(object sender, KeyEventArgs e) && e.SystemKey <= Key.NumPad9) { this.activeAdornerChain?.Terminate(); - this.ClearUserInput(); return; } @@ -207,19 +201,10 @@ private void OnWindowPreviewKeyDown(object sender, KeyEventArgs e) { this.ShowDelayed(); } - else if (this.activeAdornerChain != null - && this.activeAdornerChain.IsAdornerChainAlive) - { - // Focus ribbon - this.backUpFocusedElement = Keyboard.FocusedElement; - this.ribbon.Focusable = true; - //this.ribbon.Focus(); - - this.activeAdornerChain.Terminate(); - } else { - this.ClearUserInput(); + this.activeAdornerChain?.Terminate(); + return; } } else if (e.Key == Key.Escape @@ -228,6 +213,7 @@ private void OnWindowPreviewKeyDown(object sender, KeyEventArgs e) this.activeAdornerChain.ActiveKeyTipAdorner.Back(); this.ClearUserInput(); e.Handled = true; + return; } else { @@ -238,54 +224,70 @@ private void OnWindowPreviewKeyDown(object sender, KeyEventArgs e) return; } - Key actualKey = e.Key == Key.System ? e.SystemKey : e.Key; - bool isLetterKey = ((actualKey >= Key.A && actualKey <= Key.Z) || (actualKey >= Key.D0 && actualKey <= Key.D9) || (actualKey >= Key.NumPad0 && actualKey <= Key.NumPad9)); + var actualKey = e.Key == Key.System ? e.SystemKey : e.Key; + // we need to get the real string input for the key because of keys like ä,ö,ü #258 + var key = KeyEventUtility.GetStringFromKey(actualKey); + var isKeyRealInput = string.IsNullOrEmpty(key) == false; - if (isLetterKey) + // Don't do anything and let WPF handle the rest + if (isKeyRealInput == false) { - // Should we show the keytips and immediately react to key? - if (this.activeAdornerChain == null - || this.activeAdornerChain.IsAdornerChainAlive == false - || this.activeAdornerChain.AreAnyKeyTipsVisible == false) + // This block is a "temporary" fix for keyboard navigation not matching the office behavior. + // If someone finds a way to implement it properly, here is your starting point. + // In office: If you navigate by keyboard (in menus) and keytips are shown they are shown or hidden based on the menu you are in. + // Implementing navigation the way office does would require complex focus/state tracking etc. so i decided to just terminate keytips and not restore focus. { - this.ShowImmediatly(); + this.backUpFocusedControl = null; + this.activeAdornerChain?.Terminate(); } + return; } - if (this.activeAdornerChain == null) + var shownImmediately = false; + + // Should we show the keytips and immediately react to key? + if (this.activeAdornerChain == null + || this.activeAdornerChain.IsAdornerChainAlive == false + || this.activeAdornerChain.AreAnyKeyTipsVisible == false) { - return; + this.ShowImmediatly(); + shownImmediately = true; } - string previousInput = this.currentUserInput; - - if (isLetterKey) + if (this.activeAdornerChain == null) { - this.currentUserInput += keyConverter.ConvertToString(actualKey); + return; } - // If no key tips match the current input, continue with the previously entered and still correct keys. - if (!isLetterKey || this.activeAdornerChain.ActiveKeyTipAdorner.ContainsKeyTipStartingWith(this.currentUserInput) == false) + var previousInput = this.currentUserInput; + this.currentUserInput += key; + + if (this.activeAdornerChain.ActiveKeyTipAdorner.ContainsKeyTipStartingWith(this.currentUserInput) == false) { - MenuItem item = Keyboard.FocusedElement as MenuItem; - if (item != null && !isLetterKey) + // Handles access-keys #258 + if (shownImmediately) { - item.HandleKeyDown(e); + this.activeAdornerChain?.Terminate(); return; } + // If no key tips match the current input, continue with the previously entered and still correct keys. this.currentUserInput = previousInput; System.Media.SystemSounds.Beep.Play(); + e.Handled = true; + return; } else if (this.activeAdornerChain.ActiveKeyTipAdorner.Forward(this.currentUserInput, true)) { this.ClearUserInput(); e.Handled = true; + return; } else { this.activeAdornerChain.ActiveKeyTipAdorner.FilterKeyTips(this.currentUserInput); e.Handled = true; + return; } } } @@ -293,8 +295,10 @@ private void OnWindowPreviewKeyDown(object sender, KeyEventArgs e) private void OnWindowKeyUp(object sender, KeyEventArgs e) { if (this.ribbon.IsCollapsed - || this.ribbon.IsEnabled == false) + || this.ribbon.IsEnabled == false + || this.window.IsActive == false) { + this.activeAdornerChain?.Terminate(); return; } @@ -331,19 +335,15 @@ private void ClearUserInput() private void RestoreFocus() { - if (this.backUpFocusedElement != null) - { - this.backUpFocusedElement.Focus(); - this.backUpFocusedElement = null; // Release the reference, so GC can work - } - - this.ribbon.Focusable = false; + this.backUpFocusedControl?.Focus(); + this.backUpFocusedControl = null; } private void OnAdornerChainTerminated(object sender, EventArgs e) { this.activeAdornerChain.Terminated -= this.OnAdornerChainTerminated; this.activeAdornerChain = null; + this.ClearUserInput(); this.RestoreFocus(); } @@ -359,28 +359,20 @@ private void OnDelayedShow(object sender, EventArgs e) private void ShowImmediatly() { - this.timer.Stop(); - this.backUpFocusedElement = Keyboard.FocusedElement; - - // Focus ribbon - this.ribbon.Focusable = true; - //this.ribbon.Focus(); - this.Show(); } private void ShowDelayed() { - if (this.activeAdornerChain != null) - { - this.activeAdornerChain.Terminate(); - } + this.activeAdornerChain?.Terminate(); this.timer.Start(); } private void Show() { + this.timer.Stop(); + // Check whether the window is // - still present (prevents exceptions when window is closed by system commands) // - still active (prevents keytips showing during Alt-Tab'ing) @@ -391,6 +383,11 @@ private void Show() return; } + this.backUpFocusedControl = FocusWrapper.GetWrapperForCurrentFocus(); + + // Focus ribbon + this.ribbon.Focus(); + this.ClearUserInput(); this.activeAdornerChain = new KeyTipAdorner(this.ribbon, this.ribbon, null); @@ -398,7 +395,8 @@ private void Show() // Special behavior for backstage var specialControl = this.GetBackstage() - ?? (DependencyObject)this.GetApplicationMenu(); + ?? this.GetApplicationMenu() + ?? this.GetStartScreen(); if (specialControl != null) { @@ -410,14 +408,14 @@ private void Show() } } - private Backstage GetBackstage() + private DependencyObject GetBackstage() { if (this.ribbon.Menu == null) { return null; } - var control = this.ribbon.Menu as Backstage ?? UIHelper.FindImmediateVisualChild(this.ribbon.Menu, obj => obj.Visibility == Visibility.Visible && obj.IsOpen); + var control = this.ribbon.Menu as Backstage ?? UIHelper.FindImmediateVisualChild(this.ribbon.Menu, obj => obj.Visibility == Visibility.Visible); if (control == null) { @@ -429,7 +427,7 @@ private Backstage GetBackstage() : null; } - private ApplicationMenu GetApplicationMenu() + private DependencyObject GetApplicationMenu() { if (this.ribbon.Menu == null) { @@ -448,12 +446,27 @@ private ApplicationMenu GetApplicationMenu() : null; } + private DependencyObject GetStartScreen() + { + var control = this.ribbon.StartScreen; + + if (control == null) + { + return null; + } + + return control.IsOpen + ? control + : null; + } + private void DirectlyForwardToSpecialControl(DependencyObject specialControl) { var keys = KeyTip.GetKeys(specialControl); + if (string.IsNullOrEmpty(keys) == false) { - this.activeAdornerChain.Forward(KeyTip.GetKeys(specialControl), false); + this.activeAdornerChain.Forward(keys, false); } else { diff --git a/Fluent.Ribbon/Themes/Generic/Controls/Button.xaml b/Fluent.Ribbon/Themes/Generic/Controls/Button.xaml new file mode 100644 index 000000000..c90bf739d --- /dev/null +++ b/Fluent.Ribbon/Themes/Generic/Controls/Button.xaml @@ -0,0 +1,61 @@ + + + + \ No newline at end of file diff --git a/Fluent.Ribbon/Themes/Generic/Controls/TwoLineLabel.xaml b/Fluent.Ribbon/Themes/Generic/Controls/TwoLineLabel.xaml index faa777864..8bac5f479 100644 --- a/Fluent.Ribbon/Themes/Generic/Controls/TwoLineLabel.xaml +++ b/Fluent.Ribbon/Themes/Generic/Controls/TwoLineLabel.xaml @@ -17,6 +17,7 @@ TextAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Center" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Fluent.Ribbon/Themes/Office2010/Controls/ApplicationMenu.xaml b/Fluent.Ribbon/Themes/Office2010/Controls/ApplicationMenu.xaml index be754595f..9d7669b5e 100644 --- a/Fluent.Ribbon/Themes/Office2010/Controls/ApplicationMenu.xaml +++ b/Fluent.Ribbon/Themes/Office2010/Controls/ApplicationMenu.xaml @@ -1,6 +1,7 @@  @@ -90,8 +91,10 @@ - @@ -129,25 +131,15 @@ MinWidth="100" /> - - - - - - - - diff --git a/Fluent.Ribbon/Themes/Office2010/Controls/ApplicationMenuItem.xaml b/Fluent.Ribbon/Themes/Office2010/Controls/ApplicationMenuItem.xaml index dd102253e..fa3a9a0ea 100644 --- a/Fluent.Ribbon/Themes/Office2010/Controls/ApplicationMenuItem.xaml +++ b/Fluent.Ribbon/Themes/Office2010/Controls/ApplicationMenuItem.xaml @@ -3,9 +3,10 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Fluent="clr-namespace:Fluent" + xmlns:FluentHelpers="clr-namespace:Fluent.Helpers" xmlns:Converters="clr-namespace:Fluent.Converters" mc:Ignorable="d"> - + @@ -892,7 +893,7 @@ VerticalAlignment="Center" Grid.Column="2" d:LayoutOverrides="Width" - TextWrapping="NoWrap" /> + TextWrapping="NoWrap" /> @@ -71,7 +71,7 @@ - + - + @@ -694,20 +616,8 @@ TargetType="{x:Type Fluent:RibbonWindow}"> - - - - - - - - - - + - - - - - - - - - - + @@ -746,14 +645,12 @@ Value="True" /> + Value="0 57 0 0" /> - diff --git a/Fluent.Ribbon/Themes/Office2013/Controls/ApplicationMenu.xaml b/Fluent.Ribbon/Themes/Office2013/Controls/ApplicationMenu.xaml index bfe1de7d2..5d8d6471b 100644 --- a/Fluent.Ribbon/Themes/Office2013/Controls/ApplicationMenu.xaml +++ b/Fluent.Ribbon/Themes/Office2013/Controls/ApplicationMenu.xaml @@ -1,6 +1,7 @@  @@ -30,8 +31,10 @@ - @@ -68,25 +70,15 @@ MinWidth="100" /> - - - - - - - - - - - - - - - - - - - - diff --git a/Fluent.Ribbon/Themes/Office2013/Controls/BackstageTabControl.xaml b/Fluent.Ribbon/Themes/Office2013/Controls/BackstageTabControl.xaml index a19d5717d..c8fa59525 100644 --- a/Fluent.Ribbon/Themes/Office2013/Controls/BackstageTabControl.xaml +++ b/Fluent.Ribbon/Themes/Office2013/Controls/BackstageTabControl.xaml @@ -245,9 +245,9 @@ - - @@ -260,19 +260,19 @@ - - - + - + @@ -287,8 +287,8 @@ + Value="{Binding ParentBackstage.Background, RelativeSource={RelativeSource Self}, FallbackValue=Red}" /> + Value="{Binding ParentBackstage.Background, RelativeSource={RelativeSource Self}, FallbackValue=Red}" /> \ No newline at end of file diff --git a/Fluent.Ribbon/Themes/Office2013/Controls/Button.xaml b/Fluent.Ribbon/Themes/Office2013/Controls/Button.xaml index 69110ed1b..d3d88f03d 100644 --- a/Fluent.Ribbon/Themes/Office2013/Controls/Button.xaml +++ b/Fluent.Ribbon/Themes/Office2013/Controls/Button.xaml @@ -13,30 +13,28 @@ BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" - Height="Auto" - HorizontalAlignment="Left" - VerticalAlignment="Stretch"> - - - - - - + HorizontalAlignment="{TemplateBinding HorizontalAlignment}" + VerticalAlignment="{TemplateBinding VerticalAlignment}" + Height="Auto"> + + + + - - + \ No newline at end of file diff --git a/Fluent.Ribbon/Themes/Office2013/Controls/InRibbonGallery.xaml b/Fluent.Ribbon/Themes/Office2013/Controls/InRibbonGallery.xaml index 7b1faf9b6..6de6f1405 100644 --- a/Fluent.Ribbon/Themes/Office2013/Controls/InRibbonGallery.xaml +++ b/Fluent.Ribbon/Themes/Office2013/Controls/InRibbonGallery.xaml @@ -1,6 +1,7 @@  @@ -405,7 +406,8 @@ CanAddToQuickAccessToolBar="False" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource = {RelativeSource TemplatedParent}}" /> + Visibility="{TemplateBinding CanMinimize, Converter={StaticResource boolToVisibilityConverter}}" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/Fluent.Ribbon/Themes/Windows8/Controls/Button.xaml b/Fluent.Ribbon/Themes/Windows8/Controls/Button.xaml index 0a3af38d9..d6768402a 100644 --- a/Fluent.Ribbon/Themes/Windows8/Controls/Button.xaml +++ b/Fluent.Ribbon/Themes/Windows8/Controls/Button.xaml @@ -13,30 +13,28 @@ BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" - Height="Auto" - HorizontalAlignment="Left" - VerticalAlignment="Stretch"> - - - - - - + HorizontalAlignment="{TemplateBinding HorizontalAlignment}" + VerticalAlignment="{TemplateBinding VerticalAlignment}" + Height="Auto"> + + + + @@ -421,7 +422,8 @@ CanAddToQuickAccessToolBar="False" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource = {RelativeSource TemplatedParent}}" /> - + + + + + + + Visibility="{TemplateBinding CanMinimize, Converter={StaticResource boolToVisibilityConverter}}" /> diff --git a/Fluent.Ribbon/Themes/Windows8/Controls/RibbonTabItem.xaml b/Fluent.Ribbon/Themes/Windows8/Controls/RibbonTabItem.xaml index cd93cd492..124213a1d 100644 --- a/Fluent.Ribbon/Themes/Windows8/Controls/RibbonTabItem.xaml +++ b/Fluent.Ribbon/Themes/Windows8/Controls/RibbonTabItem.xaml @@ -224,11 +224,11 @@ Padding="15,0,0,0" Grid.ColumnSpan="1"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -