项目结构调整

This commit is contained in:
艾竹
2023-04-16 20:11:40 +08:00
parent cbfbf96033
commit 81f91f3f35
2124 changed files with 218 additions and 5516 deletions

View File

@@ -0,0 +1,887 @@
#nullable enable
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using Fluent.Extensibility;
using Fluent.Internal;
/// <summary>
/// Represents adorner for KeyTips.
/// KeyTipAdorners is chained to produce one from another.
/// Detaching root adorner couses detaching all adorners in the chain
/// </summary>
public class KeyTipAdorner : Adorner
{
#region Events
/// <summary>
/// This event is occured when adorner is
/// detached and is not able to be attached again
/// </summary>
public event EventHandler<KeyTipPressedResult>? Terminated;
#endregion
#region Fields
// KeyTips that have been
// found on this element
private readonly List<KeyTipInformation> keyTipInformations = new List<KeyTipInformation>();
private readonly FrameworkElement oneOfAssociatedElements;
// Parent adorner
private readonly KeyTipAdorner? parentAdorner;
private KeyTipAdorner? childAdorner;
private readonly FrameworkElement keyTipElementContainer;
// Is this adorner attached to the adorned element?
private bool attached;
private bool isAttaching;
// Designate that this adorner is terminated
private bool terminated;
private AdornerLayer? adornerLayer;
#endregion
#region Properties
/// <summary>
/// Determines whether at least one on the adorners in the chain is alive
/// </summary>
public bool IsAdornerChainAlive
{
get { return this.isAttaching || this.attached || this.childAdorner?.IsAdornerChainAlive == true; }
}
/// <summary>
/// Returns whether any key tips are visibile.
/// </summary>
public bool AreAnyKeyTipsVisible
{
get { return this.keyTipInformations.Any(x => x.IsVisible) || this.childAdorner?.AreAnyKeyTipsVisible == true; }
}
/// <summary>
/// Gets the currently active <see cref="KeyTipAdorner"/> by following eventually present child adorners.
/// </summary>
public KeyTipAdorner ActiveKeyTipAdorner
{
get
{
return this.childAdorner?.IsAdornerChainAlive == true
? this.childAdorner.ActiveKeyTipAdorner
: this;
}
}
/// <summary>
/// Gets a copied list of the currently available <see cref="KeyTipInformation"/>.
/// </summary>
public IReadOnlyList<KeyTipInformation> KeyTipInformations
{
get
{
return this.keyTipInformations.ToList();
}
}
#endregion
#region Intialization
/// <summary>
/// Construcotor
/// </summary>
/// <param name="adornedElement">Element to adorn.</param>
/// <param name="parentAdorner">Parent adorner or null.</param>
/// <param name="keyTipElementContainer">The element which is container for elements.</param>
public KeyTipAdorner(FrameworkElement adornedElement, FrameworkElement keyTipElementContainer, KeyTipAdorner parentAdorner)
: base(adornedElement)
{
this.parentAdorner = parentAdorner;
this.keyTipElementContainer = keyTipElementContainer;
// Try to find supported elements
this.FindKeyTips(this.keyTipElementContainer, false);
this.oneOfAssociatedElements = this.keyTipInformations.Count != 0
? this.keyTipInformations[0].AssociatedElement
: adornedElement // Maybe here is bug, coz we need keytipped item here...
;
}
// Find key tips on the given element
private void FindKeyTips(FrameworkElement container, bool hide)
{
var children = GetVisibleChildren(container);
foreach (var child in children)
{
var groupBox = child as RibbonGroupBox;
var keys = KeyTip.GetKeys(child);
if (keys is null == false
|| child is IKeyTipInformationProvider)
{
if (groupBox is null)
{
this.GenerateAndAddRegularKeyTipInformations(keys, child, hide);
// Do not search deeper in the tree
continue;
}
if (keys is null == false)
{
this.GenerateAndAddGroupBoxKeyTipInformation(hide, keys, child, groupBox);
}
}
var innerHide = hide || groupBox?.State == RibbonGroupBoxState.Collapsed;
this.FindKeyTips(child, innerHide);
}
}
private void GenerateAndAddGroupBoxKeyTipInformation(bool hide, string keys, FrameworkElement child, RibbonGroupBox groupBox)
{
var keyTipInformation = new KeyTipInformation(keys, child, hide || groupBox.State != RibbonGroupBoxState.Collapsed);
// Add to list & visual children collections
this.AddKeyTipInformationElement(keyTipInformation);
this.LogDebug("Found KeyTipped RibbonGroupBox \"{0}\" with keys \"{1}\".", keyTipInformation.AssociatedElement, keyTipInformation.Keys);
}
private void GenerateAndAddRegularKeyTipInformations(string? keys, FrameworkElement child, bool hide)
{
IEnumerable<KeyTipInformation>? informations = null;
if (child is IKeyTipInformationProvider keyTipInformationProvider)
{
informations = keyTipInformationProvider.GetKeyTipInformations(hide);
}
else if (keys is null == false)
{
informations = new[] { new KeyTipInformation(keys, child, hide) };
}
if (informations is null == false)
{
foreach (var keyTipInformation in informations)
{
// Add to list & visual children collections
this.AddKeyTipInformationElement(keyTipInformation);
this.LogDebug("Found KeyTipped element \"{0}\" with keys \"{1}\".", keyTipInformation.AssociatedElement, keyTipInformation.Keys);
}
}
}
private void AddKeyTipInformationElement(KeyTipInformation keyTipInformation)
{
this.keyTipInformations.Add(keyTipInformation);
this.AddVisualChild(keyTipInformation.KeyTip);
}
private static IList<FrameworkElement> GetVisibleChildren(FrameworkElement element)
{
var logicalChildren = LogicalTreeHelper.GetChildren(element)
.OfType<FrameworkElement>();
var children = logicalChildren;
// Always using the visual tree is very expensive, so we only search through it when we got specific control types.
// Using the visual tree here, in addition to the logical, partially fixes #244.
if (element is ContentPresenter
|| element is ContentControl)
{
children = children
.Concat(UIHelper.GetVisualChildren(element))
.OfType<FrameworkElement>();
}
else if (element is ItemsControl itemsControl)
{
children = children.Concat(UIHelper.GetAllItemContainers<FrameworkElement>(itemsControl));
}
// Don't show key tips for the selected content too early
if (element is RibbonTabControl ribbonTabControl
&& ribbonTabControl.SelectedContent is FrameworkElement selectedContent)
{
children = children.Except(new[] { selectedContent });
}
return children
.Where(x => x.Visibility == Visibility.Visible)
.Distinct()
.ToList();
}
#endregion
#region Attach & Detach
/// <summary>
/// Attaches this adorner to the adorned element
/// </summary>
public void Attach()
{
if (this.attached)
{
return;
}
this.isAttaching = true;
this.oneOfAssociatedElements.UpdateLayout();
this.LogDebug("Attach begin {0}", this.Visibility);
if (this.oneOfAssociatedElements.IsLoaded == false)
{
// Delay attaching
this.LogDebug("Delaying attach");
this.oneOfAssociatedElements.Loaded += this.OnDelayAttach;
return;
}
this.adornerLayer = GetAdornerLayer(this.oneOfAssociatedElements);
if (this.adornerLayer is null)
{
this.LogDebug("No adorner layer found");
this.isAttaching = false;
return;
}
this.FilterKeyTips(string.Empty);
// Show this adorner
this.adornerLayer.Add(this);
this.isAttaching = false;
this.attached = true;
this.LogDebug("Attach end");
}
private void OnDelayAttach(object sender, EventArgs args)
{
this.LogDebug("Delay attach (control loaded)");
this.oneOfAssociatedElements.Loaded -= this.OnDelayAttach;
this.Attach();
}
/// <summary>
/// Detaches this adorner from the adorned element
/// </summary>
public void Detach()
{
this.childAdorner?.Detach();
if (!this.attached)
{
return;
}
this.LogDebug("Detach Begin");
// Maybe adorner awaiting attaching, cancel it
this.oneOfAssociatedElements.Loaded -= this.OnDelayAttach;
// Show this adorner
this.adornerLayer?.Remove(this);
this.attached = false;
this.LogDebug("Detach End");
}
#endregion
#region Termination
/// <summary>
/// Terminate whole key tip's adorner chain
/// </summary>
public void Terminate(KeyTipPressedResult keyTipPressedResult)
{
if (this.terminated)
{
return;
}
this.terminated = true;
this.Detach();
this.parentAdorner?.Terminate(keyTipPressedResult);
this.childAdorner?.Terminate(keyTipPressedResult);
this.Terminated?.Invoke(this, keyTipPressedResult);
this.LogDebug("Termination");
}
#endregion
#region Static Methods
private static AdornerLayer? GetAdornerLayer(UIElement element)
{
var current = element;
while (true)
{
if (current is null)
{
return null;
}
var parent = (UIElement)VisualTreeHelper.GetParent(current)
?? (UIElement)LogicalTreeHelper.GetParent(current);
current = parent;
if (current is AdornerDecorator)
{
return AdornerLayer.GetAdornerLayer((UIElement)VisualTreeHelper.GetChild(current, 0));
}
}
}
private static UIElement GetTopLevelElement(UIElement element)
{
while (true)
{
var current = VisualTreeHelper.GetParent(element) as UIElement;
if (current is null)
{
return element;
}
element = current;
}
}
#endregion
#region Methods
/// <summary>
/// Back to the previous adorner.
/// </summary>
public void Back()
{
this.LogTrace("Invoking back.");
var control = this.keyTipElementContainer as IKeyTipedControl;
control?.OnKeyTipBack();
if (this.parentAdorner is null == false)
{
this.LogDebug("Back");
this.Detach();
this.parentAdorner.Attach();
}
else
{
this.Terminate(KeyTipPressedResult.Empty);
}
}
/// <summary>
/// Forwards to the elements with the given keys
/// </summary>
/// <param name="keys">Keys</param>
/// <param name="click">If true the element will be clicked</param>
/// <returns>If the element will be found the function will return true</returns>
public bool Forward(string keys, bool click)
{
this.LogTrace("Trying to forward keys \"{0}\"...", keys);
var keyTipInformation = this.TryGetKeyTipInformation(keys);
if (keyTipInformation is null)
{
this.LogTrace("Found no element for keys \"{0}\".", keys);
return false;
}
this.Forward(keys, keyTipInformation.AssociatedElement, click);
return true;
}
// Forward to the next element
private void Forward(string keys, FrameworkElement element, bool click)
{
this.LogTrace("Forwarding keys \"{0}\" to element \"{1}\".", keys, GetControlLogText(element));
this.Detach();
var keyTipPressedResult = KeyTipPressedResult.Empty;
if (click)
{
this.LogTrace("Invoking click.");
var control = element as IKeyTipedControl;
keyTipPressedResult = control?.OnKeyTipPressed() ?? KeyTipPressedResult.Empty;
}
var children = GetVisibleChildren(element);
if (children.Count == 0)
{
this.Terminate(keyTipPressedResult);
return;
}
// Panels aren't good elements to adorn. For example, trying to display KeyTips on MenuItems in SplitButton fails if using a panel.
var validChild = children.FirstOrDefault(x => x is Panel == false) ?? children[0];
this.childAdorner = ReferenceEquals(GetTopLevelElement(validChild), GetTopLevelElement(element)) == false
? new KeyTipAdorner(validChild, element, this)
: new KeyTipAdorner(element, element, this);
// Stop if no further KeyTips can be displayed.
if (this.childAdorner.keyTipInformations.Any() == false)
{
this.Terminate(keyTipPressedResult);
return;
}
this.childAdorner.Attach();
}
/// <summary>
/// Gets <see cref="KeyTipInformation"/> by keys.
/// </summary>
/// <param name="keys">The keys to look for.</param>
/// <returns>The <see cref="KeyTipInformation"/> associated with <paramref name="keys"/>.</returns>
private KeyTipInformation TryGetKeyTipInformation(string keys)
{
return this.keyTipInformations.FirstOrDefault(x => x.IsEnabled && x.Visibility == Visibility.Visible && keys.Equals(x.Keys, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Determines if an of the keytips contained in this adorner start with <paramref name="keys"/>
/// </summary>
/// <returns><c>true</c> if any keytip start with <paramref name="keys"/>. Otherwise <c>false</c>.</returns>
public bool ContainsKeyTipStartingWith(string keys)
{
foreach (var keyTipInformation in this.keyTipInformations.Where(x => x.IsEnabled))
{
var content = keyTipInformation.Keys;
if (content.StartsWith(keys, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
// Hide / unhide keytips relative matching to entered keys
internal void FilterKeyTips(string keys)
{
this.LogDebug("FilterKeyTips with \"{0}\"", keys);
// Reset visibility if filter is empty
if (string.IsNullOrEmpty(keys))
{
foreach (var keyTipInformation in this.keyTipInformations)
{
keyTipInformation.Visibility = keyTipInformation.DefaultVisibility;
}
}
// Backup current visibility of key tips
foreach (var keyTipInformation in this.keyTipInformations)
{
keyTipInformation.BackupVisibility = keyTipInformation.Visibility;
}
// Hide / unhide keytips relative matching to entered keys
foreach (var keyTipInformation in this.keyTipInformations)
{
var content = keyTipInformation.Keys;
if (string.IsNullOrEmpty(keys))
{
keyTipInformation.Visibility = keyTipInformation.BackupVisibility;
}
else
{
keyTipInformation.Visibility = content.StartsWith(keys, StringComparison.OrdinalIgnoreCase)
? keyTipInformation.BackupVisibility
: Visibility.Collapsed;
}
}
this.LogDebug("Filtered key tips: {0}", this.keyTipInformations.Count(x => x.Visibility == Visibility.Visible));
}
#endregion
#region Layout & Visual Children
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize)
{
this.LogDebug("ArrangeOverride");
foreach (var keyTipInformation in this.keyTipInformations)
{
keyTipInformation.KeyTip.Arrange(new Rect(keyTipInformation.Position, keyTipInformation.KeyTip.DesiredSize));
}
return finalSize;
}
/// <inheritdoc />
protected override Size MeasureOverride(Size constraint)
{
this.LogDebug("MeasureOverride");
foreach (var keyTipInformation in this.keyTipInformations)
{
keyTipInformation.KeyTip.Measure(SizeConstants.Infinite);
}
this.UpdateKeyTipPositions();
var result = new Size(0, 0);
foreach (var keyTipInformation in this.keyTipInformations)
{
var cornerX = keyTipInformation.KeyTip.DesiredSize.Width + keyTipInformation.Position.X;
var cornerY = keyTipInformation.KeyTip.DesiredSize.Height + keyTipInformation.Position.Y;
if (cornerX > result.Width)
{
result.Width = cornerX;
}
if (cornerY > result.Height)
{
result.Height = cornerY;
}
}
return result;
}
private void UpdateKeyTipPositions()
{
this.LogDebug("UpdateKeyTipPositions");
if (this.keyTipInformations.Count == 0)
{
return;
}
double[]? rows = null;
var groupBox = this.oneOfAssociatedElements as RibbonGroupBox ?? UIHelper.GetParent<RibbonGroupBox>(this.oneOfAssociatedElements);
var panel = groupBox?.GetPanel();
if (panel is null == false
&& groupBox is null == false)
{
var height = groupBox.GetLayoutRoot().DesiredSize.Height;
rows = new[]
{
groupBox.GetLayoutRoot().TranslatePoint(new Point(0, 0), this.AdornedElement).Y,
groupBox.GetLayoutRoot().TranslatePoint(new Point(0, panel.DesiredSize.Height / 2.0), this.AdornedElement).Y,
groupBox.GetLayoutRoot().TranslatePoint(new Point(0, panel.DesiredSize.Height), this.AdornedElement).Y,
groupBox.GetLayoutRoot().TranslatePoint(new Point(0, height + 1), this.AdornedElement).Y
};
}
foreach (var keyTipInformation in this.keyTipInformations)
{
// Skip invisible keytips
if (keyTipInformation.Visibility != Visibility.Visible)
{
continue;
}
// Update KeyTip Visibility
var visualTargetIsVisible = keyTipInformation.VisualTarget.IsVisible;
var visualTargetInVisualTree = VisualTreeHelper.GetParent(keyTipInformation.VisualTarget) is null == false;
keyTipInformation.Visibility = visualTargetIsVisible && visualTargetInVisualTree ? Visibility.Visible : Visibility.Collapsed;
keyTipInformation.KeyTip.Margin = KeyTip.GetMargin(keyTipInformation.AssociatedElement);
if (IsWithinQuickAccessToolbar(keyTipInformation.AssociatedElement))
{
var x = (keyTipInformation.VisualTarget.DesiredSize.Width / 2.0) - (keyTipInformation.KeyTip.DesiredSize.Width / 2.0);
var y = keyTipInformation.VisualTarget.DesiredSize.Height - (keyTipInformation.KeyTip.DesiredSize.Height / 2.0);
if (KeyTip.GetAutoPlacement(keyTipInformation.AssociatedElement) == false)
{
switch (KeyTip.GetHorizontalAlignment(keyTipInformation.AssociatedElement))
{
case HorizontalAlignment.Left:
x = 0;
break;
case HorizontalAlignment.Right:
x = keyTipInformation.VisualTarget.DesiredSize.Width - keyTipInformation.KeyTip.DesiredSize.Width;
break;
}
}
keyTipInformation.Position = keyTipInformation.VisualTarget.TranslatePoint(new Point(x, y), this.AdornedElement);
}
else if (keyTipInformation.AssociatedElement.Name == "PART_DialogLauncherButton")
{
// Dialog Launcher Button Exclusive Placement
var keyTipSize = keyTipInformation.KeyTip.DesiredSize;
var elementSize = keyTipInformation.VisualTarget.RenderSize;
if (rows is null)
{
continue;
}
keyTipInformation.Position = keyTipInformation.VisualTarget.TranslatePoint(new Point(
(elementSize.Width / 2.0) - (keyTipSize.Width / 2.0),
0), this.AdornedElement);
keyTipInformation.Position = new Point(keyTipInformation.Position.X, rows[3]);
}
else if (KeyTip.GetAutoPlacement(keyTipInformation.AssociatedElement) == false)
{
var keyTipSize = keyTipInformation.KeyTip.DesiredSize;
var elementSize = keyTipInformation.VisualTarget.RenderSize;
double x = 0, y = 0;
switch (KeyTip.GetHorizontalAlignment(keyTipInformation.AssociatedElement))
{
case HorizontalAlignment.Left:
break;
case HorizontalAlignment.Right:
x = elementSize.Width - keyTipSize.Width;
break;
case HorizontalAlignment.Center:
case HorizontalAlignment.Stretch:
x = (elementSize.Width / 2.0) - (keyTipSize.Width / 2.0);
break;
}
switch (KeyTip.GetVerticalAlignment(keyTipInformation.AssociatedElement))
{
case VerticalAlignment.Top:
break;
case VerticalAlignment.Bottom:
y = elementSize.Height - keyTipSize.Height;
break;
case VerticalAlignment.Center:
case VerticalAlignment.Stretch:
y = (elementSize.Height / 2.0) - (keyTipSize.Height / 2.0);
break;
}
keyTipInformation.Position = keyTipInformation.VisualTarget.TranslatePoint(new Point(x, y), this.AdornedElement);
}
else if (keyTipInformation.AssociatedElement is InRibbonGallery gallery
&& gallery.IsCollapsed == false)
{
// InRibbonGallery Exclusive Placement
var keyTipSize = keyTipInformation.KeyTip.DesiredSize;
var elementSize = keyTipInformation.VisualTarget.RenderSize;
if (rows is null)
{
continue;
}
keyTipInformation.Position = keyTipInformation.VisualTarget.TranslatePoint(new Point(
elementSize.Width - (keyTipSize.Width / 2.0),
0), this.AdornedElement);
keyTipInformation.Position = new Point(keyTipInformation.Position.X, rows[2] - (keyTipSize.Height / 2));
}
else if (keyTipInformation.AssociatedElement is RibbonTabItem || keyTipInformation.AssociatedElement is Backstage)
{
// Ribbon Tab Item Exclusive Placement
var keyTipSize = keyTipInformation.KeyTip.DesiredSize;
var elementSize = keyTipInformation.VisualTarget.RenderSize;
keyTipInformation.Position = keyTipInformation.VisualTarget.TranslatePoint(new Point(
(elementSize.Width / 2.0) - (keyTipSize.Width / 2.0),
elementSize.Height - (keyTipSize.Height / 2.0)), this.AdornedElement);
}
else if (keyTipInformation.AssociatedElement is MenuItem)
{
// MenuItem Exclusive Placement
var elementSize = keyTipInformation.VisualTarget.DesiredSize;
keyTipInformation.Position = keyTipInformation.VisualTarget.TranslatePoint(
new Point(
(elementSize.Height / 3.0) + 2,
(elementSize.Height / 4.0) + 2), this.AdornedElement);
}
else if (keyTipInformation.AssociatedElement.Parent is BackstageTabControl)
{
// Backstage Items Exclusive Placement
var keyTipSize = keyTipInformation.KeyTip.DesiredSize;
var elementSize = keyTipInformation.VisualTarget.DesiredSize;
var parent = (UIElement)keyTipInformation.VisualTarget.Parent;
var positionInParent = keyTipInformation.VisualTarget.TranslatePoint(default, parent);
keyTipInformation.Position = parent.TranslatePoint(
new Point(
5,
positionInParent.Y + ((elementSize.Height / 2.0) - keyTipSize.Height)), this.AdornedElement);
}
else
{
if (RibbonProperties.GetSize(keyTipInformation.AssociatedElement) != RibbonControlSize.Large
|| IsTextBoxShapedControl(keyTipInformation.AssociatedElement))
{
var x = keyTipInformation.KeyTip.DesiredSize.Width / 2.0;
var y = keyTipInformation.KeyTip.DesiredSize.Height / 2.0;
var point = new Point(x, y);
var translatedPoint = keyTipInformation.VisualTarget.TranslatePoint(point, this.AdornedElement);
// Snapping to rows if it present
SnapToRowsIfPresent(rows, keyTipInformation, translatedPoint);
keyTipInformation.Position = translatedPoint;
}
else
{
var x = (keyTipInformation.VisualTarget.DesiredSize.Width / 2.0) - (keyTipInformation.KeyTip.DesiredSize.Width / 2.0);
var y = keyTipInformation.VisualTarget.DesiredSize.Height - 8;
var point = new Point(x, y);
var translatedPoint = keyTipInformation.VisualTarget.TranslatePoint(point, this.AdornedElement);
// Snapping to rows if it present
SnapToRowsIfPresent(rows, keyTipInformation, translatedPoint);
keyTipInformation.Position = translatedPoint;
}
}
}
}
private static bool IsTextBoxShapedControl(FrameworkElement element)
{
return element is Spinner || element is System.Windows.Controls.ComboBox || element is System.Windows.Controls.TextBox || element is System.Windows.Controls.CheckBox;
}
// Determines whether the element is children to RibbonToolBar
private static bool IsWithinRibbonToolbarInTwoLine(DependencyObject element)
{
var ribbonToolBar = UIHelper.GetParent<RibbonToolBar>(element);
var definition = ribbonToolBar?.GetCurrentLayoutDefinition();
if (definition is null)
{
return false;
}
if (definition.RowCount == 2
|| definition.Rows.Count == 2)
{
return true;
}
return false;
}
// Determines whether the element is children to quick access toolbar
private static bool IsWithinQuickAccessToolbar(DependencyObject element)
{
return UIHelper.GetParent<QuickAccessToolBar>(element) is null == false;
}
private static void SnapToRowsIfPresent(double[]? rows, KeyTipInformation keyTipInformation, Point translatedPoint)
{
if (rows is null)
{
return;
}
var withinRibbonToolbar = IsWithinRibbonToolbarInTwoLine(keyTipInformation.VisualTarget);
var index = 0;
var mindistance = Math.Abs(rows[0] - translatedPoint.Y);
for (var j = 1; j < rows.Length; j++)
{
if (withinRibbonToolbar && j == 1)
{
continue;
}
var distance = Math.Abs(rows[j] - translatedPoint.Y);
if (distance < mindistance)
{
mindistance = distance;
index = j;
}
}
translatedPoint.Y = rows[index] - (keyTipInformation.KeyTip.DesiredSize.Height / 2.0);
}
/// <inheritdoc />
protected override int VisualChildrenCount => this.keyTipInformations.Count;
/// <inheritdoc />
protected override Visual GetVisualChild(int index)
{
return this.keyTipInformations[index].KeyTip;
}
#endregion
#region Logging
[Conditional("DEBUG")]
private void LogDebug(string format, params object[] args)
{
var message = this.GetMessageLog(format, args);
Debug.WriteLine(message, "KeyTipAdorner");
}
[Conditional("TRACE")]
private void LogTrace(string format, params object[] args)
{
var message = this.GetMessageLog(format, args);
Trace.WriteLine(message, "KeyTipAdorner");
}
private string GetMessageLog(string format, object[] args)
{
var name = GetControlLogText(this.AdornedElement);
var message = $"[{name}] {string.Format(format, args)}";
return message;
}
private static string GetControlLogText(UIElement control)
{
var name = control.GetType().Name;
if (control is IHeaderedControl headeredControl)
{
name += $" ({headeredControl.Header})";
}
return name;
}
#endregion
}
}

View File

@@ -0,0 +1,256 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Windows;
using System.Windows.Media;
using Fluent.Extensibility;
using Fluent.Internal.KnownBoxes;
using JetBrains.Annotations;
/// <summary>
/// Attached Properties for the Fluent Ribbon library
/// </summary>
[PublicAPI]
public static class RibbonProperties
{
#region Size Property
/// <summary>
/// Using a DependencyProperty as the backing store for Size.
/// This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty SizeProperty =
DependencyProperty.RegisterAttached("Size", typeof(RibbonControlSize), typeof(RibbonProperties),
new FrameworkPropertyMetadata(RibbonControlSize.Large,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsParentArrange |
FrameworkPropertyMetadataOptions.AffectsParentMeasure,
OnSizeChanged));
/// <summary>
/// Sets <see cref="SizeProperty"/> for <paramref name="element"/>.
/// </summary>
public static void SetSize(DependencyObject element, RibbonControlSize value)
{
element.SetValue(SizeProperty, value);
}
/// <summary>
/// Gets <see cref="SizeProperty"/> for <paramref name="element"/>.
/// </summary>
//[AttachedPropertyBrowsableForType(typeof(IRibbonControl))]
public static RibbonControlSize GetSize(DependencyObject element)
{
return (RibbonControlSize)element.GetValue(SizeProperty);
}
private static void OnSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sink = d as IRibbonSizeChangedSink;
sink?.OnSizePropertyChanged((RibbonControlSize)e.OldValue, (RibbonControlSize)e.NewValue);
}
#endregion
#region SizeDefinition Property
/// <summary>
/// Using a DependencyProperty as the backing store for SizeDefinition.
/// This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty SizeDefinitionProperty =
DependencyProperty.RegisterAttached("SizeDefinition", typeof(RibbonControlSizeDefinition), typeof(RibbonProperties),
new FrameworkPropertyMetadata(new RibbonControlSizeDefinition(RibbonControlSize.Large, RibbonControlSize.Middle, RibbonControlSize.Small),
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsParentArrange |
FrameworkPropertyMetadataOptions.AffectsParentMeasure,
OnSizeDefinitionChanged));
/// <summary>
/// Sets <see cref="SizeDefinitionProperty"/> for <paramref name="element"/>.
/// </summary>
public static void SetSizeDefinition(DependencyObject element, RibbonControlSizeDefinition value)
{
element.SetValue(SizeDefinitionProperty, value);
}
/// <summary>
/// Gets <see cref="SizeDefinitionProperty"/> for <paramref name="element"/>.
/// </summary>
//[AttachedPropertyBrowsableForType(typeof(IRibbonControl))]
public static RibbonControlSizeDefinition GetSizeDefinition(DependencyObject element)
{
return (RibbonControlSizeDefinition)element.GetValue(SizeDefinitionProperty);
}
// Handles RibbonSizeDefinitionProperty changes
internal static void OnSizeDefinitionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Find parent group box
var groupBox = FindParentRibbonGroupBox(d);
var element = (UIElement)d;
SetAppropriateSize(element, groupBox?.State ?? RibbonGroupBoxState.Large);
}
// Finds parent group box
internal static RibbonGroupBox FindParentRibbonGroupBox(DependencyObject element)
{
var currentElement = element;
RibbonGroupBox groupBox;
while ((groupBox = currentElement as RibbonGroupBox) is null)
{
currentElement = VisualTreeHelper.GetParent(currentElement)
?? LogicalTreeHelper.GetParent(currentElement);
if (currentElement is null)
{
break;
}
}
return groupBox;
}
/// <summary>
/// Sets appropriate size of the control according to the
/// given group box state and control's size definition
/// </summary>
/// <param name="element">UI Element</param>
/// <param name="state">Group box state</param>
public static void SetAppropriateSize(DependencyObject element, RibbonGroupBoxState state)
{
SetSize(element, GetSizeDefinition(element).GetSize(state));
}
#endregion
#region MouseOverBackgroundProperty
/// <summary>
/// <see cref="DependencyProperty"/> for specifying MouseOverBackground.
/// </summary>
public static readonly DependencyProperty MouseOverBackgroundProperty = DependencyProperty.RegisterAttached("MouseOverBackground", typeof(Brush), typeof(RibbonProperties), new PropertyMetadata(default(Brush)));
/// <summary>
/// Sets <see cref="MouseOverBackgroundProperty"/> for <paramref name="element"/>.
/// </summary>
public static void SetMouseOverBackground(DependencyObject element, Brush value)
{
element.SetValue(MouseOverBackgroundProperty, value);
}
/// <summary>
/// Gets <see cref="MouseOverBackgroundProperty"/> for <paramref name="element"/>.
/// </summary>
//[AttachedPropertyBrowsableForType(typeof(IRibbonControl))]
public static Brush GetMouseOverBackground(DependencyObject element)
{
return (Brush)element.GetValue(MouseOverBackgroundProperty);
}
#endregion
#region MouseOverForegroundProperty
/// <summary>
/// <see cref="DependencyProperty"/> for specifying MouseOverForeground.
/// </summary>
public static readonly DependencyProperty MouseOverForegroundProperty = DependencyProperty.RegisterAttached("MouseOverForeground", typeof(Brush), typeof(RibbonProperties), new PropertyMetadata(default(Brush)));
/// <summary>
/// Sets <see cref="MouseOverForegroundProperty"/> for <paramref name="element"/>.
/// </summary>
public static void SetMouseOverForeground(DependencyObject element, Brush value)
{
element.SetValue(MouseOverForegroundProperty, value);
}
/// <summary>
/// Gets <see cref="MouseOverForegroundProperty"/> for <paramref name="element"/>.
/// </summary>
//[AttachedPropertyBrowsableForType(typeof(IRibbonControl))]
public static Brush GetMouseOverForeground(DependencyObject element)
{
return (Brush)element.GetValue(MouseOverForegroundProperty);
}
#endregion
#region IsSelectedBackgroundProperty
/// <summary>
/// <see cref="DependencyProperty"/> for specifying IsSelectedBackground.
/// </summary>
public static readonly DependencyProperty IsSelectedBackgroundProperty = DependencyProperty.RegisterAttached("IsSelectedBackground", typeof(Brush), typeof(RibbonProperties), new PropertyMetadata(default(Brush)));
/// <summary>
/// Sets <see cref="IsSelectedBackgroundProperty"/> for <paramref name="element"/>.
/// </summary>
public static void SetIsSelectedBackground(DependencyObject element, Brush value)
{
element.SetValue(IsSelectedBackgroundProperty, value);
}
/// <summary>
/// Gets <see cref="IsSelectedBackgroundProperty"/> for <paramref name="element"/>.
/// </summary>
//[AttachedPropertyBrowsableForType(typeof(IRibbonControl))]
public static Brush GetIsSelectedBackground(DependencyObject element)
{
return (Brush)element.GetValue(IsSelectedBackgroundProperty);
}
#endregion
#region LastVisibleWidthProperty
/// <summary>
/// Stores the last visible width of an element.
/// </summary>
public static readonly DependencyProperty LastVisibleWidthProperty = DependencyProperty.RegisterAttached(
"LastVisibleWidth", typeof(double), typeof(RibbonProperties), new PropertyMetadata(DoubleBoxes.Zero));
/// <summary>Helper for setting <see cref="LastVisibleWidthProperty"/> on <paramref name="element"/>.</summary>
public static void SetLastVisibleWidth(DependencyObject element, double value)
{
element.SetValue(LastVisibleWidthProperty, value);
}
/// <summary>Helper for getting <see cref="LastVisibleWidthProperty"/> on <paramref name="element"/>.</summary>
public static double GetLastVisibleWidth(DependencyObject element)
{
return (double)element.GetValue(LastVisibleWidthProperty);
}
#endregion LastVisibleWidthProperty
#region IsElementInQuickAccessToolBarProperty
/// <summary>
/// Defines if the element is part of the <see cref="QuickAccessToolBar"/>.
/// </summary>
public static readonly DependencyProperty IsElementInQuickAccessToolBarProperty = DependencyProperty.RegisterAttached(
"IsElementInQuickAccessToolBar", typeof(bool), typeof(RibbonProperties), new PropertyMetadata(BooleanBoxes.FalseBox));
/// <summary>Helper for setting <see cref="IsElementInQuickAccessToolBarProperty"/> on <paramref name="element"/>.</summary>
public static void SetIsElementInQuickAccessToolBar(DependencyObject element, bool value)
{
element.SetValue(IsElementInQuickAccessToolBarProperty, value);
}
/// <summary>Helper for getting <see cref="IsElementInQuickAccessToolBarProperty"/> on <paramref name="element"/>.</summary>
public static bool GetIsElementInQuickAccessToolBar(DependencyObject element)
{
return (bool)element.GetValue(IsElementInQuickAccessToolBarProperty);
}
#endregion IsElementInQuickAccessToolBarProperty
}
}

View File

@@ -0,0 +1,183 @@
namespace Fluent.Automation.Peers
{
using System.Collections.Generic;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using System.Windows.Controls;
using Fluent.Internal;
using JetBrains.Annotations;
/// <summary>
/// Automation peer for <see cref="Ribbon"/>.
/// </summary>
public class RibbonAutomationPeer : FrameworkElementAutomationPeer, IExpandCollapseProvider
{
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonAutomationPeer([NotNull] Ribbon owner)
: base(owner)
{
this.OwningRibbon = owner;
}
private Ribbon OwningRibbon { get; }
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
/// <inheritdoc />
protected override string GetNameCore()
{
var name = base.GetNameCore();
if (string.IsNullOrEmpty(name))
{
name = this.GetLocalizedControlTypeCore();
}
return name;
}
/// <inheritdoc />
protected override string GetLocalizedControlTypeCore()
{
return "Ribbon";
}
/// <inheritdoc />
public override object GetPattern(PatternInterface patternInterface)
{
switch (patternInterface)
{
case PatternInterface.ExpandCollapse:
return this;
case PatternInterface.Scroll:
{
ItemsControl ribbonTabControl = this.OwningRibbon.TabControl;
if (ribbonTabControl != null)
{
var automationPeer = CreatePeerForElement(ribbonTabControl);
if (automationPeer != null)
{
return automationPeer.GetPattern(patternInterface);
}
}
break;
}
}
return base.GetPattern(patternInterface);
}
/// <inheritdoc />
protected override List<AutomationPeer> GetChildrenCore()
{
// If Ribbon is Collapsed, dont show anything in the UIA tree
if (this.OwningRibbon.IsCollapsed)
{
return null;
}
var children = new List<AutomationPeer>();
if (this.OwningRibbon.QuickAccessToolBar != null)
{
var automationPeer = CreatePeerForElement(this.OwningRibbon.QuickAccessToolBar);
if (automationPeer != null)
{
children.Add(automationPeer);
}
}
if (this.OwningRibbon.Menu != null)
{
var automationPeer = this.CreatePeerForMenu();
if (automationPeer != null)
{
children.Add(automationPeer);
}
}
// Directly forward the children from the tab control
if (this.OwningRibbon.TabControl != null)
{
var automationPeer = CreatePeerForElement(this.OwningRibbon.TabControl);
if (automationPeer != null)
{
// Resetting the children cache might call a recursive loop...
//automationPeer.ResetChildrenCache();
var ribbonTabs = automationPeer.GetChildren();
children.AddRange(ribbonTabs);
// Resetting the children cache might call a recursive loop...
//ribbonTabs.ForEach(x => x.ResetChildrenCache());
}
}
return children;
}
/// <inheritdoc/>
protected override bool IsOffscreenCore()
{
return this.OwningRibbon.IsCollapsed
|| base.IsOffscreenCore();
}
#region IExpandCollapseProvider Members
/// <inheritdoc />
void IExpandCollapseProvider.Collapse()
{
this.OwningRibbon.IsMinimized = true;
}
/// <inheritdoc />
void IExpandCollapseProvider.Expand()
{
this.OwningRibbon.IsMinimized = false;
}
/// <inheritdoc />
ExpandCollapseState IExpandCollapseProvider.ExpandCollapseState => this.OwningRibbon.IsMinimized ? ExpandCollapseState.Collapsed : ExpandCollapseState.Expanded;
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
internal void RaiseExpandCollapseAutomationEvent(bool oldValue, bool newValue)
{
this.RaisePropertyChangedEvent(ExpandCollapsePatternIdentifiers.ExpandCollapseStateProperty,
oldValue ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed,
newValue ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed);
}
#endregion
/// <summary>
/// Creates the <see cref="AutomationPeer"/> for <see cref="Ribbon.Menu"/>.
/// </summary>
protected virtual AutomationPeer CreatePeerForMenu()
{
var automationPeer = CreatePeerForElement(this.OwningRibbon.Menu);
if (automationPeer is null)
{
var menu = (UIElement)UIHelper.FindImmediateVisualChild<Backstage>(this.OwningRibbon.Menu, x => x.Visibility == Visibility.Visible) ?? UIHelper.FindImmediateVisualChild<ApplicationMenu>(this.OwningRibbon.Menu, x => x.Visibility == Visibility.Visible);
if (menu != null)
{
automationPeer = CreatePeerForElement(menu);
}
}
return automationPeer;
}
}
}

View File

@@ -0,0 +1,88 @@
namespace Fluent.Automation.Peers
{
using System.Collections.Generic;
using System.Windows.Automation;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using JetBrains.Annotations;
/// <summary>
/// Automation peer for <see cref="Backstage"/>.
/// </summary>
public class RibbonBackstageAutomationPeer : RibbonControlAutomationPeer, IExpandCollapseProvider
{
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonBackstageAutomationPeer([NotNull] Backstage owner)
: base(owner)
{
this.OwningBackstage = owner;
}
private Backstage OwningBackstage { get; }
/// <inheritdoc />
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Menu;
}
/// <inheritdoc />
public override object GetPattern(PatternInterface patternInterface)
{
switch (patternInterface)
{
case PatternInterface.ExpandCollapse:
return this;
}
return base.GetPattern(patternInterface);
}
/// <inheritdoc />
protected override List<AutomationPeer> GetChildrenCore()
{
var children = new List<AutomationPeer>();
if (this.OwningBackstage.Content != null)
{
var automationPeer = CreatePeerForElement(this.OwningBackstage.Content);
if (automationPeer != null)
{
children.Add(automationPeer);
}
}
return children;
}
#region IExpandCollapseProvider Members
/// <inheritdoc />
void IExpandCollapseProvider.Collapse()
{
this.OwningBackstage.IsOpen = false;
}
/// <inheritdoc />
void IExpandCollapseProvider.Expand()
{
this.OwningBackstage.IsOpen = true;
}
/// <inheritdoc />
ExpandCollapseState IExpandCollapseProvider.ExpandCollapseState => this.OwningBackstage.IsOpen == false ? ExpandCollapseState.Collapsed : ExpandCollapseState.Expanded;
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
internal void RaiseExpandCollapseAutomationEvent(bool oldValue, bool newValue)
{
this.RaisePropertyChangedEvent(ExpandCollapsePatternIdentifiers.ExpandCollapseStateProperty,
oldValue ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed,
newValue ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed);
}
#endregion
}
}

View File

@@ -0,0 +1,39 @@
namespace Fluent.Automation.Peers
{
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using JetBrains.Annotations;
/// <summary>
/// Automation peer for <see cref="BackstageTabControl" />.
/// </summary>
public class RibbonBackstageTabControlAutomationPeer : SelectorAutomationPeer, ISelectionProvider
{
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonBackstageTabControlAutomationPeer([NotNull] BackstageTabControl owner)
: base(owner)
{
this.OwningBackstageTabControl = owner;
}
private BackstageTabControl OwningBackstageTabControl { get; }
/// <inheritdoc />
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Tab;
}
/// <inheritdoc />
protected override ItemAutomationPeer CreateItemAutomationPeer(object item)
{
return new RibbonControlDataAutomationPeer(item, this);
}
bool ISelectionProvider.IsSelectionRequired => false;
bool ISelectionProvider.CanSelectMultiple => false;
}
}

View File

@@ -0,0 +1,88 @@
namespace Fluent.Automation.Peers
{
using System.Collections.Generic;
using System.Windows.Automation;
using System.Windows.Automation.Peers;
using JetBrains.Annotations;
/// <summary>
/// Automation peer for <see cref="BackstageTabItem"/>.
/// </summary>
public class RibbonBackstageTabItemAutomationPeer : FrameworkElementAutomationPeer
{
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonBackstageTabItemAutomationPeer([NotNull] BackstageTabItem owner)
: base(owner)
{
this.OwningBackstageTabItem = owner;
}
private BackstageTabItem OwningBackstageTabItem { get; }
/// <inheritdoc />
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.TabItem;
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
/// <inheritdoc />
protected override string GetNameCore()
{
var name = AutomationProperties.GetName(this.Owner);
if (string.IsNullOrEmpty(name))
{
name = (this.Owner as IHeaderedControl)?.Header as string;
}
return name;
}
/// <inheritdoc />
protected override List<AutomationPeer> GetChildrenCore()
{
var children = GetHeaderChildren() ?? new List<AutomationPeer>();
if (this.OwningBackstageTabItem.IsSelected == false)
{
return children;
}
if (this.OwningBackstageTabItem.TabControlParent?.SelectedContentHost != null)
{
var contentHostPeer = new FrameworkElementAutomationPeer(this.OwningBackstageTabItem.TabControlParent.SelectedContentHost);
var contentChildren = contentHostPeer.GetChildren();
if (contentChildren != null)
{
children.AddRange(contentChildren);
}
}
return children;
List<AutomationPeer> GetHeaderChildren()
{
if (this.OwningBackstageTabItem.Header is string)
{
return null;
}
if (this.OwningBackstageTabItem.HeaderContentHost != null)
{
return new FrameworkElementAutomationPeer(this.OwningBackstageTabItem.HeaderContentHost).GetChildren();
}
return null;
}
}
}
}

View File

@@ -0,0 +1,63 @@
namespace Fluent.Automation.Peers
{
using System.Windows.Automation.Peers;
using JetBrains.Annotations;
/// <inheritdoc />
public class RibbonButtonAutomationPeer : ButtonAutomationPeer
{
/// <summary>Initializes a new instance of the <see cref="T:ButtonAutomationPeer" /> class.</summary>
/// <param name="owner">The element associated with this automation peer.</param>
public RibbonButtonAutomationPeer([NotNull] Button owner)
: base(owner)
{
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return "RibbonButton";
}
/// <inheritdoc />
protected override string GetNameCore()
{
var name = base.GetNameCore();
if (string.IsNullOrEmpty(name))
{
name = (this.Owner as IHeaderedControl)?.Header as string;
}
return name;
}
/// <inheritdoc />
protected override string GetAccessKeyCore()
{
var text = ((Button)this.Owner).KeyTip;
if (string.IsNullOrEmpty(text))
{
text = base.GetAccessKeyCore();
}
return text;
}
/// <inheritdoc />
protected override string GetHelpTextCore()
{
var text = base.GetHelpTextCore();
if (string.IsNullOrEmpty(text))
{
if (((Button)this.Owner).ToolTip is ScreenTip ribbonToolTip)
{
text = ribbonToolTip.Text;
}
}
return text;
}
}
}

View File

@@ -0,0 +1,34 @@
namespace Fluent.Automation.Peers
{
using JetBrains.Annotations;
/// <inheritdoc />
public class RibbonCheckBoxAutomationPeer : System.Windows.Automation.Peers.ToggleButtonAutomationPeer
{
/// <summary>Initializes a new instance of the <see cref="T:ToggleButtonAutomationPeer" /> class.</summary>
/// <param name="owner">The element associated with this automation peer.</param>
public RibbonCheckBoxAutomationPeer([NotNull] CheckBox owner)
: base(owner)
{
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
/// <inheritdoc />
protected override string GetNameCore()
{
var name = base.GetNameCore();
if (string.IsNullOrEmpty(name))
{
name = (this.Owner as IHeaderedControl)?.Header as string;
}
return name;
}
}
}

View File

@@ -0,0 +1,34 @@
namespace Fluent.Automation.Peers
{
using JetBrains.Annotations;
/// <inheritdoc />
public class RibbonComboBoxAutomationPeer : System.Windows.Automation.Peers.ComboBoxAutomationPeer
{
/// <summary>Initializes a new instance of the <see cref="T:ComboBoxAutomationPeer" /> class.</summary>
/// <param name="owner">The element associated with this automation peer.</param>
public RibbonComboBoxAutomationPeer([NotNull] ComboBox owner)
: base(owner)
{
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
/// <inheritdoc />
protected override string GetNameCore()
{
var name = base.GetNameCore();
if (string.IsNullOrEmpty(name))
{
name = (this.Owner as IHeaderedControl)?.Header as string;
}
return name;
}
}
}

View File

@@ -0,0 +1,25 @@
namespace Fluent.Automation.Peers
{
using System.Windows.Automation.Peers;
using JetBrains.Annotations;
/// <summary>
/// Automation peer for <see cref="RibbonControl" />.
/// </summary>
public class RibbonControlAutomationPeer : FrameworkElementAutomationPeer
{
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonControlAutomationPeer([NotNull] RibbonControl owner)
: base(owner)
{
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
}
}

View File

@@ -0,0 +1,72 @@
namespace Fluent.Automation.Peers
{
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
/// <summary>
/// Automation peer for ribbon control items.
/// </summary>
public class RibbonControlDataAutomationPeer : ItemAutomationPeer
{
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonControlDataAutomationPeer(object item, ItemsControlAutomationPeer itemsControlPeer)
: base(item, itemsControlPeer)
{
}
/// <inheritdoc />
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.ListItem;
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
var wrapperPeer = this.GetWrapperPeer();
return wrapperPeer?.GetClassName() ?? string.Empty;
}
/// <inheritdoc />
public override object GetPattern(PatternInterface patternInterface)
{
// Doesnt implement any patterns of its own, so just forward to the wrapper peer.
var wrapperPeer = this.GetWrapperPeer();
return wrapperPeer?.GetPattern(patternInterface);
}
private UIElement GetWrapper()
{
var itemsControlAutomationPeer = this.ItemsControlAutomationPeer;
var owner = (ItemsControl)itemsControlAutomationPeer?.Owner;
return owner?.ItemContainerGenerator.ContainerFromItem(this.Item) as UIElement;
}
private AutomationPeer GetWrapperPeer()
{
var wrapper = this.GetWrapper();
if (wrapper is null)
{
return null;
}
var wrapperPeer = UIElementAutomationPeer.CreatePeerForElement(wrapper);
if (!(wrapperPeer is null))
{
return wrapperPeer;
}
if (wrapper is FrameworkElement element)
{
return new FrameworkElementAutomationPeer(element);
}
return new UIElementAutomationPeer(wrapper);
}
}
}

View File

@@ -0,0 +1,81 @@
namespace Fluent.Automation.Peers
{
using System.Windows.Automation;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using JetBrains.Annotations;
/// <summary>
/// Automation peer for <see cref="DropDownButton"/>.
/// </summary>
public class RibbonDropDownButtonAutomationPeer : RibbonHeaderedControlAutomationPeer, IExpandCollapseProvider
{
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonDropDownButtonAutomationPeer([NotNull] DropDownButton owner)
: base(owner)
{
this.OwnerDropDownButton = owner;
}
private DropDownButton OwnerDropDownButton { get; }
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
/// <inheritdoc />
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Custom;
}
/// <inheritdoc />
protected override string GetLocalizedControlTypeCore()
{
return this.Owner.GetType().Name;
}
/// <inheritdoc />
public override object GetPattern(PatternInterface patternInterface)
{
switch (patternInterface)
{
case PatternInterface.ExpandCollapse:
return this;
}
return base.GetPattern(patternInterface);
}
#region IExpandCollapseProvider Members
/// <inheritdoc />
void IExpandCollapseProvider.Collapse()
{
this.OwnerDropDownButton.IsDropDownOpen = false;
}
/// <inheritdoc />
void IExpandCollapseProvider.Expand()
{
this.OwnerDropDownButton.IsDropDownOpen = true;
}
/// <inheritdoc />
ExpandCollapseState IExpandCollapseProvider.ExpandCollapseState => this.OwnerDropDownButton.IsDropDownOpen == false ? ExpandCollapseState.Collapsed : ExpandCollapseState.Expanded;
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
internal void RaiseExpandCollapseAutomationEvent(bool oldValue, bool newValue)
{
this.RaisePropertyChangedEvent(ExpandCollapsePatternIdentifiers.ExpandCollapseStateProperty,
oldValue ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed,
newValue ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed);
}
#endregion
}
}

View File

@@ -0,0 +1,161 @@
namespace Fluent.Automation.Peers
{
using System.Collections.Generic;
using System.Windows.Automation;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using JetBrains.Annotations;
/// <summary>
/// Automation peer for <see cref="RibbonGroupBox"/>.
/// </summary>
public class RibbonGroupBoxAutomationPeer : FrameworkElementAutomationPeer, IExpandCollapseProvider, IScrollItemProvider
{
private RibbonGroupHeaderAutomationPeer headerPeer;
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonGroupBoxAutomationPeer([NotNull] RibbonGroupBox owner)
: base(owner)
{
this.OwningGroup = owner;
}
private RibbonGroupBox OwningGroup { get; }
private RibbonGroupHeaderAutomationPeer HeaderPeer
{
get
{
if (this.headerPeer is null
|| !this.headerPeer.Owner.IsDescendantOf(this.OwningGroup))
{
if (this.OwningGroup.State == RibbonGroupBoxState.Collapsed)
{
if (this.OwningGroup.CollapsedHeaderContentControl != null)
{
this.headerPeer = new RibbonGroupHeaderAutomationPeer(this.OwningGroup.CollapsedHeaderContentControl);
}
}
else if (this.OwningGroup.Header != null
&& this.OwningGroup.HeaderContentControl != null)
{
this.headerPeer = new RibbonGroupHeaderAutomationPeer(this.OwningGroup.HeaderContentControl);
}
}
return this.headerPeer;
}
}
/// <inheritdoc />
protected override List<AutomationPeer> GetChildrenCore()
{
var list = base.GetChildrenCore();
if (this.HeaderPeer != null)
{
if (list is null)
{
list = new List<AutomationPeer>(1);
}
list.Add(this.HeaderPeer);
}
return list;
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
/// <inheritdoc />
protected override string GetNameCore()
{
var name = base.GetNameCore();
if (string.IsNullOrEmpty(name))
{
name = (this.Owner as IHeaderedControl)?.Header as string;
}
return name;
}
/// <inheritdoc />
public override object GetPattern(PatternInterface patternInterface)
{
switch (patternInterface)
{
case PatternInterface.ExpandCollapse:
return this.IsCollapseOrExpandValid ? this : null;
case PatternInterface.Scroll:
return null;
default:
return base.GetPattern(patternInterface);
}
}
/// <inheritdoc />
protected override void SetFocusCore()
{
}
#region IExpandCollapseProvider Members
/// <inheritdoc />
void IExpandCollapseProvider.Expand()
{
if (this.IsCollapseOrExpandValid == false)
{
return;
}
this.OwningGroup.IsDropDownOpen = true;
}
/// <inheritdoc />
void IExpandCollapseProvider.Collapse()
{
if (this.IsCollapseOrExpandValid == false)
{
return;
}
this.OwningGroup.IsDropDownOpen = false;
}
/// <inheritdoc />
ExpandCollapseState IExpandCollapseProvider.ExpandCollapseState => this.IsCollapseOrExpandValid
? ExpandCollapseState.Collapsed
: ExpandCollapseState.Expanded;
private bool IsCollapseOrExpandValid => this.OwningGroup.State == RibbonGroupBoxState.Collapsed || this.OwningGroup.State == RibbonGroupBoxState.QuickAccess;
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
internal void RaiseExpandCollapseAutomationEvent(bool oldValue, bool newValue)
{
this.RaisePropertyChangedEvent(ExpandCollapsePatternIdentifiers.ExpandCollapseStateProperty,
oldValue ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed,
newValue ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed);
}
#endregion
#region IScrollItemProvider Members
/// <inheritdoc />
void IScrollItemProvider.ScrollIntoView()
{
this.OwningGroup.BringIntoView();
}
#endregion
}
}

View File

@@ -0,0 +1,122 @@
//namespace Fluent.Automation.Peers
//{
// using System.Windows.Automation;
// using System.Windows.Automation.Peers;
// using System.Windows.Automation.Provider;
// using Fluent.Extensions;
// using JetBrains.Annotations;
// /// <summary>
// /// Automation peer wrapper for <see cref="RibbonGroupBox" />.
// /// </summary>
// public class RibbonGroupBoxDataAutomationPeer : ItemAutomationPeer, IScrollItemProvider, IExpandCollapseProvider
// {
// /// <summary>
// /// Creates a new instance.
// /// </summary>
// public RibbonGroupBoxDataAutomationPeer(object item, [NotNull] RibbonTabItemAutomationPeer itemsControlPeer)
// : base(item, itemsControlPeer)
// {
// }
// ExpandCollapseState IExpandCollapseProvider.ExpandCollapseState
// {
// get
// {
// var ribbonGroup = this.GetWrapper() as RibbonGroupBox;
// if (ribbonGroup != null
// && ribbonGroup.State == RibbonGroupBoxState.Collapsed)
// {
// if (!ribbonGroup.IsDropDownOpen)
// {
// return ExpandCollapseState.Collapsed;
// }
// return ExpandCollapseState.Expanded;
// }
// return ExpandCollapseState.LeafNode;
// }
// }
// /// <inheritdoc />
// void IExpandCollapseProvider.Collapse()
// {
// var ribbonGroup = this.GetWrapper() as RibbonGroupBox;
// if (ribbonGroup != null
// && ribbonGroup.State == RibbonGroupBoxState.Collapsed)
// {
// ribbonGroup.IsDropDownOpen = false;
// }
// }
// /// <inheritdoc />
// void IExpandCollapseProvider.Expand()
// {
// var ribbonGroup = this.GetWrapper() as RibbonGroupBox;
// if (ribbonGroup != null
// && ribbonGroup.State == RibbonGroupBoxState.Collapsed)
// {
// ribbonGroup.IsDropDownOpen = true;
// }
// }
// /// <inheritdoc />
// void IScrollItemProvider.ScrollIntoView()
// {
// (this.GetWrapper() as RibbonGroupBox)?.BringIntoView();
// }
// /// <inheritdoc />
// public override object GetPattern(PatternInterface patternInterface)
// {
// object obj = null;
// switch (patternInterface)
// {
// case PatternInterface.ScrollItem:
// obj = this;
// break;
// case PatternInterface.ExpandCollapse:
// {
// var ribbonGroup = this.GetWrapper() as RibbonGroupBox;
// if (ribbonGroup != null
// && ribbonGroup.State == RibbonGroupBoxState.Collapsed)
// {
// obj = this;
// }
// break;
// }
// }
// if (obj is null)
// {
// var wrapperPeer = this.GetWrapperPeer();
// if (wrapperPeer != null)
// {
// obj = wrapperPeer.GetPattern(patternInterface);
// }
// }
// return obj;
// }
// /// <inheritdoc />
// protected override AutomationControlType GetAutomationControlTypeCore()
// {
// return AutomationControlType.Group;
// }
// /// <inheritdoc />
// protected override string GetClassNameCore()
// {
// var wrapperPeer = this.GetWrapperPeer();
// if (wrapperPeer != null)
// {
// return wrapperPeer.GetClassName();
// }
// return string.Empty;
// }
// }
//}

View File

@@ -0,0 +1,50 @@
namespace Fluent.Automation.Peers
{
using System.Windows;
using System.Windows.Automation.Peers;
using JetBrains.Annotations;
/// <summary>
/// Automation peer for the header of <see cref="RibbonGroupBox"/>.
/// </summary>
public class RibbonGroupHeaderAutomationPeer : FrameworkElementAutomationPeer
{
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonGroupHeaderAutomationPeer([NotNull] FrameworkElement owner)
: base(owner)
{
}
/// <inheritdoc />
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Header;
}
/// <inheritdoc />
protected override bool IsContentElementCore()
{
return false;
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
/// <inheritdoc />
protected override string GetNameCore()
{
var parent = this.GetParent();
if (parent != null)
{
return parent.GetName();
}
return string.Empty;
}
}
}

View File

@@ -0,0 +1,39 @@
namespace Fluent.Automation.Peers
{
using System.Windows;
using System.Windows.Automation.Peers;
using JetBrains.Annotations;
/// <summary>
/// Base automation peer for <see cref="IHeaderedControl"/>.
/// </summary>
public abstract class RibbonHeaderedControlAutomationPeer : FrameworkElementAutomationPeer
{
/// <summary>
/// Creates a new instance.
/// </summary>
protected RibbonHeaderedControlAutomationPeer([NotNull] FrameworkElement owner)
: base(owner)
{
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
/// <inheritdoc />
protected override string GetNameCore()
{
var name = base.GetNameCore();
if (string.IsNullOrEmpty(name))
{
name = (this.Owner as IHeaderedControl)?.Header as string;
}
return name;
}
}
}

View File

@@ -0,0 +1,25 @@
namespace Fluent.Automation.Peers
{
using JetBrains.Annotations;
/// <summary>
/// Automation peer for <see cref="InRibbonGallery" />
/// </summary>
// todo: add full automation for expansion, listing items (?) etc.
public class RibbonInRibbonGalleryAutomationPeer : RibbonHeaderedControlAutomationPeer
{
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonInRibbonGalleryAutomationPeer([NotNull] InRibbonGallery owner)
: base(owner)
{
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
}
}

View File

@@ -0,0 +1,69 @@
namespace Fluent.Automation.Peers
{
using System.Collections.Generic;
using System.Windows.Automation.Peers;
using JetBrains.Annotations;
/// <summary>
/// Automation peer for <see cref="QuickAccessToolBar"/>.
/// </summary>
public class RibbonQuickAccessToolBarAutomationPeer : FrameworkElementAutomationPeer
{
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonQuickAccessToolBarAutomationPeer([NotNull] QuickAccessToolBar owner)
: base(owner)
{
this.OwningQuickAccessToolBar = owner;
}
private QuickAccessToolBar OwningQuickAccessToolBar { get; }
/// <inheritdoc />
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.ToolBar;
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
/// <inheritdoc />
protected override List<AutomationPeer> GetChildrenCore()
{
var children = new List<AutomationPeer>();
foreach (var quickAccessMenuItem in this.OwningQuickAccessToolBar.Items)
{
//if (quickAccessMenuItem.IsChecked == false)
//{
// continue;
//}
var automationPeer = CreatePeerForElement(quickAccessMenuItem);
if (automationPeer != null)
{
children.Add(automationPeer);
}
}
var customizeMenuButton = this.OwningQuickAccessToolBar.MenuDownButton;
if (customizeMenuButton != null)
{
var automationPeer = CreatePeerForElement(customizeMenuButton);
if (automationPeer != null)
{
children.Add(automationPeer);
}
}
return children;
}
}
}

View File

@@ -0,0 +1,34 @@
namespace Fluent.Automation.Peers
{
using JetBrains.Annotations;
/// <inheritdoc />
public class RibbonRadioButtonAutomationPeer : System.Windows.Automation.Peers.RadioButtonAutomationPeer
{
/// <summary>Initializes a new instance of the <see cref="T:RadioButtonAutomationPeer" /> class.</summary>
/// <param name="owner">The element associated with this automation peer.</param>
public RibbonRadioButtonAutomationPeer([NotNull] RadioButton owner)
: base(owner)
{
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
/// <inheritdoc />
protected override string GetNameCore()
{
var name = base.GetNameCore();
if (string.IsNullOrEmpty(name))
{
name = (this.Owner as IHeaderedControl)?.Header as string;
}
return name;
}
}
}

View File

@@ -0,0 +1,38 @@
namespace Fluent.Automation.Peers
{
using System.Windows.Automation.Peers;
using JetBrains.Annotations;
/// <summary>
/// Automation peer for <see cref="ScreenTip" />.
/// </summary>
public class RibbonScreenTipAutomationPeer : ToolTipAutomationPeer
{
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonScreenTipAutomationPeer([NotNull] ScreenTip owner)
: base(owner)
{
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
/// <inheritdoc />
protected override string GetNameCore()
{
var name = base.GetNameCore();
if (string.IsNullOrEmpty(name))
{
name = ((ScreenTip)this.Owner).Title;
}
return name;
}
}
}

View File

@@ -0,0 +1,102 @@
namespace Fluent.Automation.Peers
{
using System.Windows.Automation;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using System.Windows.Input;
using System.Windows.Threading;
using Fluent.Extensions;
using Fluent.Internal;
using JetBrains.Annotations;
/// <summary>
/// Automation peer for <see cref="SplitButton"/>.
/// </summary>
public class RibbonSplitButtonAutomationPeer : RibbonDropDownButtonAutomationPeer, IInvokeProvider
{
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonSplitButtonAutomationPeer([NotNull] SplitButton owner)
: base(owner)
{
this.SplitButtonOnwer = owner;
}
private SplitButton SplitButtonOnwer { get; }
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
/// <inheritdoc />
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.SplitButton;
}
/// <inheritdoc />
public override object GetPattern(PatternInterface patternInterface)
{
switch (patternInterface)
{
case PatternInterface.Invoke:
return this;
default:
return base.GetPattern(patternInterface);
}
}
/// <inheritdoc />
protected override string GetAutomationIdCore()
{
var id = base.GetAutomationIdCore();
if (string.IsNullOrEmpty(id))
{
if (this.SplitButtonOnwer.Command is RoutedCommand routedCommand
&& string.IsNullOrEmpty(routedCommand.Name) == false)
{
id = routedCommand.Name;
}
}
return id ?? string.Empty;
}
/// <inheritdoc />
protected override string GetNameCore()
{
var name = base.GetNameCore();
if (string.IsNullOrEmpty(name))
{
if (this.SplitButtonOnwer.Command is RoutedUICommand routedUiCommand
&& string.IsNullOrEmpty(routedUiCommand.Text) == false)
{
name = routedUiCommand.Text;
}
else if (this.SplitButtonOnwer.Button?.Content is string buttonContent)
{
name = AccessTextHelper.RemoveAccessKeyMarker(buttonContent);
}
}
return name;
}
/// <inheritdoc />
public void Invoke()
{
if (this.IsEnabled() == false)
{
throw new ElementNotEnabledException();
}
this.RunInDispatcherAsync(() => this.SplitButtonOnwer.AutomationButtonClick(), DispatcherPriority.Input);
}
}
}

View File

@@ -0,0 +1,108 @@
namespace Fluent.Automation.Peers
{
using System.Collections.Generic;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using JetBrains.Annotations;
/// <summary>
/// Automation peer for <see cref="RibbonTabControl"/>.
/// </summary>
public class RibbonTabControlAutomationPeer : SelectorAutomationPeer, ISelectionProvider
{
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonTabControlAutomationPeer([NotNull] RibbonTabControl owner)
: base(owner)
{
this.OwningRibbonTabControl = owner;
}
private RibbonTabControl OwningRibbonTabControl { get; }
/// <inheritdoc />
protected override ItemAutomationPeer CreateItemAutomationPeer(object item)
{
return new RibbonTabItemDataAutomationPeer(item, this);
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
/// <inheritdoc />
protected override Point GetClickablePointCore()
{
return new Point(double.NaN, double.NaN);
}
bool ISelectionProvider.IsSelectionRequired => true;
bool ISelectionProvider.CanSelectMultiple => false;
/// <inheritdoc />
public override object GetPattern(PatternInterface patternInterface)
{
switch (patternInterface)
{
case PatternInterface.Scroll:
var ribbonTabsContainerPanel = this.OwningRibbonTabControl.TabsContainer;
if (ribbonTabsContainerPanel != null)
{
var automationPeer = CreatePeerForElement(ribbonTabsContainerPanel);
if (automationPeer != null)
{
return automationPeer.GetPattern(patternInterface);
}
}
var ribbonTabsContainer = this.OwningRibbonTabControl.TabsContainer as RibbonTabsContainer;
if (ribbonTabsContainer != null
&& ribbonTabsContainer.ScrollOwner != null)
{
var automationPeer = CreatePeerForElement(ribbonTabsContainer.ScrollOwner);
if (automationPeer != null)
{
automationPeer.EventsSource = this;
return automationPeer.GetPattern(patternInterface);
}
}
break;
}
return base.GetPattern(patternInterface);
}
/// <inheritdoc />
protected override List<AutomationPeer> GetChildrenCore()
{
var children = base.GetChildrenCore() ?? new List<AutomationPeer>();
var minimizeButton = this.OwningRibbonTabControl.MinimizeButton;
if (minimizeButton != null)
{
var automationPeer = CreatePeerForElement(minimizeButton);
if (automationPeer != null)
{
children.Add(automationPeer);
}
}
var toolbarPanel = this.OwningRibbonTabControl.ToolbarPanel;
if (toolbarPanel != null)
{
var automationPeer = new RibbonToolbarPanelAutomationPeer(toolbarPanel);
children.Add(automationPeer);
}
return children;
}
}
}

View File

@@ -0,0 +1,114 @@
namespace Fluent.Automation.Peers
{
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Windows.Automation;
using System.Windows.Automation.Peers;
using JetBrains.Annotations;
/// <summary>
/// Automation peer wrapper for <see cref="RibbonTabItem"/>.
/// </summary>
public class RibbonTabItemAutomationPeer : FrameworkElementAutomationPeer
{
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonTabItemAutomationPeer([NotNull] RibbonTabItem owner)
: base(owner)
{
this.OwningTab = owner;
}
private RibbonTabItem OwningTab { get; }
/// <inheritdoc />
public override object GetPattern(PatternInterface patternInterface)
{
switch (patternInterface)
{
case PatternInterface.Scroll:
var container = this.OwningTab.GroupsContainer;
if (container != null)
{
var automationPeer = CreatePeerForElement(container);
if (automationPeer != null)
{
return automationPeer.GetPattern(patternInterface);
}
}
break;
}
return base.GetPattern(patternInterface);
}
/// <inheritdoc />
protected override List<AutomationPeer> GetChildrenCore()
{
var children = GetHeaderChildren() ?? new List<AutomationPeer>();
if (this.OwningTab.IsSelected == false)
{
return children;
}
foreach (var @group in this.OwningTab.Groups)
{
var peer = CreatePeerForElement(@group);
if (peer != null)
{
children.Add(peer);
}
}
return children;
List<AutomationPeer> GetHeaderChildren()
{
if (this.OwningTab.Header is string)
{
return null;
}
if (this.OwningTab.HeaderContentHost != null)
{
return new FrameworkElementAutomationPeer(this.OwningTab.HeaderContentHost).GetChildren();
}
return null;
}
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
[MethodImpl(MethodImplOptions.NoInlining)]
internal void RaiseTabExpandCollapseAutomationEvent(bool oldValue, bool newValue)
{
this.EventsSource?.RaisePropertyChangedEvent(ExpandCollapsePatternIdentifiers.ExpandCollapseStateProperty, oldValue ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed, newValue ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed);
}
[MethodImpl(MethodImplOptions.NoInlining)]
internal void RaiseTabSelectionEvents()
{
var eventsSource = this.EventsSource;
if (eventsSource != null)
{
if (this.OwningTab.IsSelected)
{
eventsSource.RaiseAutomationEvent(AutomationEvents.SelectionItemPatternOnElementSelected);
}
else
{
eventsSource.RaiseAutomationEvent(AutomationEvents.SelectionItemPatternOnElementRemovedFromSelection);
}
}
}
}
}

View File

@@ -0,0 +1,134 @@
namespace Fluent.Automation.Peers
{
using System.Windows.Automation;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using Fluent.Extensions;
/// <summary>
/// Automation peer for <see cref="RibbonTabItem"/>.
/// </summary>
public class RibbonTabItemDataAutomationPeer : SelectorItemAutomationPeer, IScrollItemProvider, IExpandCollapseProvider
{
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonTabItemDataAutomationPeer(object item, RibbonTabControlAutomationPeer tabControlAutomationPeer)
: base(item, tabControlAutomationPeer)
{
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return "RibbonTabItem";
}
/// <inheritdoc />
protected override string GetNameCore()
{
var nameCore = base.GetNameCore();
if (string.IsNullOrEmpty(nameCore) == false)
{
var wrapper = this.GetWrapper() as RibbonTabItem;
if (wrapper?.Header is string headerString)
{
return headerString;
}
}
return nameCore;
}
/// <inheritdoc />
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.TabItem;
}
#region IExpandCollapseProvider Members
/// <summary>
/// If Ribbon.IsMinimized then set Ribbon.IsDropDownOpen to false
/// </summary>
void IExpandCollapseProvider.Collapse()
{
var wrapperTab = this.GetWrapper() as RibbonTabItem;
if (wrapperTab != null)
{
var tabControl = wrapperTab.TabControlParent;
if (tabControl != null &&
tabControl.IsMinimized)
{
tabControl.IsDropDownOpen = false;
}
}
}
/// <summary>
/// If Ribbon.IsMinimized then set Ribbon.IsDropDownOpen to true
/// </summary>
void IExpandCollapseProvider.Expand()
{
var wrapperTab = this.GetWrapper() as RibbonTabItem;
// Select the tab and display popup
if (wrapperTab != null)
{
var tabControl = wrapperTab.TabControlParent;
if (tabControl != null &&
tabControl.IsMinimized)
{
wrapperTab.IsSelected = true;
tabControl.IsDropDownOpen = true;
}
}
}
/// <summary>
/// Return Ribbon.IsDropDownOpen
/// </summary>
ExpandCollapseState IExpandCollapseProvider.ExpandCollapseState
{
get
{
var wrapperTab = this.GetWrapper() as RibbonTabItem;
if (wrapperTab != null)
{
var tabControl = wrapperTab.TabControlParent;
if (tabControl != null &&
tabControl.IsMinimized)
{
if (wrapperTab.IsSelected && tabControl.IsDropDownOpen)
{
return ExpandCollapseState.Expanded;
}
else
{
return ExpandCollapseState.Collapsed;
}
}
}
// When not minimized
return ExpandCollapseState.Expanded;
}
}
#endregion
#region IScrollItemProvider Members
void IScrollItemProvider.ScrollIntoView()
{
var wrapperTab = this.GetWrapper() as RibbonTabItem;
if (wrapperTab != null)
{
wrapperTab.BringIntoView();
}
}
#endregion
}
}

View File

@@ -0,0 +1,34 @@
namespace Fluent.Automation.Peers
{
using JetBrains.Annotations;
/// <inheritdoc />
public class RibbonTextBoxAutomationPeer : System.Windows.Automation.Peers.TextBoxAutomationPeer
{
/// <summary>Initializes a new instance of the <see cref="T:TextBoxAutomationPeer" /> class.</summary>
/// <param name="owner">The element associated with this automation peer.</param>
public RibbonTextBoxAutomationPeer([NotNull] TextBox owner)
: base(owner)
{
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
/// <inheritdoc />
protected override string GetNameCore()
{
var name = base.GetNameCore();
if (string.IsNullOrEmpty(name))
{
name = (this.Owner as IHeaderedControl)?.Header as string;
}
return name;
}
}
}

View File

@@ -0,0 +1,50 @@
namespace Fluent.Automation.Peers
{
using System.Windows.Automation.Peers;
using System.Windows.Controls;
/// <summary>
/// Automation peer for <see cref="RibbonTitleBar"/>.
/// </summary>
public class RibbonTitleBarAutomationPeer : FrameworkElementAutomationPeer
{
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonTitleBarAutomationPeer(RibbonTitleBar owner)
: base(owner)
{
}
/// <inheritdoc />
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Header;
}
/// <inheritdoc />
protected override bool IsContentElementCore()
{
return false;
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return this.Owner.GetType().Name;
}
/// <inheritdoc />
protected override string GetNameCore()
{
var contentPresenter = this.Owner as HeaderedContentControl;
if (contentPresenter?.Header != null)
{
return contentPresenter.Header.ToString();
}
return base.GetNameCore();
}
}
}

View File

@@ -0,0 +1,34 @@
namespace Fluent.Automation.Peers
{
using JetBrains.Annotations;
/// <inheritdoc />
public class RibbonToggleButtonAutomationPeer : System.Windows.Automation.Peers.ToggleButtonAutomationPeer
{
/// <summary>Initializes a new instance of the <see cref="T:ToggleButtonAutomationPeer" /> class.</summary>
/// <param name="owner">The element associated with this automation peer.</param>
public RibbonToggleButtonAutomationPeer([NotNull] ToggleButton owner)
: base(owner)
{
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return "RibbonToggleButton";
}
/// <inheritdoc />
protected override string GetNameCore()
{
var name = base.GetNameCore();
if (string.IsNullOrEmpty(name))
{
name = (this.Owner as IHeaderedControl)?.Header as string;
}
return name;
}
}
}

View File

@@ -0,0 +1,49 @@
namespace Fluent.Automation.Peers
{
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using JetBrains.Annotations;
/// <summary>
/// Automation peer for <see cref="RibbonTabControl.ToolbarPanel"/>.
/// </summary>
public class RibbonToolbarPanelAutomationPeer : FrameworkElementAutomationPeer
{
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonToolbarPanelAutomationPeer([NotNull] Panel owner)
: base(owner)
{
this.OwnerPanel = owner;
}
private Panel OwnerPanel { get; }
/// <inheritdoc />
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.List;
}
/// <inheritdoc />
protected override string GetClassNameCore()
{
return "ToolbarPanel";
}
/// <inheritdoc />
protected override string GetNameCore()
{
var name = base.GetNameCore();
if (string.IsNullOrEmpty(name))
{
// Todo: localization
name = "Ribbon toolbar";
}
return name;
}
}
}

View File

@@ -0,0 +1,71 @@
namespace Fluent.Automation.Peers
{
using System.Collections.Generic;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
/// <summary>
/// <see cref="AutomationPeer"/> for <see cref="TwoLineLabel"/>.
/// </summary>
public class TwoLineLabelAutomationPeer : FrameworkElementAutomationPeer
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="owner">Owner of the AutomationPeer.</param>
public TwoLineLabelAutomationPeer(TwoLineLabel owner)
: base(owner)
{
}
/// <summary>
/// <see cref="AutomationPeer.GetChildrenCore"/>
/// </summary>
protected override List<AutomationPeer> GetChildrenCore()
{
return null;
}
/// <summary>
/// <see cref="AutomationPeer.GetAutomationControlTypeCore"/>
/// </summary>
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Text;
}
/// <summary>
/// <see cref="AutomationPeer.GetClassNameCore"/>
/// </summary>
/// <returns></returns>
protected override string GetClassNameCore()
{
return "TwoLineLabel";
}
/// <inheritdoc />
protected override string GetNameCore()
{
return ((TwoLineLabel)this.Owner).Text;
}
/// <summary>
/// <see cref="AutomationPeer.IsControlElementCore"/>
/// </summary>
protected override bool IsControlElementCore()
{
// Return false if TwoLineLabel is part of a ControlTemplate, otherwise return the base method
var tb = (TwoLineLabel)this.Owner;
var templatedParent = tb.TemplatedParent;
// If the templatedParent is a ContentPresenter, this TextBlock is generated from a DataTemplate
if (templatedParent is null
|| templatedParent is ContentPresenter)
{
return base.IsControlElementCore();
}
return false;
}
}
}

View File

@@ -0,0 +1,90 @@
namespace Fluent.Collections
{
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
/// <summary>
/// Synchronizes a target collection with a source collection in a one way fashion.
/// </summary>
public class CollectionSyncHelper<TItem>
{
/// <summary>
/// Creates a new instance with <paramref name="source"/> as <see cref="Source"/> and <paramref name="target"/> as <see cref="Target"/>.
/// </summary>
public CollectionSyncHelper(ObservableCollection<TItem> source, IList target)
{
this.Source = source ?? throw new ArgumentNullException(nameof(source));
this.Target = target ?? throw new ArgumentNullException(nameof(target));
this.SyncTarget();
this.Source.CollectionChanged += this.SourceOnCollectionChanged;
}
/// <summary>
/// The source collection.
/// </summary>
public ObservableCollection<TItem> Source { get; }
/// <summary>
/// The target collection.
/// </summary>
public IList Target { get; }
/// <summary>
/// Clears <see cref="Target"/> and then copies all items from <see cref="Source"/> to <see cref="Target"/>.
/// </summary>
private void SyncTarget()
{
this.Target.Clear();
foreach (var item in this.Source)
{
this.Target.Add(item);
}
}
private void SourceOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
for (var i = 0; i < e.NewItems.Count; i++)
{
var item = (TItem)e.NewItems[i];
this.Target.Insert(e.NewStartingIndex + i, item);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (var item in e.OldItems)
{
this.Target.Remove((TItem)item);
}
break;
case NotifyCollectionChangedAction.Replace:
foreach (var item in e.OldItems)
{
this.Target.Remove((TItem)item);
}
foreach (var item in e.NewItems)
{
this.Target.Add((TItem)item);
}
break;
case NotifyCollectionChangedAction.Reset:
this.SyncTarget();
break;
}
}
}
}

View File

@@ -0,0 +1,147 @@
namespace Fluent.Collections
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
/// <summary>
/// Special collection with support for logical children of a parent object.
/// </summary>
/// <typeparam name="TItem">The type for items.</typeparam>
public class ItemCollectionWithLogicalTreeSupport<TItem> : ObservableCollection<TItem>
{
/// <summary>
/// Creates a new instance.
/// </summary>
/// <param name="parent">The parent which supports logical children.</param>
public ItemCollectionWithLogicalTreeSupport(ILogicalChildSupport parent)
{
this.Parent = parent ?? throw new ArgumentNullException(nameof(parent));
}
/// <summary>
/// Gets wether this collections parent has logical ownership of the items.
/// </summary>
public bool IsOwningItems { get; private set; } = true;
/// <summary>
/// The parent object which support logical children.
/// </summary>
public ILogicalChildSupport Parent { get; }
/// <summary>
/// Adds all items to the logical tree of <see cref="Parent"/>.
/// </summary>
public void AquireLogicalOwnership()
{
if (this.IsOwningItems)
{
return;
}
this.IsOwningItems = true;
foreach (var item in this.Items)
{
this.AddLogicalChild(item);
}
}
/// <summary>
/// Removes all items from the logical tree of <see cref="Parent"/>.
/// </summary>
public void ReleaseLogicalOwnership()
{
if (this.IsOwningItems == false)
{
return;
}
foreach (var item in this.Items)
{
this.RemoveLogicalChild(item);
}
this.IsOwningItems = false;
}
/// <summary>
/// Gets all items where the logical parent is <see cref="Parent"/>.
/// </summary>
public IEnumerable<TItem> GetLogicalChildren()
{
if (this.IsOwningItems == false)
{
return Enumerable.Empty<TItem>();
}
return this.Items;
}
/// <inheritdoc />
protected override void InsertItem(int index, TItem item)
{
base.InsertItem(index, item);
this.AddLogicalChild(item);
}
/// <inheritdoc />
protected override void RemoveItem(int index)
{
this.RemoveLogicalChild(this[index]);
base.RemoveItem(index);
}
/// <inheritdoc />
protected override void SetItem(int index, TItem item)
{
var oldItem = this[index];
if (oldItem != null)
{
this.RemoveLogicalChild(oldItem);
}
base.SetItem(index, item);
if (item != null)
{
this.AddLogicalChild(item);
}
}
/// <inheritdoc />
protected override void ClearItems()
{
foreach (var item in this.Items)
{
this.RemoveLogicalChild(item);
}
base.ClearItems();
}
private void AddLogicalChild(TItem item)
{
if (this.IsOwningItems == false)
{
return;
}
this.Parent.AddLogicalChild(item);
}
private void RemoveLogicalChild(TItem item)
{
if (this.IsOwningItems == false)
{
return;
}
this.Parent.RemoveLogicalChild(item);
}
}
}

View File

@@ -0,0 +1,141 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using Fluent.Helpers;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents backstage button
/// </summary>
public class ApplicationMenu : DropDownButton
{
private static readonly PropertyInfo targetElementPropertyInfo = typeof(ContextMenuEventArgs).GetProperty("TargetElement", BindingFlags.Instance | BindingFlags.NonPublic);
#region Properties
/// <summary>
/// Gets or sets width of right content
/// </summary>
public double RightPaneWidth
{
get { return (double)this.GetValue(RightPaneWidthProperty); }
set { this.SetValue(RightPaneWidthProperty, value); }
}
/// <summary>Identifies the <see cref="RightPaneWidth"/> dependency property.</summary>
public static readonly DependencyProperty RightPaneWidthProperty = DependencyProperty.Register(nameof(RightPaneWidth), typeof(double), typeof(ApplicationMenu), new PropertyMetadata(300.0));
/// <summary>
/// Gets or sets application menu right pane content
/// </summary>
public object RightPaneContent
{
get { return this.GetValue(RightPaneContentProperty); }
set { this.SetValue(RightPaneContentProperty, value); }
}
/// <summary>Identifies the <see cref="RightPaneContent"/> dependency property.</summary>
public static readonly DependencyProperty RightPaneContentProperty = DependencyProperty.Register(nameof(RightPaneContent), typeof(object), typeof(ApplicationMenu), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
/// <summary>
/// Gets or sets application menu bottom pane content
/// </summary>
public object FooterPaneContent
{
get { return this.GetValue(FooterPaneContentProperty); }
set { this.SetValue(FooterPaneContentProperty, value); }
}
/// <summary>Identifies the <see cref="FooterPaneContent"/> dependency property.</summary>
public static readonly DependencyProperty FooterPaneContentProperty = DependencyProperty.Register(nameof(FooterPaneContent), typeof(object), typeof(ApplicationMenu), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region Initialization
/// <summary>
/// Static constructor
/// </summary>
static ApplicationMenu()
{
var type = typeof(ApplicationMenu);
// Override style metadata
DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(type));
// Disable QAT for this control
CanAddToQuickAccessToolBarProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));
// Make default KeyTip
KeyTipProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(null, CoerceKeys));
}
private static object CoerceKeys(DependencyObject d, object basevalue)
{
return basevalue ?? RibbonLocalization.Current.Localization.BackstageButtonKeyTip;
}
/// <summary>
/// Default constructor
/// </summary>
public ApplicationMenu()
{
this.CoerceValue(KeyTipProperty);
}
#endregion
/// <inheritdoc />
protected override void OnContextMenuOpening(ContextMenuEventArgs e)
{
if (ReferenceEquals(e.Source, this))
{
var targetElement = targetElementPropertyInfo?.GetValue(e);
if (targetElement is null
|| ReferenceEquals(targetElement, this))
{
e.Handled = true;
return;
}
}
base.OnContextMenuOpening(e);
}
#region Quick Access Toolbar
/// <inheritdoc />
public override FrameworkElement CreateQuickAccessItem()
{
throw new NotImplementedException();
}
#endregion
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.RightPaneContent != null)
{
yield return this.RightPaneContent;
}
if (this.FooterPaneContent != null)
{
yield return this.FooterPaneContent;
}
}
}
}
}

View File

@@ -0,0 +1,827 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using Fluent.Extensions;
using Fluent.Internal;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents backstage button
/// </summary>
[ContentProperty(nameof(Content))]
public class Backstage : RibbonControl
{
private static readonly object syncIsOpen = new object();
/// <summary>
/// Occurs when IsOpen has been changed
/// </summary>
public event DependencyPropertyChangedEventHandler IsOpenChanged;
private BackstageAdorner adorner;
#region Properties
/// <summary>
/// Gets the <see cref="AdornerLayer"/> for the <see cref="Backstage"/>.
/// </summary>
/// <remarks>This is exposed to make it possible to show content on the same <see cref="AdornerLayer"/> as the backstage is shown on.</remarks>
public AdornerLayer AdornerLayer { get; private set; }
/// <summary>
/// Gets or sets whether backstage is shown
/// </summary>
public bool IsOpen
{
get { return (bool)this.GetValue(IsOpenProperty); }
set { this.SetValue(IsOpenProperty, value); }
}
/// <summary>Identifies the <see cref="IsOpen"/> dependency property.</summary>
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register(nameof(IsOpen), typeof(bool), typeof(Backstage), new PropertyMetadata(BooleanBoxes.FalseBox, OnIsOpenChanged, CoerceIsOpen));
/// <summary>
/// Gets or sets whether backstage can be openend or closed.
/// </summary>
public bool CanChangeIsOpen
{
get { return (bool)this.GetValue(CanChangeIsOpenProperty); }
set { this.SetValue(CanChangeIsOpenProperty, value); }
}
/// <summary>Identifies the <see cref="CanChangeIsOpen"/> dependency property.</summary>
public static readonly DependencyProperty CanChangeIsOpenProperty =
DependencyProperty.Register(nameof(CanChangeIsOpen), typeof(bool), typeof(Backstage), new PropertyMetadata(BooleanBoxes.TrueBox));
/// <summary>
/// Gets or sets whether context tabs on the titlebar should be hidden when backstage is open
/// </summary>
public bool HideContextTabsOnOpen
{
get { return (bool)this.GetValue(HideContextTabsOnOpenProperty); }
set { this.SetValue(HideContextTabsOnOpenProperty, value); }
}
/// <summary>Identifies the <see cref="HideContextTabsOnOpen"/> dependency property.</summary>
public static readonly DependencyProperty HideContextTabsOnOpenProperty =
DependencyProperty.Register(nameof(HideContextTabsOnOpen), typeof(bool), typeof(Backstage), new PropertyMetadata(BooleanBoxes.TrueBox));
/// <summary>
/// Gets or sets whether opening or closing should be animated.
/// </summary>
public bool AreAnimationsEnabled
{
get { return (bool)this.GetValue(AreAnimationsEnabledProperty); }
set { this.SetValue(AreAnimationsEnabledProperty, value); }
}
/// <summary>Identifies the <see cref="AreAnimationsEnabled"/> dependency property.</summary>
public static readonly DependencyProperty AreAnimationsEnabledProperty =
DependencyProperty.Register(nameof(AreAnimationsEnabled), typeof(bool), typeof(Backstage), new PropertyMetadata(BooleanBoxes.TrueBox));
/// <summary>
/// Gets or sets whether to close the backstage when Esc is pressed
/// </summary>
public bool CloseOnEsc
{
get { return (bool)this.GetValue(CloseOnEscProperty); }
set { this.SetValue(CloseOnEscProperty, value); }
}
/// <summary>Identifies the <see cref="CloseOnEsc"/> dependency property.</summary>
public static readonly DependencyProperty CloseOnEscProperty =
DependencyProperty.Register(nameof(CloseOnEsc), typeof(bool), typeof(Backstage), new PropertyMetadata(BooleanBoxes.TrueBox));
private static object CoerceIsOpen(DependencyObject d, object baseValue)
{
var backstage = (Backstage)d;
if (backstage.CanChangeIsOpen == false)
{
return backstage.IsOpen;
}
return baseValue;
}
private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (Backstage)d;
var oldValue = (bool)e.OldValue;
var newValue = (bool)e.NewValue;
lock (syncIsOpen)
{
if ((bool)e.NewValue)
{
control.Show();
}
else
{
control.Hide();
}
// Invoke the event
control.IsOpenChanged?.Invoke(control, e);
}
(UIElementAutomationPeer.FromElement(control) as Fluent.Automation.Peers.RibbonBackstageAutomationPeer)?.RaiseExpandCollapseAutomationEvent(oldValue, newValue);
}
/// <summary>Identifies the <see cref="UseHighestAvailableAdornerLayer"/> dependency property.</summary>
public static readonly DependencyProperty UseHighestAvailableAdornerLayerProperty = DependencyProperty.Register(nameof(UseHighestAvailableAdornerLayer), typeof(bool), typeof(Backstage), new PropertyMetadata(BooleanBoxes.TrueBox));
/// <summary>
/// Gets or sets whether the highest available adorner layer should be used for the <see cref="BackstageAdorner"/>.
/// This means that we will try to look up the visual tree till we find the highest <see cref="AdornerDecorator"/>.
/// </summary>
public bool UseHighestAvailableAdornerLayer
{
get { return (bool)this.GetValue(UseHighestAvailableAdornerLayerProperty); }
set { this.SetValue(UseHighestAvailableAdornerLayerProperty, value); }
}
#region Content
/// <summary>
/// Gets or sets content of the backstage
/// </summary>
public UIElement Content
{
get { return (UIElement)this.GetValue(ContentProperty); }
set { this.SetValue(ContentProperty, value); }
}
/// <summary>Identifies the <see cref="Content"/> dependency property.</summary>
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register(nameof(Content), typeof(UIElement), typeof(Backstage), new PropertyMetadata(OnContentChanged));
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var backstage = (Backstage)d;
if (e.OldValue != null)
{
if (e.NewValue is DependencyObject dependencyObject)
{
BindingOperations.ClearBinding(dependencyObject, VisibilityProperty);
}
backstage.RemoveLogicalChild(e.OldValue);
}
if (e.NewValue != null)
{
backstage.AddLogicalChild(e.NewValue);
if (e.NewValue is DependencyObject dependencyObject)
{
BindingOperations.SetBinding(dependencyObject, VisibilityProperty, new Binding { Path = new PropertyPath(VisibilityProperty), Source = backstage });
}
}
}
#endregion
#region LogicalChildren
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.Content != null)
{
yield return this.Content;
}
if (this.Icon != null)
{
yield return this.Icon;
}
}
}
#endregion
#endregion
#region Initialization
/// <summary>
/// Static constructor
/// </summary>
static Backstage()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Backstage), new FrameworkPropertyMetadata(typeof(Backstage)));
// Disable QAT for this control
CanAddToQuickAccessToolBarProperty.OverrideMetadata(typeof(Backstage), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));
KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(Backstage), new FrameworkPropertyMetadata(KeyboardNavigationMode.Cycle));
}
/// <summary>
/// Default constructor
/// </summary>
public Backstage()
{
this.Loaded += this.OnBackstageLoaded;
this.Unloaded += this.OnBackstageUnloaded;
this.DataContextChanged += this.Handle_DataContextChanged;
}
private void Handle_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (this.adorner != null)
{
this.adorner.DataContext = e.NewValue;
}
}
/// <summary>
/// Called when this control receives the <see cref="PopupService.DismissPopupEvent"/>.
/// </summary>
protected virtual void OnDismissPopup(object sender, DismissPopupEventArgs e)
{
// Don't close on dismiss popup event if application lost focus
// or keytips should be shown.
if (e.DismissReason == DismissPopupReason.ApplicationLostFocus
|| e.DismissReason == DismissPopupReason.ShowingKeyTips)
{
return;
}
// Only close backstage when popups should always be closed.
// "Always" applies to controls marked with IsDefinitive for example.
if (e.DismissMode != DismissPopupMode.Always)
{
return;
}
this.IsOpen = false;
}
#endregion
#region Methods
// Handles click event
private void Click()
{
this.IsOpen = !this.IsOpen;
}
#region Show / Hide
// We have to collapse WindowsFormsHost while Backstate is open
private readonly Dictionary<FrameworkElement, Visibility> collapsedElements = new Dictionary<FrameworkElement, Visibility>();
// Saved window sizes
private double savedWindowMinWidth = double.NaN;
private double savedWindowMinHeight = double.NaN;
private double savedWindowWidth = double.NaN;
private double savedWindowHeight = double.NaN;
private Window ownerWindow;
private Ribbon parentRibbon;
/// <summary>
/// Shows the <see cref="Backstage"/>
/// </summary>
/// <returns>
/// <c>true</c> if the <see cref="Backstage"/> was made visible.
/// <c>false</c> if the <see cref="Backstage"/> was not made visible.
/// </returns>
protected virtual bool Show()
{
// don't open the backstage while in design mode
if (DesignerProperties.GetIsInDesignMode(this))
{
return false;
}
if (this.IsLoaded == false)
{
this.Loaded += this.OnDelayedShow;
return false;
}
if (this.Content is null)
{
return false;
}
this.CreateAndAttachBackstageAdorner();
this.ShowAdorner();
this.parentRibbon = GetParentRibbon(this);
if (this.parentRibbon != null)
{
if (this.parentRibbon.TabControl != null)
{
this.parentRibbon.TabControl.IsDropDownOpen = false;
this.parentRibbon.TabControl.HighlightSelectedItem = false;
this.parentRibbon.TabControl.RequestBackstageClose += this.HandleTabControlRequestBackstageClose;
}
if (this.parentRibbon.QuickAccessToolBar != null)
{
this.parentRibbon.QuickAccessToolBar.IsEnabled = false;
}
if (this.parentRibbon.TitleBar != null)
{
this.parentRibbon.TitleBar.HideContextTabs = this.HideContextTabsOnOpen;
}
}
this.ownerWindow = Window.GetWindow(this);
if (this.ownerWindow is null
&& this.Parent != null)
{
this.ownerWindow = Window.GetWindow(this.Parent);
}
this.SaveWindowSize(this.ownerWindow);
this.SaveWindowMinSize(this.ownerWindow);
if (this.ownerWindow != null)
{
this.ownerWindow.KeyDown += this.HandleOwnerWindowKeyDown;
if (this.savedWindowMinWidth < 500)
{
this.ownerWindow.MinWidth = 500;
}
if (this.savedWindowMinHeight < 400)
{
this.ownerWindow.MinHeight = 400;
}
this.ownerWindow.SizeChanged += this.HandleOwnerWindowSizeChanged;
// We have to collapse WindowsFormsHost while Backstage is open
this.CollapseWindowsFormsHosts(this.ownerWindow);
}
var content = this.Content as IInputElement;
content?.Focus();
return true;
}
/// <summary>
/// Hides the <see cref="Backstage"/>
/// </summary>
protected virtual void Hide()
{
// potentially fixes https://github.com/fluentribbon/Fluent.Ribbon/issues/489
if (this.Dispatcher.HasShutdownStarted
|| this.Dispatcher.HasShutdownFinished)
{
return;
}
this.Loaded -= this.OnDelayedShow;
if (this.Content is null)
{
return;
}
if (!this.IsLoaded
|| this.adorner is null)
{
return;
}
this.HideAdornerAndRestoreParentProperties();
}
private void ShowAdorner()
{
if (this.adorner is null)
{
return;
}
if (this.AreAnimationsEnabled
&& this.TryFindResource("Fluent.Ribbon.Storyboards.Backstage.IsOpenTrueStoryboard") is Storyboard storyboard)
{
storyboard = storyboard.Clone();
storyboard.CurrentStateInvalidated += HandleStoryboardCurrentStateInvalidated;
storyboard.Completed += HandleStoryboardOnCompleted;
storyboard.Begin(this.adorner);
}
else
{
this.adorner.Visibility = Visibility.Visible;
}
void HandleStoryboardCurrentStateInvalidated(object sender, EventArgs e)
{
this.adorner.Visibility = Visibility.Visible;
storyboard.CurrentStateInvalidated -= HandleStoryboardCurrentStateInvalidated;
}
void HandleStoryboardOnCompleted(object sender, EventArgs args)
{
this.AdornerLayer?.Update();
storyboard.Completed -= HandleStoryboardOnCompleted;
}
}
private void HideAdornerAndRestoreParentProperties()
{
if (this.adorner is null)
{
return;
}
if (this.AreAnimationsEnabled
&& this.TryFindResource("Fluent.Ribbon.Storyboards.Backstage.IsOpenFalseStoryboard") is Storyboard storyboard)
{
storyboard = storyboard.Clone();
storyboard.Completed += HandleStoryboardOnCompleted;
storyboard.Begin(this.adorner);
}
else
{
this.adorner.Visibility = Visibility.Collapsed;
this.RestoreParentProperties();
}
void HandleStoryboardOnCompleted(object sender, EventArgs args)
{
if (this.adorner != null)
{
this.adorner.Visibility = Visibility.Collapsed;
}
if (this.AdornerLayer != null)
{
this.AdornerLayer.Visibility = Visibility.Visible;
}
this.RestoreParentProperties();
storyboard.Completed -= HandleStoryboardOnCompleted;
}
}
private static void HandleOpenBackstageCommandCanExecute(object sender, CanExecuteRoutedEventArgs args)
{
var target = ((BackstageAdorner)args.Source).Backstage;
args.CanExecute = target.CanChangeIsOpen;
}
private static void HandleOpenBackstageCommandExecuted(object sender, ExecutedRoutedEventArgs args)
{
var target = ((BackstageAdorner)args.Source).Backstage;
target.IsOpen = !target.IsOpen;
}
private void CreateAndAttachBackstageAdorner()
{
// It's possible that we created an adorner but it's parent AdornerLayer got destroyed.
// If that's the case we have to destroy our adorner.
// This fixes #228 Backstage disappears when changing DontUseDwm
if (this.adorner?.Parent is null)
{
this.DestroyAdorner();
}
if (this.adorner != null)
{
return;
}
FrameworkElement elementToAdorn = UIHelper.GetParent<AdornerDecorator>(this)
?? UIHelper.GetParent<AdornerDecorator>(this.Parent);
if (elementToAdorn is null)
{
return;
}
if (this.UseHighestAvailableAdornerLayer)
{
AdornerDecorator currentAdornerDecorator;
while ((currentAdornerDecorator = UIHelper.GetParent<AdornerDecorator>(elementToAdorn)) != null)
{
elementToAdorn = currentAdornerDecorator;
}
}
this.AdornerLayer = UIHelper.GetAdornerLayer(elementToAdorn);
if (this.AdornerLayer is null)
{
throw new Exception($"AdornerLayer could not be found for {this}.");
}
this.adorner = new BackstageAdorner(elementToAdorn, this);
BindingOperations.SetBinding(this.adorner, DataContextProperty, new Binding
{
Path = new PropertyPath(DataContextProperty),
Source = this
});
this.AdornerLayer.Add(this.adorner);
this.AdornerLayer.CommandBindings.Add(new CommandBinding(RibbonCommands.OpenBackstage, HandleOpenBackstageCommandExecuted, HandleOpenBackstageCommandCanExecute));
}
private void DestroyAdorner()
{
this.AdornerLayer?.CommandBindings.Clear();
this.AdornerLayer?.Remove(this.adorner);
if (this.adorner != null)
{
BindingOperations.ClearAllBindings(this.adorner);
}
this.adorner?.Clear();
this.adorner = null;
this.AdornerLayer = null;
}
private void RestoreParentProperties()
{
if (this.parentRibbon != null)
{
if (this.parentRibbon.TabControl != null)
{
this.parentRibbon.TabControl.HighlightSelectedItem = true;
this.parentRibbon.TabControl.RequestBackstageClose -= this.HandleTabControlRequestBackstageClose;
}
if (this.parentRibbon.QuickAccessToolBar != null)
{
this.parentRibbon.QuickAccessToolBar.IsEnabled = true;
this.parentRibbon.QuickAccessToolBar.Refresh();
}
if (this.parentRibbon.TitleBar != null)
{
this.parentRibbon.TitleBar.HideContextTabs = false;
}
this.parentRibbon = null;
}
if (this.ownerWindow != null)
{
this.ownerWindow.PreviewKeyDown -= this.HandleOwnerWindowKeyDown;
this.ownerWindow.SizeChanged -= this.HandleOwnerWindowSizeChanged;
if (double.IsNaN(this.savedWindowMinWidth) == false
&& double.IsNaN(this.savedWindowMinHeight) == false)
{
this.ownerWindow.MinWidth = this.savedWindowMinWidth;
this.ownerWindow.MinHeight = this.savedWindowMinHeight;
}
if (double.IsNaN(this.savedWindowWidth) == false
&& double.IsNaN(this.savedWindowHeight) == false)
{
this.ownerWindow.Width = this.savedWindowWidth;
this.ownerWindow.Height = this.savedWindowHeight;
}
this.savedWindowMinWidth = double.NaN;
this.savedWindowMinHeight = double.NaN;
this.savedWindowWidth = double.NaN;
this.savedWindowHeight = double.NaN;
this.ownerWindow = null;
}
// Uncollapse elements
foreach (var element in this.collapsedElements)
{
element.Key.Visibility = element.Value;
}
this.collapsedElements.Clear();
}
private void OnDelayedShow(object sender, EventArgs args)
{
this.Loaded -= this.OnDelayedShow;
// Delaying show so everthing can load properly.
// If we don't run this in the background setting IsOpen=true on application start we don't have access to the Bastage from the BackstageTabControl.
this.RunInDispatcherAsync(() => this.Show(), DispatcherPriority.Background);
}
private void SaveWindowMinSize(Window window)
{
if (window is null)
{
this.savedWindowMinWidth = double.NaN;
this.savedWindowMinHeight = double.NaN;
return;
}
this.savedWindowMinWidth = window.MinWidth;
this.savedWindowMinHeight = window.MinHeight;
}
private void SaveWindowSize(Window window)
{
if (window is null
|| window.WindowState == WindowState.Maximized)
{
this.savedWindowWidth = double.NaN;
this.savedWindowHeight = double.NaN;
return;
}
this.savedWindowWidth = window.ActualWidth;
this.savedWindowHeight = window.ActualHeight;
}
private void HandleOwnerWindowSizeChanged(object sender, SizeChangedEventArgs e)
{
this.SaveWindowSize(Window.GetWindow(this));
}
private void HandleTabControlRequestBackstageClose(object sender, EventArgs e)
{
this.IsOpen = false;
}
// We have to collapse WindowsFormsHost while Backstage is open
private void CollapseWindowsFormsHosts(DependencyObject parent)
{
switch (parent)
{
case null:
case BackstageAdorner _:
return;
case FrameworkElement frameworkElement when parent is HwndHost
&& frameworkElement.Visibility != Visibility.Collapsed:
this.collapsedElements.Add(frameworkElement, frameworkElement.Visibility);
frameworkElement.Visibility = Visibility.Collapsed;
return;
}
// Traverse visual tree
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
this.CollapseWindowsFormsHosts(VisualTreeHelper.GetChild(parent, i));
}
}
/// <inheritdoc />
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Handled)
{
return;
}
if (e.Key == Key.Enter
|| e.Key == Key.Space)
{
if (this.IsFocused)
{
this.IsOpen = !this.IsOpen;
e.Handled = true;
}
}
base.OnKeyDown(e);
}
// Handles backstage Esc key keydown
private void HandleOwnerWindowKeyDown(object sender, KeyEventArgs e)
{
if (this.CloseOnEsc == false
|| e.Key != Key.Escape)
{
return;
}
// only handle ESC when the backstage is open
e.Handled = this.IsOpen;
this.IsOpen = false;
}
private void OnBackstageLoaded(object sender, RoutedEventArgs e)
{
this.AddHandler(PopupService.DismissPopupEvent, (EventHandler<DismissPopupEventArgs>)this.OnDismissPopup);
}
private void OnBackstageUnloaded(object sender, RoutedEventArgs e)
{
this.RemoveHandler(PopupService.DismissPopupEvent, (EventHandler<DismissPopupEventArgs>)this.OnDismissPopup);
this.DestroyAdorner();
}
#endregion
#endregion
#region Overrides
/// <inheritdoc />
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
if (ReferenceEquals(e.Source, this) == false)
{
return;
}
this.Click();
}
/// <inheritdoc />
public override KeyTipPressedResult OnKeyTipPressed()
{
this.IsOpen = true;
base.OnKeyTipPressed();
return KeyTipPressedResult.Empty;
}
/// <inheritdoc />
public override void OnKeyTipBack()
{
this.IsOpen = false;
base.OnKeyTipBack();
}
/// <inheritdoc />
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (this.IsOpen)
{
this.Hide();
}
this.DestroyAdorner();
if (this.IsOpen)
{
this.Show();
}
}
#endregion
#region Quick Access Toolbar
/// <inheritdoc />
public override FrameworkElement CreateQuickAccessItem()
{
throw new NotImplementedException();
}
#endregion
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() => new Fluent.Automation.Peers.RibbonBackstageAutomationPeer(this);
}
}

View File

@@ -0,0 +1,115 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using Fluent.Internal;
/// <summary>
/// Represents adorner for Backstage
/// </summary>
internal class BackstageAdorner : Adorner
{
// Content of Backstage
private readonly UIElement backstageContent;
// Collection of visual children
private readonly VisualCollection visualChildren;
private readonly Rectangle background;
private readonly BackstageTabControl backstageTabControl;
/// <summary>
/// Initializes a new instance of the <see cref="BackstageAdorner"/> class.
/// </summary>
/// <param name="adornedElement">Adorned element</param>
/// <param name="backstage">Backstage</param>
public BackstageAdorner(FrameworkElement adornedElement, Backstage backstage)
: base(adornedElement)
{
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Contained);
KeyboardNavigation.SetControlTabNavigation(this, KeyboardNavigationMode.Contained);
KeyboardNavigation.SetDirectionalNavigation(this, KeyboardNavigationMode.Contained);
this.Backstage = backstage;
this.backstageContent = this.Backstage.Content;
this.backstageTabControl = this.backstageContent as BackstageTabControl
?? UIHelper.FindVisualChild<BackstageTabControl>(this.backstageContent);
this.background = new Rectangle();
if (this.backstageTabControl != null)
{
BindingOperations.SetBinding(this.background, Shape.FillProperty, new Binding
{
Path = new PropertyPath(Control.BackgroundProperty),
Source = this.backstageTabControl
});
BindingOperations.SetBinding(this.background, MarginProperty, new Binding
{
Path = new PropertyPath(BackstageTabControl.SelectedContentMarginProperty),
Source = this.backstageTabControl
});
}
else
{
this.background.SetResourceReference(Shape.FillProperty, "WhiteBrush");
}
this.visualChildren = new VisualCollection(this)
{
this.background,
this.backstageContent
};
}
/// <summary>
/// Gets the <see cref="Fluent.Backstage"/>.
/// </summary>
public Backstage Backstage { get; }
public void Clear()
{
BindingOperations.ClearAllBindings(this.background);
this.visualChildren.Clear();
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize)
{
// Arrange background and compensate margin used by animation
this.background.Arrange(new Rect(this.Margin.Left * -1, 0, Math.Max(0, finalSize.Width), Math.Max(0, finalSize.Height)));
this.backstageContent.Arrange(new Rect(0, 0, Math.Max(0, finalSize.Width), Math.Max(0, finalSize.Height)));
return finalSize;
}
/// <inheritdoc />
protected override Size MeasureOverride(Size constraint)
{
var size = new Size(Math.Max(0, this.AdornedElement.RenderSize.Width), Math.Max(0, this.AdornedElement.RenderSize.Height));
this.background.Measure(size);
this.backstageContent.Measure(size);
return this.AdornedElement.RenderSize;
}
/// <inheritdoc />
protected override int VisualChildrenCount => this.visualChildren.Count;
/// <inheritdoc />
protected override Visual GetVisualChild(int index)
{
return this.visualChildren[index];
}
}
}

View File

@@ -0,0 +1,514 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using Fluent.Extensions;
using Fluent.Helpers;
using Fluent.Internal;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents Backstage tab control.
/// </summary>
[TemplatePart(Name = "PART_SelectedContentHost", Type = typeof(ContentPresenter))]
public class BackstageTabControl : Selector, ILogicalChildSupport
{
#region Properties
internal ContentPresenter SelectedContentHost { get; private set; }
/// <summary>
/// Gets or sets the margin which is used to render selected content.
/// </summary>
public Thickness SelectedContentMargin
{
get { return (Thickness)this.GetValue(SelectedContentMarginProperty); }
set { this.SetValue(SelectedContentMarginProperty, value); }
}
/// <summary>Identifies the <see cref="SelectedContentMargin"/> dependency property.</summary>
public static readonly DependencyProperty SelectedContentMarginProperty =
DependencyProperty.Register(nameof(SelectedContentMargin), typeof(Thickness), typeof(BackstageTabControl), new PropertyMetadata(default(Thickness)));
// Dependency property key for SelectedContent
private static readonly DependencyPropertyKey SelectedContentPropertyKey = DependencyProperty.RegisterReadOnly(nameof(SelectedContent), typeof(object), typeof(BackstageTabControl), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
/// <summary>Identifies the <see cref="SelectedContent"/> dependency property.</summary>
public static readonly DependencyProperty SelectedContentProperty = SelectedContentPropertyKey.DependencyProperty;
/// <summary>
/// Gets content for selected tab
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public object SelectedContent
{
get { return this.GetValue(SelectedContentProperty); }
internal set { this.SetValue(SelectedContentPropertyKey, value); }
}
/// <summary>Identifies the <see cref="ContentStringFormat"/> dependency property.</summary>
public static readonly DependencyProperty ContentStringFormatProperty = DependencyProperty.Register(nameof(ContentStringFormat), typeof(string), typeof(BackstageTabControl), new PropertyMetadata());
/// <summary>Identifies the <see cref="ContentTemplate"/> dependency property.</summary>
public static readonly DependencyProperty ContentTemplateProperty = DependencyProperty.Register(nameof(ContentTemplate), typeof(DataTemplate), typeof(BackstageTabControl), new PropertyMetadata());
/// <summary>Identifies the <see cref="ContentTemplateSelector"/> dependency property.</summary>
public static readonly DependencyProperty ContentTemplateSelectorProperty = DependencyProperty.Register(nameof(ContentTemplateSelector), typeof(DataTemplateSelector), typeof(BackstageTabControl), new PropertyMetadata());
private static readonly DependencyPropertyKey SelectedContentStringFormatPropertyKey = DependencyProperty.RegisterReadOnly(nameof(SelectedContentStringFormat), typeof(string), typeof(BackstageTabControl), new PropertyMetadata());
/// <summary>Identifies the <see cref="SelectedContentStringFormat"/> dependency property.</summary>
public static readonly DependencyProperty SelectedContentStringFormatProperty = SelectedContentStringFormatPropertyKey.DependencyProperty;
private static readonly DependencyPropertyKey SelectedContentTemplatePropertyKey = DependencyProperty.RegisterReadOnly(nameof(SelectedContentTemplate), typeof(DataTemplate), typeof(BackstageTabControl), new PropertyMetadata());
/// <summary>Identifies the <see cref="SelectedContentTemplate"/> dependency property.</summary>
public static readonly DependencyProperty SelectedContentTemplateProperty = SelectedContentTemplatePropertyKey.DependencyProperty;
private static readonly DependencyPropertyKey SelectedContentTemplateSelectorPropertyKey = DependencyProperty.RegisterReadOnly(nameof(SelectedContentTemplateSelector), typeof(DataTemplateSelector), typeof(BackstageTabControl), new PropertyMetadata());
/// <summary>Identifies the <see cref="SelectedContentTemplateSelector"/> dependency property.</summary>
public static readonly DependencyProperty SelectedContentTemplateSelectorProperty = SelectedContentTemplateSelectorPropertyKey.DependencyProperty;
/// <summary>
/// Get or sets the string format for the content.
/// </summary>
public string ContentStringFormat
{
get
{
return (string)this.GetValue(ContentStringFormatProperty);
}
set
{
this.SetValue(ContentStringFormatProperty, value);
}
}
/// <summary>
/// Gets or sets the <see cref="DataTemplate"/> which should be used for the content
/// </summary>
public DataTemplate ContentTemplate
{
get
{
return (DataTemplate)this.GetValue(ContentTemplateProperty);
}
set
{
this.SetValue(ContentTemplateProperty, value);
}
}
/// <summary>
/// Gets or sets the <see cref="ContentTemplateSelector"/> which should be used for the content
/// </summary>
public DataTemplateSelector ContentTemplateSelector
{
get
{
return (DataTemplateSelector)this.GetValue(ContentTemplateSelectorProperty);
}
set
{
this.SetValue(ContentTemplateSelectorProperty, value);
}
}
/// <summary>
/// Get or sets the string format for the selected content.
/// </summary>
public string SelectedContentStringFormat
{
get
{
return (string)this.GetValue(SelectedContentStringFormatProperty);
}
internal set
{
this.SetValue(SelectedContentStringFormatPropertyKey, value);
}
}
/// <summary>
/// Gets or sets the <see cref="DataTemplate"/> which should be used for the selected content
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public DataTemplate SelectedContentTemplate
{
get
{
return (DataTemplate)this.GetValue(SelectedContentTemplateProperty);
}
internal set
{
this.SetValue(SelectedContentTemplatePropertyKey, value);
}
}
/// <summary>
/// Gets or sets the <see cref="ContentTemplateSelector"/> which should be used for the selected content
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public DataTemplateSelector SelectedContentTemplateSelector
{
get
{
return (DataTemplateSelector)this.GetValue(SelectedContentTemplateSelectorProperty);
}
internal set
{
this.SetValue(SelectedContentTemplateSelectorPropertyKey, value);
}
}
#region ItemsPanelMinWidth
/// <summary>Identifies the <see cref="ItemsPanelMinWidth"/> dependency property.</summary>
public static readonly DependencyProperty ItemsPanelMinWidthProperty = DependencyProperty.Register(nameof(ItemsPanelMinWidth), typeof(double), typeof(BackstageTabControl), new PropertyMetadata(125d));
/// <summary>
/// Gets or sets the MinWidth for the ItemsPanel.
/// </summary>
public double ItemsPanelMinWidth
{
get { return (double)this.GetValue(ItemsPanelMinWidthProperty); }
set { this.SetValue(ItemsPanelMinWidthProperty, value); }
}
#endregion
#region ItemsPanelBackground
/// <summary>
/// Gets or sets current Backround of the ItemsPanel
/// </summary>
public Brush ItemsPanelBackground
{
get { return (Brush)this.GetValue(ItemsPanelBackgroundProperty); }
set { this.SetValue(ItemsPanelBackgroundProperty, value); }
}
/// <summary>Identifies the <see cref="ItemsPanelBackground"/> dependency property.</summary>
public static readonly DependencyProperty ItemsPanelBackgroundProperty = DependencyProperty.Register(nameof(ItemsPanelBackground), typeof(Brush), typeof(BackstageTabControl));
#endregion
/// <summary>
/// Gets or sets the <see cref="ParentBackstage"/>
/// </summary>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Backstage ParentBackstage
{
get { return (Backstage)this.GetValue(ParentBackstageProperty); }
set { this.SetValue(ParentBackstageProperty, value); }
}
/// <summary>Identifies the <see cref="ParentBackstage"/> dependency property.</summary>
public static readonly DependencyProperty ParentBackstageProperty =
DependencyProperty.Register(nameof(ParentBackstage), typeof(Backstage), typeof(BackstageTabControl), new PropertyMetadata());
/// <summary>
/// Defines if the <see cref="WindowSteeringHelperControl"/> is enabled in this control
/// </summary>
public bool IsWindowSteeringHelperEnabled
{
get { return (bool)this.GetValue(IsWindowSteeringHelperEnabledProperty); }
set { this.SetValue(IsWindowSteeringHelperEnabledProperty, value); }
}
/// <summary>Identifies the <see cref="IsWindowSteeringHelperEnabled"/> dependency property.</summary>
public static readonly DependencyProperty IsWindowSteeringHelperEnabledProperty =
DependencyProperty.Register(nameof(IsWindowSteeringHelperEnabled), typeof(bool), typeof(BackstageTabControl), new PropertyMetadata(BooleanBoxes.TrueBox));
/// <summary>
/// Defines if the back button is visible or not.
/// </summary>
public bool IsBackButtonVisible
{
get { return (bool)this.GetValue(IsBackButtonVisibleProperty); }
set { this.SetValue(IsBackButtonVisibleProperty, value); }
}
/// <summary>Identifies the <see cref="IsBackButtonVisible"/> dependency property.</summary>
public static readonly DependencyProperty IsBackButtonVisibleProperty = DependencyProperty.Register(nameof(IsBackButtonVisible), typeof(bool), typeof(BackstageTabControl), new PropertyMetadata(BooleanBoxes.TrueBox));
#endregion
#region Constructors
/// <summary>
/// Static constructor
/// </summary>
static BackstageTabControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BackstageTabControl), new FrameworkPropertyMetadata(typeof(BackstageTabControl)));
}
/// <summary>
/// Default constructor
/// </summary>
public BackstageTabControl()
{
// Fixed incorect menu showing
this.ContextMenu = new ContextMenu
{
Width = 0,
Height = 0,
HasDropShadow = false
};
this.ContextMenu.Opened += (sender, args) => this.ContextMenu.IsOpen = false;
this.Loaded += this.HandleLoaded;
this.Unloaded += this.HandleUnloaded;
}
private void HandleLoaded(object sender, RoutedEventArgs e)
{
this.ParentBackstage = UIHelper.GetParent<Backstage>(this);
}
private void HandleUnloaded(object sender, RoutedEventArgs e)
{
this.ParentBackstage = null;
}
#endregion
#region Overrides
/// <inheritdoc />
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
this.ItemContainerGenerator.StatusChanged += this.OnGeneratorStatusChanged;
}
/// <inheritdoc />
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.SelectedContentHost = this.GetTemplateChild("PART_SelectedContentHost") as ContentPresenter;
}
/// <inheritdoc />
protected override DependencyObject GetContainerForItemOverride()
{
return new BackstageTabItem();
}
/// <inheritdoc />
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is BackstageTabItem
|| item is Button
|| item is SeparatorTabItem
|| item is Separator;
}
/// <inheritdoc />
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (e.Action == NotifyCollectionChangedAction.Remove
&& this.SelectedIndex == -1)
{
var startIndex = e.OldStartingIndex + 1;
if (startIndex > this.Items.Count)
{
startIndex = 0;
}
var item = this.FindNextTabItem(startIndex, -1);
if (item != null)
{
item.IsSelected = true;
}
}
}
/// <inheritdoc />
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
if (e.AddedItems.Count > 0)
{
this.UpdateSelectedContent();
}
}
#endregion
#region Private methods
/// <summary>
/// Gets selected <see cref="BackstageTabItem"/>.
/// If there is no item selected, the first found item is selected and it's container (<see cref="BackstageTabItem"/>) is returned.
/// </summary>
/// <returns>The currently selected <see cref="BackstageTabItem"/>. Or null of nothing was selected and nothing could be selected.</returns>
private BackstageTabItem GetSelectedTabItem()
{
var container = this.ItemContainerGenerator.ContainerOrContainerContentFromItem<BackstageTabItem>(this.SelectedItem);
if (container is null
|| container.IsEnabled == false
|| container.Visibility != Visibility.Visible)
{
container = this.FindNextTabItem(this.SelectedIndex, 1);
if (container != null)
{
this.SelectedItem = this.ItemContainerGenerator.ItemFromContainerOrContainerContent(container);
}
}
return container;
}
// Finds next tab item
private BackstageTabItem FindNextTabItem(int startIndex, int direction)
{
if (direction == 0)
{
return null;
}
var index = startIndex;
for (var i = 0; i < this.Items.Count; i++)
{
index += direction;
if (index >= this.Items.Count)
{
index = 0;
}
else if (index < 0)
{
index = this.Items.Count - 1;
}
var container = this.ItemContainerGenerator.ContainerOrContainerContentFromIndex<BackstageTabItem>(index);
if (container != null
&& container.IsEnabled
&& container.Visibility == Visibility.Visible)
{
return container;
}
}
return null;
}
// Updates selected content
private void UpdateSelectedContent()
{
if (this.SelectedIndex < 0)
{
this.SelectedContent = null;
}
else
{
var selectedTabItem = this.GetSelectedTabItem();
if (selectedTabItem is null)
{
return;
}
this.SelectedContent = selectedTabItem.Content;
if (selectedTabItem.ContentTemplate != null
|| selectedTabItem.ContentTemplateSelector != null
|| selectedTabItem.ContentStringFormat != null)
{
this.SelectedContentTemplate = selectedTabItem.ContentTemplate;
this.SelectedContentTemplateSelector = selectedTabItem.ContentTemplateSelector;
this.SelectedContentStringFormat = selectedTabItem.ContentStringFormat;
}
else
{
this.SelectedContentTemplate = this.ContentTemplate;
this.SelectedContentTemplateSelector = this.ContentTemplateSelector;
this.SelectedContentStringFormat = this.ContentStringFormat;
}
this.UpdateLayout();
}
}
#endregion
#region Event handling
// Handles GeneratorStatusChange
private void OnGeneratorStatusChanged(object sender, EventArgs e)
{
if (this.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
{
return;
}
if (this.HasItems
&& this.SelectedIndex == -1)
{
this.SelectedIndex = 0;
}
this.UpdateSelectedContent();
}
#endregion
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() => new Fluent.Automation.Peers.RibbonBackstageTabControlAutomationPeer(this);
/// <inheritdoc />
void ILogicalChildSupport.AddLogicalChild(object child)
{
this.AddLogicalChild(child);
}
/// <inheritdoc />
void ILogicalChildSupport.RemoveLogicalChild(object child)
{
this.RemoveLogicalChild(child);
}
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.SelectedContent != null)
{
yield return this.SelectedContent;
}
}
}
}
}

View File

@@ -0,0 +1,280 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using Fluent.Extensions;
using Fluent.Helpers;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents backstage tab item
/// </summary>
[TemplatePart(Name = "PART_Header", Type = typeof(FrameworkElement))]
public class BackstageTabItem : ContentControl, IHeaderedControl, IKeyTipedControl, ILogicalChildSupport
{
internal FrameworkElement HeaderContentHost { get; private set; }
#region Icon
/// <summary>
/// Gets or sets Icon for the element
/// </summary>
public object Icon
{
get { return this.GetValue(IconProperty); }
set { this.SetValue(IconProperty, value); }
}
/// <summary>Identifies the <see cref="Icon"/> dependency property.</summary>
public static readonly DependencyProperty IconProperty = RibbonControl.IconProperty.AddOwner(typeof(BackstageTabItem), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
/// <inheritdoc />
public string KeyTip
{
get { return (string)this.GetValue(KeyTipProperty); }
set { this.SetValue(KeyTipProperty, value); }
}
/// <summary>
/// Dependency property for <see cref="KeyTip"/>
/// </summary>
public static readonly DependencyProperty KeyTipProperty = Fluent.KeyTip.KeysProperty.AddOwner(typeof(BackstageTabItem));
/// <summary>
/// Gets or sets a value indicating whether the tab is selected
/// </summary>
[Bindable(true)]
[Category("Appearance")]
public bool IsSelected
{
get { return (bool)this.GetValue(IsSelectedProperty); }
set { this.SetValue(IsSelectedProperty, value); }
}
/// <summary>
/// Dependency property for <see cref="IsSelected"/>
/// </summary>
public static readonly DependencyProperty IsSelectedProperty =
Selector.IsSelectedProperty.AddOwner(typeof(BackstageTabItem),
new FrameworkPropertyMetadata(BooleanBoxes.FalseBox,
FrameworkPropertyMetadataOptions.Journal |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
FrameworkPropertyMetadataOptions.AffectsParentMeasure,
OnIsSelectedChanged));
/// <summary>
/// Gets parent tab control
/// </summary>
internal BackstageTabControl TabControlParent
{
get
{
return ItemsControlHelper.ItemsControlFromItemContainer(this) as BackstageTabControl;
}
}
/// <summary>
/// Gets or sets tab items text
/// </summary>
public object Header
{
get { return this.GetValue(HeaderProperty); }
set { this.SetValue(HeaderProperty, value); }
}
/// <summary>Identifies the <see cref="Header"/> dependency property.</summary>
public static readonly DependencyProperty HeaderProperty = RibbonControl.HeaderProperty.AddOwner(typeof(BackstageTabItem), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
/// <summary>
/// Static constructor
/// </summary>
static BackstageTabItem()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BackstageTabItem), new FrameworkPropertyMetadata(typeof(BackstageTabItem)));
KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(BackstageTabItem), new FrameworkPropertyMetadata(KeyboardNavigationMode.Local));
KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(typeof(BackstageTabItem), new FrameworkPropertyMetadata(KeyboardNavigationMode.Cycle));
}
#region Overrides
/// <inheritdoc />
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.HeaderContentHost = this.GetTemplateChild("PART_Header") as FrameworkElement;
}
/// <inheritdoc />
protected override void OnContentChanged(object oldContent, object newContent)
{
base.OnContentChanged(oldContent, newContent);
if (this.IsSelected
&& this.TabControlParent != null)
{
this.TabControlParent.SelectedContent = newContent;
}
}
/// <inheritdoc />
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
if (ReferenceEquals(e.Source, this)
|| this.IsSelected == false)
{
this.IsSelected = true;
}
}
/// <inheritdoc />
protected override void OnKeyUp(KeyEventArgs e)
{
if ((e.Key == Key.Space || e.Key == Key.Enter)
&& (ReferenceEquals(e.Source, this) || this.IsSelected == false))
{
this.IsSelected = true;
}
else
{
base.OnKeyUp(e);
}
}
#endregion
#region Private methods
// Handles IsSelected changed
private static void OnIsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var container = (BackstageTabItem)d;
var newValue = (bool)e.NewValue;
if (newValue)
{
if (container.TabControlParent != null
&& ReferenceEquals(container.TabControlParent.ItemContainerGenerator.ContainerOrContainerContentFromItem<BackstageTabItem>(container.TabControlParent.SelectedItem), container) == false)
{
UnselectSelectedItem(container.TabControlParent);
container.TabControlParent.SelectedItem = container.TabControlParent.ItemContainerGenerator.ItemFromContainerOrContainerContent(container);
}
container.OnSelected(new RoutedEventArgs(Selector.SelectedEvent, container));
}
else
{
container.OnUnselected(new RoutedEventArgs(Selector.UnselectedEvent, container));
}
}
private static void UnselectSelectedItem(BackstageTabControl backstageTabControl)
{
if (backstageTabControl?.SelectedItem is null)
{
return;
}
if (backstageTabControl.ItemContainerGenerator.ContainerOrContainerContentFromItem<BackstageTabItem>(backstageTabControl.SelectedItem) is BackstageTabItem backstageTabItem)
{
backstageTabItem.IsSelected = false;
}
}
#endregion
/// <summary>
/// Handles selected event
/// </summary>
/// <param name="e">The event data.</param>
protected virtual void OnSelected(RoutedEventArgs e)
{
this.HandleIsSelectedChanged(e);
}
/// <summary>
/// Handles unselected event
/// </summary>
/// <param name="e">The event data.</param>
protected virtual void OnUnselected(RoutedEventArgs e)
{
this.HandleIsSelectedChanged(e);
}
#region Event handling
/// <summary>
/// Handles IsSelected changed
/// </summary>
/// <param name="e">The event data.</param>
private void HandleIsSelectedChanged(RoutedEventArgs e)
{
this.RaiseEvent(e);
}
#endregion
/// <inheritdoc />
public KeyTipPressedResult OnKeyTipPressed()
{
UnselectSelectedItem(this.TabControlParent);
this.IsSelected = true;
return KeyTipPressedResult.Empty;
}
/// <inheritdoc />
public void OnKeyTipBack()
{
}
/// <inheritdoc />
void ILogicalChildSupport.AddLogicalChild(object child)
{
this.AddLogicalChild(child);
}
/// <inheritdoc />
void ILogicalChildSupport.RemoveLogicalChild(object child)
{
this.RemoveLogicalChild(child);
}
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.Icon != null)
{
yield return this.Icon;
}
if (this.Header != null)
{
yield return this.Header;
}
}
}
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() => new Fluent.Automation.Peers.RibbonBackstageTabItemAutomationPeer(this);
}
}

View File

@@ -0,0 +1,246 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Collections;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Markup;
using Fluent.Helpers;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents button
/// </summary>
[ContentProperty(nameof(Header))]
public class Button : System.Windows.Controls.Button, IRibbonControl, IQuickAccessItemProvider, ILargeIconProvider
{
#region Properties
#region Size
/// <inheritdoc />
public RibbonControlSize Size
{
get { return (RibbonControlSize)this.GetValue(SizeProperty); }
set { this.SetValue(SizeProperty, value); }
}
/// <summary>Identifies the <see cref="Size"/> dependency property.</summary>
public static readonly DependencyProperty SizeProperty = RibbonProperties.SizeProperty.AddOwner(typeof(Button));
#endregion
#region SizeDefinition
/// <inheritdoc />
public RibbonControlSizeDefinition SizeDefinition
{
get { return (RibbonControlSizeDefinition)this.GetValue(SizeDefinitionProperty); }
set { this.SetValue(SizeDefinitionProperty, value); }
}
/// <summary>Identifies the <see cref="SizeDefinition"/> dependency property.</summary>
public static readonly DependencyProperty SizeDefinitionProperty = RibbonProperties.SizeDefinitionProperty.AddOwner(typeof(Button));
#endregion
#region KeyTip
/// <inheritdoc />
public string KeyTip
{
get { return (string)this.GetValue(KeyTipProperty); }
set { this.SetValue(KeyTipProperty, value); }
}
/// <summary>
/// <see cref="DependencyProperty"/> for <see cref="KeyTip"/>.
/// </summary>
public static readonly DependencyProperty KeyTipProperty = Fluent.KeyTip.KeysProperty.AddOwner(typeof(Button));
#endregion
#region Header
/// <inheritdoc />
public object Header
{
get { return this.GetValue(HeaderProperty); }
set { this.SetValue(HeaderProperty, value); }
}
/// <summary>Identifies the <see cref="Header"/> dependency property.</summary>
public static readonly DependencyProperty HeaderProperty = RibbonControl.HeaderProperty.AddOwner(typeof(Button), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region Icon
/// <inheritdoc />
public object Icon
{
get { return this.GetValue(IconProperty); }
set { this.SetValue(IconProperty, value); }
}
/// <summary>Identifies the <see cref="Icon"/> dependency property.</summary>
public static readonly DependencyProperty IconProperty = RibbonControl.IconProperty.AddOwner(typeof(Button), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region LargeIcon
/// <inheritdoc />
public object LargeIcon
{
get { return this.GetValue(LargeIconProperty); }
set { this.SetValue(LargeIconProperty, value); }
}
/// <summary>Identifies the <see cref="LargeIcon"/> dependency property.</summary>
public static readonly DependencyProperty LargeIconProperty = LargeIconProviderProperties.LargeIconProperty.AddOwner(typeof(Button), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region IsDefinitive
/// <summary>
/// Gets or sets whether ribbon control click must close backstage
/// </summary>
public bool IsDefinitive
{
get { return (bool)this.GetValue(IsDefinitiveProperty); }
set { this.SetValue(IsDefinitiveProperty, value); }
}
/// <summary>Identifies the <see cref="IsDefinitive"/> dependency property.</summary>
public static readonly DependencyProperty IsDefinitiveProperty =
DependencyProperty.Register(nameof(IsDefinitive), typeof(bool), typeof(Button), new PropertyMetadata(BooleanBoxes.TrueBox));
#endregion
#endregion
#region Constructors
/// <summary>
/// Static constructor
/// </summary>
static Button()
{
var type = typeof(Button);
DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(type));
ContextMenuService.Attach(type);
ToolTipService.Attach(type);
}
/// <summary>
/// Default constructor
/// </summary>
public Button()
{
ContextMenuService.Coerce(this);
}
#endregion
#region Overrides
/// <inheritdoc />
protected override void OnClick()
{
// Close popup on click
if (this.IsDefinitive)
{
PopupService.RaiseDismissPopupEvent(this, DismissPopupMode.Always);
}
base.OnClick();
}
#endregion
#region Quick Access Item Creating
/// <inheritdoc />
public virtual FrameworkElement CreateQuickAccessItem()
{
var button = new Button();
button.Click += (sender, e) => this.RaiseEvent(e);
RibbonControl.BindQuickAccessItem(this, button);
return button;
}
/// <inheritdoc />
public bool CanAddToQuickAccessToolBar
{
get { return (bool)this.GetValue(CanAddToQuickAccessToolBarProperty); }
set { this.SetValue(CanAddToQuickAccessToolBarProperty, value); }
}
/// <summary>Identifies the <see cref="CanAddToQuickAccessToolBar"/> dependency property.</summary>
public static readonly DependencyProperty CanAddToQuickAccessToolBarProperty = RibbonControl.CanAddToQuickAccessToolBarProperty.AddOwner(typeof(Button), new PropertyMetadata(BooleanBoxes.TrueBox, RibbonControl.OnCanAddToQuickAccessToolBarChanged));
#endregion
#region Implementation of IKeyTipedControl
/// <inheritdoc />
public KeyTipPressedResult OnKeyTipPressed()
{
this.OnClick();
return KeyTipPressedResult.Empty;
}
/// <inheritdoc />
public void OnKeyTipBack()
{
}
#endregion
/// <inheritdoc />
void ILogicalChildSupport.AddLogicalChild(object child)
{
this.AddLogicalChild(child);
}
/// <inheritdoc />
void ILogicalChildSupport.RemoveLogicalChild(object child)
{
this.RemoveLogicalChild(child);
}
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.Icon != null)
{
yield return this.Icon;
}
if (this.LargeIcon != null)
{
yield return this.LargeIcon;
}
if (this.Header != null)
{
yield return this.Header;
}
}
}
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() => new Fluent.Automation.Peers.RibbonButtonAutomationPeer(this);
}
}

View File

@@ -0,0 +1,218 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Collections;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Data;
using System.Windows.Markup;
using Fluent.Helpers;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents Fluent UI specific CheckBox
/// </summary>
[ContentProperty(nameof(Header))]
public class CheckBox : System.Windows.Controls.CheckBox, IRibbonControl, IQuickAccessItemProvider, ILargeIconProvider
{
#region Properties
#region Size
/// <inheritdoc />
public RibbonControlSize Size
{
get { return (RibbonControlSize)this.GetValue(SizeProperty); }
set { this.SetValue(SizeProperty, value); }
}
/// <summary>Identifies the <see cref="Size"/> dependency property.</summary>
public static readonly DependencyProperty SizeProperty = RibbonProperties.SizeProperty.AddOwner(typeof(CheckBox));
#endregion
#region SizeDefinition
/// <inheritdoc />
public RibbonControlSizeDefinition SizeDefinition
{
get { return (RibbonControlSizeDefinition)this.GetValue(SizeDefinitionProperty); }
set { this.SetValue(SizeDefinitionProperty, value); }
}
/// <summary>Identifies the <see cref="SizeDefinition"/> dependency property.</summary>
public static readonly DependencyProperty SizeDefinitionProperty = RibbonProperties.SizeDefinitionProperty.AddOwner(typeof(CheckBox));
#endregion
#region KeyTip
/// <inheritdoc />
public string KeyTip
{
get { return (string)this.GetValue(KeyTipProperty); }
set { this.SetValue(KeyTipProperty, value); }
}
/// <summary>
/// Using a DependencyProperty as the backing store for Keys.
/// This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty KeyTipProperty = Fluent.KeyTip.KeysProperty.AddOwner(typeof(CheckBox));
#endregion
#region Header
/// <inheritdoc />
public object Header
{
get { return this.GetValue(HeaderProperty); }
set { this.SetValue(HeaderProperty, value); }
}
/// <summary>Identifies the <see cref="Header"/> dependency property.</summary>
public static readonly DependencyProperty HeaderProperty = RibbonControl.HeaderProperty.AddOwner(typeof(CheckBox), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region Icon
/// <inheritdoc />
public object Icon
{
get { return this.GetValue(IconProperty); }
set { this.SetValue(IconProperty, value); }
}
/// <summary>Identifies the <see cref="Icon"/> dependency property.</summary>
public static readonly DependencyProperty IconProperty = RibbonControl.IconProperty.AddOwner(typeof(CheckBox), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region LargeIcon
/// <inheritdoc />
public object LargeIcon
{
get { return this.GetValue(LargeIconProperty); }
set { this.SetValue(LargeIconProperty, value); }
}
/// <summary>Identifies the <see cref="LargeIcon"/> dependency property.</summary>
public static readonly DependencyProperty LargeIconProperty = LargeIconProviderProperties.LargeIconProperty.AddOwner(typeof(CheckBox), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#endregion
#region Constructors
/// <summary>
/// Static constructor
/// </summary>
static CheckBox()
{
var type = typeof(CheckBox);
DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(type));
ContextMenuService.Attach(type);
ToolTipService.Attach(type);
}
/// <summary>
/// Default constructor
/// </summary>
public CheckBox()
{
ContextMenuService.Coerce(this);
}
#endregion
#region Quick Access Item Creating
/// <inheritdoc />
public virtual FrameworkElement CreateQuickAccessItem()
{
var button = new CheckBox();
RibbonControl.Bind(this, button, nameof(this.IsChecked), IsCheckedProperty, BindingMode.TwoWay);
button.Click += (sender, e) => this.RaiseEvent(e);
RibbonControl.BindQuickAccessItem(this, button);
return button;
}
/// <inheritdoc />
public bool CanAddToQuickAccessToolBar
{
get { return (bool)this.GetValue(CanAddToQuickAccessToolBarProperty); }
set { this.SetValue(CanAddToQuickAccessToolBarProperty, value); }
}
/// <summary>Identifies the <see cref="CanAddToQuickAccessToolBar"/> dependency property.</summary>
public static readonly DependencyProperty CanAddToQuickAccessToolBarProperty = RibbonControl.CanAddToQuickAccessToolBarProperty.AddOwner(typeof(CheckBox), new PropertyMetadata(BooleanBoxes.TrueBox, RibbonControl.OnCanAddToQuickAccessToolBarChanged));
#endregion
#region Implementation of IKeyTipedControl
/// <inheritdoc />
public KeyTipPressedResult OnKeyTipPressed()
{
this.OnClick();
return KeyTipPressedResult.Empty;
}
/// <inheritdoc />
public void OnKeyTipBack()
{
}
#endregion
/// <inheritdoc />
void ILogicalChildSupport.AddLogicalChild(object child)
{
this.AddLogicalChild(child);
}
/// <inheritdoc />
void ILogicalChildSupport.RemoveLogicalChild(object child)
{
this.RemoveLogicalChild(child);
}
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.Icon != null)
{
yield return this.Icon;
}
if (this.LargeIcon != null)
{
yield return this.LargeIcon;
}
if (this.Header != null)
{
yield return this.Header;
}
}
}
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() => new Fluent.Automation.Peers.RibbonCheckBoxAutomationPeer(this);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,858 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections;
using System.Windows;
using System.Windows.Automation.Peers;
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.Extensions;
using Fluent.Helpers;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents custom Fluent UI ComboBox
/// </summary>
[TemplatePart(Name = "PART_ResizeBothThumb", Type = typeof(Thumb))]
[TemplatePart(Name = "PART_ResizeVerticalThumb", Type = typeof(Thumb))]
[TemplatePart(Name = "PART_MenuPanel", Type = typeof(Panel))]
[TemplatePart(Name = "PART_SelectedImage", Type = typeof(Image))]
[TemplatePart(Name = "PART_ContentSite", Type = typeof(ContentPresenter))]
[TemplatePart(Name = "PART_ContentBorder", Type = typeof(Border))]
[TemplatePart(Name = "PART_ScrollViewer", Type = typeof(ScrollViewer))]
[TemplatePart(Name = "PART_DropDownBorder", Type = typeof(Border))]
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;
private IInputElement focusedElement;
private Panel menuPanel;
private Border dropDownBorder;
private Border contentBorder;
private ContentPresenter contentSite;
// Freezed image (created during snapping)
private Image snappedImage;
// Is visual currently snapped
private bool isSnapped;
private ScrollViewer scrollViewer;
private bool canSizeY;
#endregion
#region Properties
#region Size
/// <inheritdoc />
public RibbonControlSize Size
{
get { return (RibbonControlSize)this.GetValue(SizeProperty); }
set { this.SetValue(SizeProperty, value); }
}
/// <summary>Identifies the <see cref="Size"/> dependency property.</summary>
public static readonly DependencyProperty SizeProperty = RibbonProperties.SizeProperty.AddOwner(typeof(ComboBox));
#endregion
#region SizeDefinition
/// <inheritdoc />
public RibbonControlSizeDefinition SizeDefinition
{
get { return (RibbonControlSizeDefinition)this.GetValue(SizeDefinitionProperty); }
set { this.SetValue(SizeDefinitionProperty, value); }
}
/// <summary>Identifies the <see cref="SizeDefinition"/> dependency property.</summary>
public static readonly DependencyProperty SizeDefinitionProperty = RibbonProperties.SizeDefinitionProperty.AddOwner(typeof(ComboBox));
#endregion
#region KeyTip
/// <inheritdoc />
public string KeyTip
{
get { return (string)this.GetValue(KeyTipProperty); }
set { this.SetValue(KeyTipProperty, value); }
}
/// <summary>
/// Using a DependencyProperty as the backing store for Keys.
/// This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty KeyTipProperty = Fluent.KeyTip.KeysProperty.AddOwner(typeof(ComboBox));
#endregion
/// <inheritdoc />
public Popup DropDownPopup { get; private set; }
/// <inheritdoc />
public bool IsContextMenuOpened { get; set; }
#region Header
/// <inheritdoc />
public object Header
{
get { return this.GetValue(HeaderProperty); }
set { this.SetValue(HeaderProperty, value); }
}
/// <summary>Identifies the <see cref="Header"/> dependency property.</summary>
public static readonly DependencyProperty HeaderProperty = RibbonControl.HeaderProperty.AddOwner(typeof(ComboBox), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region Icon
/// <inheritdoc />
public object Icon
{
get { return this.GetValue(IconProperty); }
set { this.SetValue(IconProperty, value); }
}
/// <summary>Identifies the <see cref="Icon"/> dependency property.</summary>
public static readonly DependencyProperty IconProperty = RibbonControl.IconProperty.AddOwner(typeof(ComboBox), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region Menu
/// <summary>
/// Gets or sets menu to show in combo box bottom
/// </summary>
public RibbonMenu Menu
{
get { return (RibbonMenu)this.GetValue(MenuProperty); }
set { this.SetValue(MenuProperty, value); }
}
/// <summary>Identifies the <see cref="Menu"/> dependency property.</summary>
public static readonly DependencyProperty MenuProperty =
DependencyProperty.Register(nameof(Menu), typeof(RibbonMenu), typeof(ComboBox), new PropertyMetadata());
#endregion
#region InputWidth
/// <summary>
/// Gets or sets width of the value input part of combobox
/// </summary>
public double InputWidth
{
get { return (double)this.GetValue(InputWidthProperty); }
set { this.SetValue(InputWidthProperty, value); }
}
/// <summary>Identifies the <see cref="InputWidth"/> dependency property.</summary>
public static readonly DependencyProperty InputWidthProperty =
DependencyProperty.Register(nameof(InputWidth), typeof(double), typeof(ComboBox), new PropertyMetadata(DoubleBoxes.NaN));
#endregion
#region ResizeMode
/// <summary>
/// Gets or sets context menu resize mode
/// </summary>
public ContextMenuResizeMode ResizeMode
{
get { return (ContextMenuResizeMode)this.GetValue(ResizeModeProperty); }
set { this.SetValue(ResizeModeProperty, value); }
}
/// <summary>Identifies the <see cref="ResizeMode"/> dependency property.</summary>
public static readonly DependencyProperty ResizeModeProperty =
DependencyProperty.Register(nameof(ResizeMode), typeof(ContextMenuResizeMode), typeof(ComboBox), new PropertyMetadata(ContextMenuResizeMode.None));
#endregion
#region Snapping
/// <summary>
/// Snaps / Unsnaps the Visual
/// (remove visuals and substitute with freezed image)
/// </summary>
private bool IsSnapped
{
get { return this.isSnapped; }
set
{
if (value == this.isSnapped)
{
return;
}
if (this.snappedImage is 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
/// <summary>
/// Gets or sets initial dropdown height
/// </summary>
public double DropDownHeight
{
get { return (double)this.GetValue(DropDownHeightProperty); }
set { this.SetValue(DropDownHeightProperty, value); }
}
/// <summary>Identifies the <see cref="DropDownHeight"/> dependency property.</summary>
public static readonly DependencyProperty DropDownHeightProperty =
DependencyProperty.Register(nameof(DropDownHeight), typeof(double), typeof(ComboBox), new PropertyMetadata(DoubleBoxes.NaN));
#endregion
#endregion
#region Constructors
/// <summary>
/// Static constructor
/// </summary>
static ComboBox()
{
var type = typeof(ComboBox);
DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(type));
SelectedItemProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(OnSelectedItemChanged, CoerceSelectedItem));
ToolTipService.Attach(type);
PopupService.Attach(type);
ContextMenuService.Attach(type);
}
private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var combo = (ComboBox)d;
if (combo.isQuickAccessOpened == false
&& combo.isQuickAccessFocused == false
&& combo.quickAccessCombo != null)
{
combo.UpdateQuickAccessCombo();
}
}
private static object CoerceSelectedItem(DependencyObject d, object basevalue)
{
var combo = (ComboBox)d;
if (combo.isQuickAccessOpened
|| combo.isQuickAccessFocused)
{
return combo.selectedItem;
}
return basevalue;
}
/// <summary>
/// Default Constructor
/// </summary>
public ComboBox()
{
ContextMenuService.Coerce(this);
}
#endregion
#region QuickAccess
/// <inheritdoc />
public virtual FrameworkElement CreateQuickAccessItem()
{
var combo = new ComboBox();
RibbonControl.BindQuickAccessItem(this, combo);
RibbonControl.Bind(this, combo, nameof(this.ActualWidth), MaxWidthProperty, BindingMode.OneWay);
RibbonControl.Bind(this, combo, nameof(this.InputWidth), InputWidthProperty, BindingMode.OneWay);
RibbonControl.Bind(this, combo, nameof(this.IsEditable), IsEditableProperty, BindingMode.OneWay);
RibbonControl.Bind(this, combo, nameof(this.IsReadOnly), IsReadOnlyProperty, BindingMode.OneWay);
RibbonControl.Bind(this, combo, nameof(this.ResizeMode), ResizeModeProperty, BindingMode.OneWay);
RibbonControl.Bind(this, combo, nameof(this.Text), TextProperty, BindingMode.TwoWay);
RibbonControl.Bind(this, combo, nameof(this.DisplayMemberPath), DisplayMemberPathProperty, BindingMode.OneWay);
RibbonControl.Bind(this, combo, nameof(this.GroupStyleSelector), GroupStyleSelectorProperty, BindingMode.OneWay);
RibbonControl.Bind(this, combo, nameof(this.ItemContainerStyle), ItemContainerStyleProperty, BindingMode.OneWay);
RibbonControl.Bind(this, combo, nameof(this.ItemsPanel), ItemsPanelProperty, BindingMode.OneWay);
RibbonControl.Bind(this, combo, nameof(this.ItemStringFormat), ItemStringFormatProperty, BindingMode.OneWay);
RibbonControl.Bind(this, combo, nameof(this.ItemTemplate), ItemTemplateProperty, BindingMode.OneWay);
RibbonControl.Bind(this, combo, nameof(this.SelectedValuePath), SelectedValuePathProperty, BindingMode.OneWay);
RibbonControl.Bind(this, combo, nameof(this.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 == false)
{
this.RunInDispatcherAsync(this.FreezeAnBringSelectedItemIntoView);
}
}
private void FreezeAnBringSelectedItemIntoView()
{
this.Freeze();
this.RunInDispatcherAsync(this.BringSelectedItemIntoView, DispatcherPriority.Input);
}
private void BringSelectedItemIntoView()
{
if (this.quickAccessCombo.SelectedItem is null)
{
return;
}
var containerFromItem = this.quickAccessCombo.ItemContainerGenerator.ContainerOrContainerContentFromItem<FrameworkElement>(this.quickAccessCombo.SelectedItem);
containerFromItem?.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;
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.RunInDispatcherAsync(() =>
{
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;
}, DispatcherPriority.ApplicationIdle);
}
}
private void OnFirstLoaded(object sender, RoutedEventArgs e)
{
this.Loaded -= this.OnFirstLoaded;
this.UpdateQuickAccessCombo();
}
/// <inheritdoc />
public bool CanAddToQuickAccessToolBar
{
get { return (bool)this.GetValue(CanAddToQuickAccessToolBarProperty); }
set { this.SetValue(CanAddToQuickAccessToolBarProperty, value); }
}
/// <summary>Identifies the <see cref="CanAddToQuickAccessToolBar"/> dependency property.</summary>
public static readonly DependencyProperty CanAddToQuickAccessToolBarProperty = RibbonControl.CanAddToQuickAccessToolBarProperty.AddOwner(typeof(ComboBox), new PropertyMetadata(BooleanBoxes.TrueBox, RibbonControl.OnCanAddToQuickAccessToolBarChanged));
#endregion
#region Overrides
/// <inheritdoc />
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.scrollViewer = this.GetTemplateChild("PART_ScrollViewer") as ScrollViewer;
this.dropDownBorder = this.GetTemplateChild("PART_DropDownBorder") as Border;
base.OnApplyTemplate();
}
/// <inheritdoc />
protected override void OnDropDownOpened(EventArgs e)
{
base.OnDropDownOpened(e);
Mouse.Capture(this, CaptureMode.SubTree);
if (this.SelectedItem != null)
{
Keyboard.Focus(this.ItemContainerGenerator.ContainerOrContainerContentFromItem<IInputElement>(this.SelectedItem));
}
this.focusedElement = Keyboard.FocusedElement;
if (this.focusedElement != null)
{
this.focusedElement.LostKeyboardFocus += this.OnFocusedElementLostKeyboardFocus;
}
this.canSizeY = true;
this.scrollViewer.Width = double.NaN;
this.scrollViewer.Height = double.NaN;
var popupChild = this.DropDownPopup.Child as FrameworkElement;
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;
}
popupChild?.UpdateLayout();
}
/// <inheritdoc />
protected override void OnDropDownClosed(EventArgs e)
{
base.OnDropDownClosed(e);
if (ReferenceEquals(Mouse.Captured, this))
{
Mouse.Capture(null);
}
if (this.focusedElement != null)
{
this.focusedElement.LostKeyboardFocus -= this.OnFocusedElementLostKeyboardFocus;
}
this.focusedElement = null;
this.scrollViewer.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.ItemFromContainerOrContainerContent((DependencyObject)Keyboard.FocusedElement)))
{
this.SelectedItem = this.ItemContainerGenerator.ItemFromContainerOrContainerContent((DependencyObject)Keyboard.FocusedElement);
}
}
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
protected override void OnKeyDown(KeyEventArgs e)
{
var baseKeyDownCalled = false;
if ((this.Menu != null && this.Menu.IsKeyboardFocusWithin == false)
&& e.Key != Key.Tab)
{
base.OnKeyDown(e);
baseKeyDownCalled = true;
if (e.Handled)
{
return;
}
}
if (this.Menu != null
&& this.Menu.Items.IsEmpty == false)
{
if (e.Key == Key.Tab)
{
if (this.Menu.IsKeyboardFocusWithin)
{
Keyboard.Focus(this.ItemContainerGenerator.ContainerOrContainerContentFromIndex<IInputElement>(0));
}
else
{
Keyboard.Focus(this.Menu.ItemContainerGenerator.ContainerOrContainerContentFromIndex<IInputElement>(0));
}
e.Handled = true;
return;
}
if (this.Menu.Items.Contains(this.Menu.ItemContainerGenerator.ItemFromContainerOrContainerContent((DependencyObject)Keyboard.FocusedElement)))
{
if (e.Key == Key.Down)
{
var indexOfMenuSelectedItem = this.Menu.ItemContainerGenerator.IndexFromContainer((DependencyObject)Keyboard.FocusedElement);
if (indexOfMenuSelectedItem != this.Menu.Items.Count - 1)
{
Keyboard.Focus(this.Menu.ItemContainerGenerator.ContainerOrContainerContentFromIndex<IInputElement>(indexOfMenuSelectedItem + 1));
}
else
{
Keyboard.Focus(this.Menu.ItemContainerGenerator.ContainerOrContainerContentFromIndex<IInputElement>(0));
}
e.Handled = true;
return;
}
if (e.Key == Key.Up)
{
var indexOfMenuSelectedItem = this.Menu.ItemContainerGenerator.IndexFromContainer((DependencyObject)Keyboard.FocusedElement);
if (indexOfMenuSelectedItem != 0)
{
Keyboard.Focus(this.Menu.ItemContainerGenerator.ContainerOrContainerContentFromIndex<IInputElement>(indexOfMenuSelectedItem - 1));
}
else
{
Keyboard.Focus(this.Menu.ItemContainerGenerator.ContainerOrContainerContentFromIndex<IInputElement>(this.Menu.Items.Count - 1));
}
e.Handled = true;
return;
}
}
}
if (baseKeyDownCalled == false
&& e.Handled == false)
{
base.OnKeyDown(e);
}
}
#endregion
#region Methods
/// <inheritdoc />
public virtual KeyTipPressedResult OnKeyTipPressed()
{
// Edge case: Whole dropdown content is disabled
if (this.IsKeyboardFocusWithin == false)
{
Keyboard.Focus(this);
}
if (this.IsEditable == false)
{
this.IsDropDownOpen = true;
return new KeyTipPressedResult(true, true);
}
return new KeyTipPressedResult(true, false);
}
/// <inheritdoc />
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.scrollViewer.Width))
{
this.scrollViewer.Width = this.scrollViewer.ActualWidth;
}
var monitorRight = RibbonControl.GetControlMonitor(this).Right;
var popupChild = this.DropDownPopup.Child as FrameworkElement;
if (popupChild is null)
{
return;
}
var delta = monitorRight - this.PointToScreen(default).X - popupChild.ActualWidth - e.HorizontalChange;
var deltaX = popupChild.ActualWidth - this.scrollViewer.ActualWidth;
var deltaBorders = this.dropDownBorder.ActualWidth - this.scrollViewer.ActualWidth;
if (delta > 0)
{
this.scrollViewer.Width = Math.Max(0, Math.Max(this.scrollViewer.Width + e.HorizontalChange, this.ActualWidth - deltaBorders));
}
else
{
this.scrollViewer.Width = Math.Max(0, Math.Max(monitorRight - this.PointToScreen(default).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 == false)
{
return;
}
if (double.IsNaN(this.scrollViewer.Height))
{
this.scrollViewer.Height = this.scrollViewer.ActualHeight;
}
this.scrollViewer.Height = Math.Max(0, this.scrollViewer.Height + e.VerticalChange);
}
#endregion
/// <inheritdoc />
void ILogicalChildSupport.AddLogicalChild(object child)
{
this.AddLogicalChild(child);
}
/// <inheritdoc />
void ILogicalChildSupport.RemoveLogicalChild(object child)
{
this.RemoveLogicalChild(child);
}
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.Icon != null)
{
yield return this.Icon;
}
if (this.Header != null)
{
yield return this.Header;
}
}
}
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() => new Fluent.Automation.Peers.RibbonComboBoxAutomationPeer(this);
}
}

View File

@@ -0,0 +1,151 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Windows;
using System.Windows.Controls.Primitives;
/// <summary>
/// Represents context menu resize mode
/// </summary>
public enum ContextMenuResizeMode
{
/// <summary>
/// Context menu can not be resized
/// </summary>
None = 0,
/// <summary>
/// Context menu can be only resized vertically
/// </summary>
Vertical,
/// <summary>
/// Context menu can be resized vertically and horizontally
/// </summary>
Both
}
/// <summary>
/// Represents a pop-up menu that enables a control
/// to expose functionality that is specific to the context of the control
/// </summary>
[TemplatePart(Name = "PART_ResizeVerticalThumb", Type = typeof(Thumb))]
[TemplatePart(Name = "PART_ResizeBothThumb", Type = typeof(Thumb))]
public class ContextMenu : System.Windows.Controls.ContextMenu
{
#region Fields
// Thumb to resize in both directions
private Thumb resizeBothThumb;
// Thumb to resize vertical
private Thumb resizeVerticalThumb;
#endregion
#region Properties
/// <summary>
/// Gets or sets context menu resize mode
/// </summary>
public ContextMenuResizeMode ResizeMode
{
get { return (ContextMenuResizeMode)this.GetValue(ResizeModeProperty); }
set { this.SetValue(ResizeModeProperty, value); }
}
/// <summary>Identifies the <see cref="ResizeMode"/> dependency property.</summary>
public static readonly DependencyProperty ResizeModeProperty =
DependencyProperty.Register(nameof(ResizeMode), typeof(ContextMenuResizeMode),
typeof(ContextMenu), new PropertyMetadata(ContextMenuResizeMode.None));
#endregion
#region Constructor
/// <summary>
/// Static constructor
/// </summary>]
static ContextMenu()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ContextMenu), new FrameworkPropertyMetadata(typeof(ContextMenu)));
FocusVisualStyleProperty.OverrideMetadata(typeof(ContextMenu), new FrameworkPropertyMetadata());
}
#endregion
#region Overrides
/// <inheritdoc />
public override void OnApplyTemplate()
{
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;
}
}
/// <inheritdoc />
protected override DependencyObject GetContainerForItemOverride()
{
return new MenuItem();
}
/// <inheritdoc />
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is FrameworkElement;
}
#endregion
#region Private methods
// Handles resize both drag
private void OnResizeBothDelta(object sender, DragDeltaEventArgs e)
{
if (double.IsNaN(this.Width))
{
this.Width = this.ActualWidth;
}
if (double.IsNaN(this.Height))
{
this.Height = this.ActualHeight;
}
this.Width = Math.Max(this.MinWidth, this.Width + e.HorizontalChange);
this.Height = Math.Max(this.MinHeight, this.Height + e.VerticalChange);
}
// Handles resize vertical drag
private void OnResizeVerticalDelta(object sender, DragDeltaEventArgs e)
{
if (double.IsNaN(this.Height))
{
this.Height = this.ActualHeight;
}
this.Height = Math.Max(this.MinHeight, this.Height + e.VerticalChange);
}
#endregion
}
}

View File

@@ -0,0 +1,862 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Threading;
using Fluent.Extensions;
using Fluent.Helpers;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents drop down button
/// </summary>
[ContentProperty(nameof(Items))]
[TemplatePart(Name = "PART_ResizeVerticalThumb", Type = typeof(Thumb))]
[TemplatePart(Name = "PART_ResizeBothThumb", Type = typeof(Thumb))]
[TemplatePart(Name = "PART_ScrollViewer", Type = typeof(ScrollViewer))]
[TemplatePart(Name = "PART_Popup", Type = typeof(Popup))]
[TemplatePart(Name = "PART_ButtonBorder", Type = typeof(UIElement))]
public class DropDownButton : ItemsControl, IQuickAccessItemProvider, IRibbonControl, IDropDownControl, ILargeIconProvider
{
#region Fields
// Thumb to resize in both directions
private Thumb resizeBothThumb;
// Thumb to resize vertical
private Thumb resizeVerticalThumb;
private ScrollViewer scrollViewer;
private UIElement buttonBorder;
private readonly Stack<WeakReference> openMenuItems = new Stack<WeakReference>();
#endregion
#region Properties
#region Size
/// <summary>
/// Gets or sets Size for the element.
/// </summary>
public RibbonControlSize Size
{
get { return (RibbonControlSize)this.GetValue(SizeProperty); }
set { this.SetValue(SizeProperty, value); }
}
/// <summary>Identifies the <see cref="Size"/> dependency property.</summary>
public static readonly DependencyProperty SizeProperty = RibbonProperties.SizeProperty.AddOwner(typeof(DropDownButton));
#endregion
#region SizeDefinition
/// <inheritdoc />
public RibbonControlSizeDefinition SizeDefinition
{
get { return (RibbonControlSizeDefinition)this.GetValue(SizeDefinitionProperty); }
set { this.SetValue(SizeDefinitionProperty, value); }
}
/// <summary>Identifies the <see cref="SizeDefinition"/> dependency property.</summary>
public static readonly DependencyProperty SizeDefinitionProperty = RibbonProperties.SizeDefinitionProperty.AddOwner(typeof(DropDownButton));
#endregion
#region KeyTip
/// <inheritdoc />
public string KeyTip
{
get { return (string)this.GetValue(KeyTipProperty); }
set { this.SetValue(KeyTipProperty, value); }
}
/// <summary>
/// Using a DependencyProperty as the backing store for Keys.
/// This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty KeyTipProperty = Fluent.KeyTip.KeysProperty.AddOwner(typeof(DropDownButton));
#endregion
/// <inheritdoc />
public Popup DropDownPopup { get; private set; }
/// <inheritdoc />
public bool IsContextMenuOpened { get; set; }
#region Header
/// <inheritdoc />
public object Header
{
get { return this.GetValue(HeaderProperty); }
set { this.SetValue(HeaderProperty, value); }
}
/// <summary>Identifies the <see cref="Header"/> dependency property.</summary>
public static readonly DependencyProperty HeaderProperty = RibbonControl.HeaderProperty.AddOwner(typeof(DropDownButton), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region Icon
/// <inheritdoc />
public object Icon
{
get { return this.GetValue(IconProperty); }
set { this.SetValue(IconProperty, value); }
}
/// <summary>Identifies the <see cref="Icon"/> dependency property.</summary>
public static readonly DependencyProperty IconProperty = RibbonControl.IconProperty.AddOwner(typeof(DropDownButton), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region LargeIcon
/// <inheritdoc />
public object LargeIcon
{
get { return this.GetValue(LargeIconProperty); }
set { this.SetValue(LargeIconProperty, value); }
}
/// <summary>Identifies the <see cref="LargeIcon"/> dependency property.</summary>
public static readonly DependencyProperty LargeIconProperty = LargeIconProviderProperties.LargeIconProperty.AddOwner(typeof(DropDownButton), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region HasTriangle
/// <summary>
/// Gets or sets whether button has triangle
/// </summary>
public bool HasTriangle
{
get { return (bool)this.GetValue(HasTriangleProperty); }
set { this.SetValue(HasTriangleProperty, value); }
}
/// <summary>Identifies the <see cref="HasTriangle"/> dependency property.</summary>
public static readonly DependencyProperty HasTriangleProperty =
DependencyProperty.Register(nameof(HasTriangle), typeof(bool), typeof(DropDownButton), new PropertyMetadata(BooleanBoxes.TrueBox));
#endregion
#region IsDropDownOpen
/// <inheritdoc />
public bool IsDropDownOpen
{
get { return (bool)this.GetValue(IsDropDownOpenProperty); }
set { this.SetValue(IsDropDownOpenProperty, value); }
}
/// <summary>Identifies the <see cref="IsDropDownOpen"/> dependency property.</summary>
public static readonly DependencyProperty IsDropDownOpenProperty =
DependencyProperty.Register(nameof(IsDropDownOpen), typeof(bool), typeof(DropDownButton),
new PropertyMetadata(BooleanBoxes.FalseBox, OnIsDropDownOpenChanged));
#endregion
#region ResizeMode
/// <summary>
/// Gets or sets context menu resize mode
/// </summary>
public ContextMenuResizeMode ResizeMode
{
get { return (ContextMenuResizeMode)this.GetValue(ResizeModeProperty); }
set { this.SetValue(ResizeModeProperty, value); }
}
/// <summary>Identifies the <see cref="ResizeMode"/> dependency property.</summary>
public static readonly DependencyProperty ResizeModeProperty =
DependencyProperty.Register(nameof(ResizeMode), typeof(ContextMenuResizeMode),
typeof(DropDownButton), new PropertyMetadata(ContextMenuResizeMode.None));
#endregion
#region MaxDropDownHeight
/// <summary>
/// Get or sets max height of drop down popup
/// </summary>
public double MaxDropDownHeight
{
get { return (double)this.GetValue(MaxDropDownHeightProperty); }
set { this.SetValue(MaxDropDownHeightProperty, value); }
}
/// <summary>Identifies the <see cref="MaxDropDownHeight"/> dependency property.</summary>
public static readonly DependencyProperty MaxDropDownHeightProperty =
DependencyProperty.Register(nameof(MaxDropDownHeight), typeof(double), typeof(DropDownButton), new PropertyMetadata(SystemParameters.PrimaryScreenHeight / 3.0));
#endregion
#region DropDownHeight
/// <summary>
/// Gets or sets initial dropdown height
/// </summary>
public double DropDownHeight
{
get { return (double)this.GetValue(DropDownHeightProperty); }
set { this.SetValue(DropDownHeightProperty, value); }
}
/// <summary>Identifies the <see cref="DropDownHeight"/> dependency property.</summary>
public static readonly DependencyProperty DropDownHeightProperty =
DependencyProperty.Register(nameof(DropDownHeight), typeof(double), typeof(DropDownButton), new PropertyMetadata(DoubleBoxes.NaN));
#endregion
#region ClosePopupOnMouseDown
/// <summary>
/// Gets or sets whether the popup of this drop down button should automatically be closed on mouse down.
/// </summary>
public bool ClosePopupOnMouseDown
{
get { return (bool)this.GetValue(ClosePopupOnMouseDownProperty); }
set { this.SetValue(ClosePopupOnMouseDownProperty, value); }
}
/// <summary>Identifies the <see cref="ClosePopupOnMouseDown"/> dependency property.</summary>
public static readonly DependencyProperty ClosePopupOnMouseDownProperty =
DependencyProperty.Register(nameof(ClosePopupOnMouseDown), typeof(bool), typeof(DropDownButton), new PropertyMetadata(BooleanBoxes.FalseBox));
#endregion
#region ClosePopupOnMouseDownDelay
/// <summary>
/// Gets or sets the delay in milliseconds to close the popup on mouse down.
/// </summary>
public int ClosePopupOnMouseDownDelay
{
get { return (int)this.GetValue(ClosePopupOnMouseDownDelayProperty); }
set { this.SetValue(ClosePopupOnMouseDownDelayProperty, value); }
}
/// <summary>Identifies the <see cref="ClosePopupOnMouseDownDelay"/> dependency property.</summary>
public static readonly DependencyProperty ClosePopupOnMouseDownDelayProperty =
DependencyProperty.Register(nameof(ClosePopupOnMouseDownDelay), typeof(int), typeof(DropDownButton), new PropertyMetadata(150));
#endregion
#endregion
#region Events
/// <inheritdoc />
public event EventHandler DropDownOpened;
/// <inheritdoc />
public event EventHandler DropDownClosed;
#endregion
#region Initialize
/// <summary>
/// Static constructor
/// </summary>
static DropDownButton()
{
var type = typeof(DropDownButton);
DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(type));
System.Windows.Controls.ToolTipService.IsEnabledProperty.OverrideMetadata(typeof(DropDownButton), new FrameworkPropertyMetadata(null, CoerceToolTipIsEnabled));
KeyboardNavigation.ControlTabNavigationProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(KeyboardNavigationMode.Once));
KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(KeyboardNavigationMode.Cycle));
ToolTipService.Attach(type);
PopupService.Attach(type);
ContextMenuService.Attach(type);
}
/// <summary>
/// Default constructor
/// </summary>
public DropDownButton()
{
ContextMenuService.Coerce(this);
this.Loaded += this.OnLoaded;
this.Unloaded += this.OnUnloaded;
this.AddHandler(System.Windows.Controls.MenuItem.SubmenuOpenedEvent, new RoutedEventHandler(this.OnSubmenuOpened));
this.AddHandler(System.Windows.Controls.MenuItem.SubmenuClosedEvent, new RoutedEventHandler(this.OnSubmenuClosed));
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
this.SubscribeEvents();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
this.UnSubscribeEvents();
}
private void SubscribeEvents()
{
// Always unsubscribe events to ensure we don't subscribe twice
this.UnSubscribeEvents();
if (this.resizeVerticalThumb != null)
{
this.resizeVerticalThumb.DragDelta += this.OnResizeVerticalDelta;
}
if (this.resizeBothThumb != null)
{
this.resizeBothThumb.DragDelta += this.OnResizeBothDelta;
}
if (this.buttonBorder != null)
{
this.buttonBorder.MouseLeftButtonDown += this.HandleButtonBorderMouseLeftButtonDown;
}
if (this.DropDownPopup != null)
{
this.DropDownPopup.KeyDown += this.OnDropDownPopupKeyDown;
this.DropDownPopup.AddHandler(MouseDownEvent, new RoutedEventHandler(this.OnDropDownPopupMouseDown), true);
}
}
private void UnSubscribeEvents()
{
if (this.resizeVerticalThumb != null)
{
this.resizeVerticalThumb.DragDelta -= this.OnResizeVerticalDelta;
}
if (this.resizeBothThumb != null)
{
this.resizeBothThumb.DragDelta -= this.OnResizeBothDelta;
}
if (this.buttonBorder != null)
{
this.buttonBorder.MouseLeftButtonDown -= this.HandleButtonBorderMouseLeftButtonDown;
}
if (this.DropDownPopup != null)
{
this.DropDownPopup.KeyDown -= this.OnDropDownPopupKeyDown;
this.DropDownPopup.RemoveHandler(MouseDownEvent, new RoutedEventHandler(this.OnDropDownPopupMouseDown));
}
}
/// <inheritdoc />
public override void OnApplyTemplate()
{
this.UnSubscribeEvents();
this.DropDownPopup = this.Template.FindName("PART_Popup", this) as Popup;
if (this.DropDownPopup != null)
{
KeyboardNavigation.SetDirectionalNavigation(this.DropDownPopup, KeyboardNavigationMode.Cycle);
KeyboardNavigation.SetTabNavigation(this.DropDownPopup, KeyboardNavigationMode.Continue);
}
this.resizeVerticalThumb = this.Template.FindName("PART_ResizeVerticalThumb", this) as Thumb;
this.resizeBothThumb = this.Template.FindName("PART_ResizeBothThumb", this) as Thumb;
this.scrollViewer = this.Template.FindName("PART_ScrollViewer", this) as ScrollViewer;
this.buttonBorder = this.Template.FindName("PART_ButtonBorder", this) as UIElement;
base.OnApplyTemplate();
this.SubscribeEvents();
}
#endregion
#region Overrides
/// <inheritdoc />
protected override DependencyObject GetContainerForItemOverride()
{
return new MenuItem();
}
/// <inheritdoc />
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is FrameworkElement;
}
private void OnDropDownPopupKeyDown(object sender, KeyEventArgs e)
{
if (e.Handled)
{
return;
}
var handled = false;
switch (e.Key)
{
case Key.Escape:
this.IsDropDownOpen = false;
handled = true;
break;
}
if (handled)
{
e.Handled = true;
}
}
private void OnDropDownPopupMouseDown(object sender, RoutedEventArgs e)
{
if (this.ClosePopupOnMouseDown
&& this.resizeBothThumb.IsMouseOver == false
&& this.resizeVerticalThumb.IsMouseOver == false)
{
// Note: get outside thread to prevent exceptions (it's a dependency property after all)
var timespan = this.ClosePopupOnMouseDownDelay;
// Ugly workaround, but use a timer to allow routed event to continue
Task.Factory.StartNew(async () =>
{
await Task.Delay(timespan);
this.RunInDispatcherAsync(() => this.IsDropDownOpen = false);
});
}
}
private void HandleButtonBorderMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
this.Focus();
this.IsDropDownOpen = !this.IsDropDownOpen;
}
/// <inheritdoc />
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Handled)
{
return;
}
var handled = false;
switch (e.Key)
{
case Key.Down:
if (this.HasItems
&& this.IsDropDownOpen == false) // Only handle this for initial navigation. Further navigation is handled by the dropdown itself
{
this.IsDropDownOpen = true;
var container = this.ItemContainerGenerator.ContainerFromIndex(0);
NavigateToContainer(container, FocusNavigationDirection.Down);
handled = true;
}
break;
case Key.Up:
if (this.HasItems
&& this.IsDropDownOpen == false) // Only handle this for initial navigation. Further navigation is handled by the dropdown itself
{
this.IsDropDownOpen = true;
var container = this.ItemContainerGenerator.ContainerFromIndex(this.Items.Count - 1);
NavigateToContainer(container, FocusNavigationDirection.Up);
handled = true;
}
break;
case Key.Escape:
if (this.IsDropDownOpen)
{
this.IsDropDownOpen = false;
handled = true;
}
break;
case Key.Enter:
case Key.Space:
this.IsDropDownOpen = !this.IsDropDownOpen;
handled = true;
break;
}
if (handled)
{
e.Handled = true;
}
base.OnKeyDown(e);
}
private static void NavigateToContainer(DependencyObject container, FocusNavigationDirection focusNavigationDirection = FocusNavigationDirection.Down)
{
var element = container as FrameworkElement;
if (element is null)
{
return;
}
if (element.Focusable)
{
Keyboard.Focus(element);
}
else
{
element.MoveFocus(new TraversalRequest(focusNavigationDirection));
}
}
private static object CoerceToolTipIsEnabled(DependencyObject d, object basevalue)
{
var control = (DropDownButton)d;
return !control.IsDropDownOpen;
}
#endregion
#region Methods
/// <inheritdoc />
public virtual KeyTipPressedResult OnKeyTipPressed()
{
this.IsDropDownOpen = true;
return new KeyTipPressedResult(true, true);
}
/// <inheritdoc />
public void OnKeyTipBack()
{
this.IsDropDownOpen = false;
}
#endregion
#region Private methods
// Handles resize both drag
private void OnResizeBothDelta(object sender, DragDeltaEventArgs e)
{
if (this.scrollViewer is null)
{
return;
}
if (double.IsNaN(this.scrollViewer.Width))
{
this.scrollViewer.Width = this.scrollViewer.ActualWidth;
}
if (double.IsNaN(this.scrollViewer.Height))
{
this.scrollViewer.Height = this.scrollViewer.ActualHeight;
}
this.scrollViewer.Width = Math.Max(this.ActualWidth, this.scrollViewer.Width + e.HorizontalChange);
this.scrollViewer.Height = Math.Min(Math.Max(this.ActualHeight, this.scrollViewer.Height + e.VerticalChange), this.MaxDropDownHeight);
}
// Handles resize vertical drag
private void OnResizeVerticalDelta(object sender, DragDeltaEventArgs e)
{
if (this.scrollViewer is null)
{
return;
}
if (double.IsNaN(this.scrollViewer.Height))
{
this.scrollViewer.Height = this.scrollViewer.ActualHeight;
}
this.scrollViewer.Height = Math.Min(Math.Max(this.ActualHeight, this.scrollViewer.Height + e.VerticalChange), this.MaxDropDownHeight);
}
private static void OnIsDropDownOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (DropDownButton)d;
var oldValue = (bool)e.OldValue;
var newValue = (bool)e.NewValue;
control.OnIsDropDownOpenChanged(newValue);
(UIElementAutomationPeer.FromElement(control) as Fluent.Automation.Peers.RibbonDropDownButtonAutomationPeer)?.RaiseExpandCollapseAutomationEvent(oldValue, newValue);
}
private void OnIsDropDownOpenChanged(bool newValue)
{
this.SetValue(System.Windows.Controls.ToolTipService.IsEnabledProperty, !newValue);
Debug.WriteLine($"{this.Header} IsDropDownOpen: {newValue}");
if (newValue)
{
Mouse.Capture(this, CaptureMode.SubTree);
Keyboard.Focus(this.DropDownPopup);
this.RunInDispatcherAsync(
() =>
{
var container = this.ItemContainerGenerator.ContainerFromIndex(0);
NavigateToContainer(container);
// Edge case: Whole dropdown content is disabled
if (this.IsKeyboardFocusWithin == false)
{
Keyboard.Focus(this.DropDownPopup);
}
});
this.OnDropDownOpened();
}
else
{
// If focus is within the subtree, make sure we have the focus so that focus isn't in the disposed hwnd
if (this.IsKeyboardFocusWithin)
{
// make sure the control has focus
this.Focus();
}
Mouse.Capture(null);
this.OnDropDownClosed();
}
}
/// <summary>
/// Called when drop down opened.
/// </summary>
protected virtual void OnDropDownOpened()
{
this.DropDownOpened?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Called when drop down closed.
/// </summary>
protected virtual void OnDropDownClosed()
{
foreach (var openMenuItem in this.openMenuItems.ToArray())
{
if (openMenuItem.IsAlive == false)
{
continue;
}
var menuItem = (System.Windows.Controls.MenuItem)openMenuItem.Target;
if (menuItem.IsSubmenuOpen)
{
menuItem.IsSubmenuOpen = false;
}
}
this.openMenuItems.Clear();
this.DropDownClosed?.Invoke(this, EventArgs.Empty);
}
#endregion
#region Quick Access Item Creating
/// <inheritdoc />
public virtual FrameworkElement CreateQuickAccessItem()
{
var button = new DropDownButton
{
Size = RibbonControlSize.Small
};
this.BindQuickAccessItem(button);
RibbonControl.Bind(this, button, nameof(this.DisplayMemberPath), DisplayMemberPathProperty, BindingMode.OneWay);
RibbonControl.Bind(this, button, nameof(this.GroupStyleSelector), GroupStyleSelectorProperty, BindingMode.OneWay);
RibbonControl.Bind(this, button, nameof(this.ItemContainerStyle), ItemContainerStyleProperty, BindingMode.OneWay);
RibbonControl.Bind(this, button, nameof(this.ItemsPanel), ItemsPanelProperty, BindingMode.OneWay);
RibbonControl.Bind(this, button, nameof(this.ItemStringFormat), ItemStringFormatProperty, BindingMode.OneWay);
RibbonControl.Bind(this, button, nameof(this.ItemTemplate), ItemTemplateProperty, BindingMode.OneWay);
RibbonControl.Bind(this, button, nameof(this.MaxDropDownHeight), MaxDropDownHeightProperty, BindingMode.OneWay);
this.BindQuickAccessItemDropDownEvents(button);
button.DropDownOpened += this.OnQuickAccessOpened;
return button;
}
/// <summary>
/// Handles quick access button drop down menu opened
/// </summary>
protected void OnQuickAccessOpened(object sender, EventArgs e)
{
var buttonInQuickAccess = (DropDownButton)sender;
buttonInQuickAccess.DropDownClosed += this.OnQuickAccessMenuClosedOrUnloaded;
buttonInQuickAccess.Unloaded += this.OnQuickAccessMenuClosedOrUnloaded;
ItemsControlHelper.MoveItemsToDifferentControl(this, buttonInQuickAccess);
}
/// <summary>
/// Handles quick access button drop down menu closed
/// </summary>
protected void OnQuickAccessMenuClosedOrUnloaded(object sender, EventArgs e)
{
var buttonInQuickAccess = (DropDownButton)sender;
buttonInQuickAccess.DropDownClosed -= this.OnQuickAccessMenuClosedOrUnloaded;
buttonInQuickAccess.Unloaded -= this.OnQuickAccessMenuClosedOrUnloaded;
this.RunInDispatcherAsync(() =>
{
ItemsControlHelper.MoveItemsToDifferentControl(buttonInQuickAccess, this);
}, DispatcherPriority.Loaded);
}
/// <summary>
/// This method must be overridden to bind properties to use in quick access creating
/// </summary>
/// <param name="element">Toolbar item</param>
protected virtual void BindQuickAccessItem(FrameworkElement element)
{
RibbonControl.BindQuickAccessItem(this, element);
RibbonControl.Bind(this, element, nameof(this.ResizeMode), ResizeModeProperty, BindingMode.Default);
RibbonControl.Bind(this, element, nameof(this.MaxDropDownHeight), MaxDropDownHeightProperty, BindingMode.Default);
RibbonControl.Bind(this, element, nameof(this.HasTriangle), HasTriangleProperty, BindingMode.Default);
}
/// <summary>
/// Binds the DropDownClosed and DropDownOpened events to the created quick access item
/// </summary>
/// <param name="button">Toolbar item</param>
protected void BindQuickAccessItemDropDownEvents(DropDownButton button)
{
if (this.DropDownClosed != null)
{
button.DropDownClosed += this.DropDownClosed;
}
if (this.DropDownOpened != null)
{
button.DropDownOpened += this.DropDownOpened;
}
}
/// <inheritdoc />
public bool CanAddToQuickAccessToolBar
{
get { return (bool)this.GetValue(CanAddToQuickAccessToolBarProperty); }
set { this.SetValue(CanAddToQuickAccessToolBarProperty, value); }
}
/// <summary>Identifies the <see cref="CanAddToQuickAccessToolBar"/> dependency property.</summary>
public static readonly DependencyProperty CanAddToQuickAccessToolBarProperty = RibbonControl.CanAddToQuickAccessToolBarProperty.AddOwner(typeof(DropDownButton), new PropertyMetadata(BooleanBoxes.TrueBox, RibbonControl.OnCanAddToQuickAccessToolBarChanged));
#endregion
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() => new Fluent.Automation.Peers.RibbonDropDownButtonAutomationPeer(this);
#region MenuItem workarounds
private void OnSubmenuOpened(object sender, RoutedEventArgs e)
{
var menuItem = e.OriginalSource as MenuItem;
if (menuItem != null)
{
this.openMenuItems.Push(new WeakReference(menuItem));
}
}
private void OnSubmenuClosed(object sender, RoutedEventArgs e)
{
if (this.openMenuItems.Count > 0)
{
this.openMenuItems.Pop();
}
}
#endregion MenuItem workarounds
/// <inheritdoc />
void ILogicalChildSupport.AddLogicalChild(object child)
{
this.AddLogicalChild(child);
}
/// <inheritdoc />
void ILogicalChildSupport.RemoveLogicalChild(object child)
{
this.RemoveLogicalChild(child);
}
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.Icon != null)
{
yield return this.Icon;
}
if (this.LargeIcon != null)
{
yield return this.LargeIcon;
}
if (this.Header != null)
{
yield return this.Header;
}
}
}
}
}

View File

@@ -0,0 +1,545 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;
using Fluent.Extensions;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents gallery control.
/// Usually a gallery is hosted in context menu
/// </summary>
[ContentProperty(nameof(Items))]
[TemplatePart(Name = "PART_DropDownButton", Type = typeof(DropDownButton))]
public class Gallery : ListBox
{
#region Fields
private ObservableCollection<GalleryGroupFilter> filters;
private DropDownButton groupsMenuButton;
#endregion
#region Properties
#region MinItemsInRow
/// <summary>
/// Min width of the Gallery
/// </summary>
public int MinItemsInRow
{
get { return (int)this.GetValue(MinItemsInRowProperty); }
set { this.SetValue(MinItemsInRowProperty, value); }
}
/// <summary>Identifies the <see cref="MinItemsInRow"/> dependency property.</summary>
public static readonly DependencyProperty MinItemsInRowProperty =
DependencyProperty.Register(nameof(MinItemsInRow), typeof(int),
typeof(Gallery), new PropertyMetadata(1));
#endregion
#region MaxItemsInRow
/// <summary>
/// Max width of the Gallery
/// </summary>
public int MaxItemsInRow
{
get { return (int)this.GetValue(MaxItemsInRowProperty); }
set { this.SetValue(MaxItemsInRowProperty, value); }
}
/// <summary>Identifies the <see cref="MaxItemsInRow"/> dependency property.</summary>
public static readonly DependencyProperty MaxItemsInRowProperty = DependencyProperty.Register(nameof(MaxItemsInRow), typeof(int), typeof(Gallery), new PropertyMetadata(IntBoxes.Zero));
#endregion
#region IsGrouped
/// <summary>Identifies the <see cref="IsGrouped"/> dependency property.</summary>
public static readonly DependencyProperty IsGroupedProperty = DependencyProperty.Register(nameof(IsGrouped), typeof(bool), typeof(Gallery), new PropertyMetadata(BooleanBoxes.FalseBox));
/// <summary>
/// Gets or sets whether the inner gallery panel shows groups
/// (Filter property still works as usual)
/// </summary>
public bool IsGrouped
{
get { return (bool)this.GetValue(IsGroupedProperty); }
set { this.SetValue(IsGroupedProperty, value); }
}
#endregion
#region GroupBy
/// <summary>
/// Gets or sets name of property which
/// will use to group items in the Gallery.
/// </summary>
public string GroupBy
{
get { return (string)this.GetValue(GroupByProperty); }
set { this.SetValue(GroupByProperty, value); }
}
/// <summary>Identifies the <see cref="GroupBy"/> dependency property.</summary>
public static readonly DependencyProperty GroupByProperty = DependencyProperty.Register(nameof(GroupBy), typeof(string), typeof(Gallery), new PropertyMetadata());
#endregion
#region GroupByAdvanced
/// <summary>
/// Gets or sets name of property which
/// will use to group items in the Gallery.
/// </summary>
public Func<object, string> GroupByAdvanced
{
get { return (Func<object, string>)this.GetValue(GroupByAdvancedProperty); }
set { this.SetValue(GroupByAdvancedProperty, value); }
}
/// <summary>Identifies the <see cref="GroupByAdvanced"/> dependency property.</summary>
public static readonly DependencyProperty GroupByAdvancedProperty = DependencyProperty.Register(nameof(GroupByAdvanced), typeof(Func<object, string>), typeof(Gallery), new PropertyMetadata());
#endregion
#region Orientation
/// <summary>
/// Gets or sets orientation of gallery
/// </summary>
public Orientation Orientation
{
get { return (Orientation)this.GetValue(OrientationProperty); }
set { this.SetValue(OrientationProperty, value); }
}
/// <summary>Identifies the <see cref="Orientation"/> dependency property.</summary>
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register(nameof(Orientation), typeof(Orientation),
typeof(Gallery), new PropertyMetadata(Orientation.Horizontal));
#endregion
#region ItemWidth
/// <summary>
/// Gets or sets item width
/// </summary>
public double ItemWidth
{
get { return (double)this.GetValue(ItemWidthProperty); }
set { this.SetValue(ItemWidthProperty, value); }
}
/// <summary>Identifies the <see cref="ItemWidth"/> dependency property.</summary>
public static readonly DependencyProperty ItemWidthProperty =
DependencyProperty.Register(nameof(ItemWidth), typeof(double), typeof(Gallery), new PropertyMetadata(DoubleBoxes.NaN));
/// <summary>
/// Gets or sets item height
/// </summary>
public double ItemHeight
{
get { return (double)this.GetValue(ItemHeightProperty); }
set { this.SetValue(ItemHeightProperty, value); }
}
/// <summary>Identifies the <see cref="ItemHeight"/> dependency property.</summary>
public static readonly DependencyProperty ItemHeightProperty =
DependencyProperty.Register(nameof(ItemHeight), typeof(double), typeof(Gallery), new PropertyMetadata(DoubleBoxes.NaN));
#endregion
#region Filters
/// <summary>
/// Gets collection of filters
/// </summary>
public ObservableCollection<GalleryGroupFilter> Filters
{
get
{
if (this.filters is null)
{
this.filters = new ObservableCollection<GalleryGroupFilter>();
this.filters.CollectionChanged += this.OnFilterCollectionChanged;
}
return this.filters;
}
}
// Handle toolbar items changes
private void OnFilterCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
this.HasFilter = this.Filters.Count > 0;
this.InvalidateProperty(SelectedFilterProperty);
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
for (var i = 0; i < e.NewItems.Count; i++)
{
if (this.groupsMenuButton != null)
{
var filter = (GalleryGroupFilter)e.NewItems[i];
var menuItem = new MenuItem
{
Header = filter.Title,
Tag = filter
};
if (ReferenceEquals(filter, this.SelectedFilter))
{
menuItem.IsChecked = true;
}
menuItem.Click += this.OnFilterMenuItemClick;
this.groupsMenuButton.Items.Insert(e.NewStartingIndex + i, menuItem);
}
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (var item in e.OldItems)
{
if (this.groupsMenuButton != null)
{
var menuItem = this.GetFilterMenuItem(item as GalleryGroupFilter);
menuItem.Click -= this.OnFilterMenuItemClick;
this.groupsMenuButton.Items.Remove(menuItem);
}
}
break;
case NotifyCollectionChangedAction.Replace:
foreach (var item in e.OldItems)
{
if (this.groupsMenuButton != null)
{
var menuItem = this.GetFilterMenuItem(item as GalleryGroupFilter);
menuItem.Click -= this.OnFilterMenuItemClick;
this.groupsMenuButton.Items.Remove(menuItem);
}
}
foreach (var item in e.NewItems.OfType<GalleryGroupFilter>())
{
if (this.groupsMenuButton != null)
{
var filter = item;
var menuItem = new MenuItem
{
Header = filter.Title,
Tag = filter
};
if (ReferenceEquals(filter, this.SelectedFilter))
{
menuItem.IsChecked = true;
}
menuItem.Click += this.OnFilterMenuItemClick;
this.groupsMenuButton.Items.Add(menuItem);
}
}
break;
}
}
/// <summary>
/// Gets or sets selected filter
/// </summary>
public GalleryGroupFilter SelectedFilter
{
get { return (GalleryGroupFilter)this.GetValue(SelectedFilterProperty); }
set { this.SetValue(SelectedFilterProperty, value); }
}
/// <summary>Identifies the <see cref="SelectedFilter"/> dependency property.</summary>
public static readonly DependencyProperty SelectedFilterProperty =
DependencyProperty.Register(nameof(SelectedFilter), typeof(GalleryGroupFilter),
typeof(Gallery), new PropertyMetadata(null, OnSelectedFilterChanged, CoerceSelectedFilter));
// Coerce selected filter
private static object CoerceSelectedFilter(DependencyObject d, object basevalue)
{
var gallery = (Gallery)d;
if (basevalue is null
&& gallery.Filters.Count > 0)
{
return gallery.Filters[0];
}
return basevalue;
}
// Handles filter property changed
private static void OnSelectedFilterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var gallery = (Gallery)d;
if (e.NewValue is GalleryGroupFilter filter)
{
gallery.SelectedFilterTitle = filter.Title;
gallery.SelectedFilterGroups = filter.Groups;
}
else
{
gallery.SelectedFilterTitle = string.Empty;
gallery.SelectedFilterGroups = null;
}
gallery.UpdateLayout();
}
/// <summary>
/// Gets selected filter title
/// </summary>
public string SelectedFilterTitle
{
get { return (string)this.GetValue(SelectedFilterTitleProperty); }
private set { this.SetValue(SelectedFilterTitlePropertyKey, value); }
}
private static readonly DependencyPropertyKey SelectedFilterTitlePropertyKey =
DependencyProperty.RegisterReadOnly(nameof(SelectedFilterTitle), typeof(string),
typeof(Gallery), new PropertyMetadata());
/// <summary>Identifies the <see cref="SelectedFilterTitle"/> dependency property.</summary>
public static readonly DependencyProperty SelectedFilterTitleProperty = SelectedFilterTitlePropertyKey.DependencyProperty;
/// <summary>
/// Gets selected filter groups
/// </summary>
public string SelectedFilterGroups
{
get { return (string)this.GetValue(SelectedFilterGroupsProperty); }
private set { this.SetValue(SelectedFilterGroupsPropertyKey, value); }
}
private static readonly DependencyPropertyKey SelectedFilterGroupsPropertyKey =
DependencyProperty.RegisterReadOnly(nameof(SelectedFilterGroups), typeof(string),
typeof(Gallery), new PropertyMetadata());
/// <summary>Identifies the <see cref="SelectedFilterGroups"/> dependency property.</summary>
public static readonly DependencyProperty SelectedFilterGroupsProperty = SelectedFilterGroupsPropertyKey.DependencyProperty;
/// <summary>
/// Gets whether gallery has selected filter
/// </summary>
public bool HasFilter
{
get { return (bool)this.GetValue(HasFilterProperty); }
private set { this.SetValue(HasFilterPropertyKey, value); }
}
private static readonly DependencyPropertyKey HasFilterPropertyKey = DependencyProperty.RegisterReadOnly(nameof(HasFilter), typeof(bool), typeof(Gallery), new PropertyMetadata(BooleanBoxes.FalseBox));
/// <summary>Identifies the <see cref="HasFilter"/> dependency property.</summary>
public static readonly DependencyProperty HasFilterProperty = HasFilterPropertyKey.DependencyProperty;
private void OnFilterMenuItemClick(object sender, RoutedEventArgs e)
{
var senderItem = (MenuItem)sender;
var item = this.GetFilterMenuItem(this.SelectedFilter);
item.IsChecked = false;
senderItem.IsChecked = true;
this.SelectedFilter = senderItem.Tag as GalleryGroupFilter;
this.groupsMenuButton.IsDropDownOpen = false;
e.Handled = true;
}
private MenuItem GetFilterMenuItem(GalleryGroupFilter filter)
{
if (filter is null)
{
return null;
}
return this.groupsMenuButton.Items.Cast<MenuItem>().FirstOrDefault(item => (item != null) && (item.Header.ToString() == filter.Title));
/*foreach (MenuItem item in groupsMenuButton.Items)
{
if ((item!=null)&&(item.Header == filter.Title)) return item;
}
return null;*/
}
#endregion
#region Selectable
/// <summary>
/// Gets or sets whether gallery items can be selected
/// </summary>
public bool Selectable
{
get { return (bool)this.GetValue(SelectableProperty); }
set { this.SetValue(SelectableProperty, value); }
}
/// <summary>Identifies the <see cref="Selectable"/> dependency property.</summary>
public static readonly DependencyProperty SelectableProperty =
DependencyProperty.Register(nameof(Selectable), typeof(bool),
typeof(Gallery), new PropertyMetadata(BooleanBoxes.TrueBox, OnSelectableChanged));
private static void OnSelectableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(SelectedItemProperty);
}
#endregion
#region IsLastItem
/// <summary>
/// Gets whether gallery is last item in ItemsControl
/// </summary>
public bool IsLastItem
{
get { return (bool)this.GetValue(IsLastItemProperty); }
private set { this.SetValue(IsLastItemPropertyKey, value); }
}
/// <summary>Identifies the <see cref="IsLastItem"/> dependency property.</summary>
public static readonly DependencyPropertyKey IsLastItemPropertyKey = DependencyProperty.RegisterReadOnly(nameof(IsLastItem), typeof(bool), typeof(Gallery), new PropertyMetadata(BooleanBoxes.FalseBox));
/// <summary>Identifies the <see cref="IsLastItem"/> dependency property.</summary>
public static readonly DependencyProperty IsLastItemProperty = IsLastItemPropertyKey.DependencyProperty;
#endregion
#endregion
#region Constructors
/// <summary>
/// Static constructor
/// </summary>
static Gallery()
{
var type = typeof(Gallery);
DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(typeof(Gallery)));
SelectedItemProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(null, CoerceSelectedItem));
ContextMenuService.Attach(type);
}
// Coerce selected item
private static object CoerceSelectedItem(DependencyObject d, object basevalue)
{
var gallery = (Gallery)d;
if (gallery.Selectable == false)
{
var galleryItem = gallery.ItemContainerGenerator.ContainerOrContainerContentFromItem<GalleryItem>(basevalue);
if (basevalue != null
&& galleryItem != null)
{
galleryItem.IsSelected = false;
}
return null;
}
return basevalue;
}
/// <summary>
/// Default constructor
/// </summary>
public Gallery()
{
ContextMenuService.Coerce(this);
this.Loaded += this.OnLoaded;
this.Focusable = false;
KeyboardNavigation.SetDirectionalNavigation(this, KeyboardNavigationMode.Continue);
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (this.Parent is ItemsControl parent)
{
if (parent.Items.IndexOf(this) == parent.Items.Count - 1)
{
this.IsLastItem = true;
}
else
{
this.IsLastItem = false;
}
}
}
#endregion
#region Overrides
/// <inheritdoc />
protected override DependencyObject GetContainerForItemOverride()
{
return new GalleryItem();
}
/// <inheritdoc />
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is GalleryItem;
}
/// <inheritdoc />
public override void OnApplyTemplate()
{
if (this.groupsMenuButton != null)
{
foreach (var menuItem in this.groupsMenuButton.Items.Cast<MenuItem>())
{
menuItem.Click -= this.OnFilterMenuItemClick;
}
}
this.groupsMenuButton?.Items.Clear();
this.groupsMenuButton = this.GetTemplateChild("PART_DropDownButton") as DropDownButton;
if (this.groupsMenuButton != null)
{
for (var i = 0; i < this.Filters.Count; i++)
{
var item = new MenuItem
{
Header = this.Filters[i].Title,
Tag = this.Filters[i],
IsDefinitive = false
};
if (ReferenceEquals(this.Filters[i], this.SelectedFilter))
{
item.IsChecked = true;
}
item.Click += this.OnFilterMenuItemClick;
this.groupsMenuButton.Items.Add(item);
}
}
base.OnApplyTemplate();
}
#endregion
}
}

View File

@@ -0,0 +1,146 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Windows;
using System.Windows.Controls;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents container of grouped gallery items in GalleryPanel or Gallery
/// </summary>
public class GalleryGroupContainer : HeaderedItemsControl
{
#region Properites
#region IsHeadered
/// <summary>
/// Gets or sets whether the header must be shown.
/// When the property is false this control uses to show all items without grouping
/// </summary>
public bool IsHeadered
{
get { return (bool)this.GetValue(IsHeaderedProperty); }
set { this.SetValue(IsHeaderedProperty, value); }
}
/// <summary>Identifies the <see cref="IsHeadered"/> dependency property.</summary>
public static readonly DependencyProperty IsHeaderedProperty =
DependencyProperty.Register(nameof(IsHeadered), typeof(bool),
typeof(GalleryGroupContainer), new PropertyMetadata(BooleanBoxes.TrueBox));
#endregion
#region Orientation
/// <summary>
/// Gets or sets panel orientation
/// </summary>
public Orientation Orientation
{
get { return (Orientation)this.GetValue(OrientationProperty); }
set { this.SetValue(OrientationProperty, value); }
}
/// <summary>Identifies the <see cref="Orientation"/> dependency property.</summary>
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register(nameof(Orientation), typeof(Orientation),
typeof(GalleryGroupContainer), new PropertyMetadata(Orientation.Horizontal));
#endregion
#region ItemWidth
/// <summary>
/// Gets or sets a value that specifies the width of
/// all items that are contained within
/// </summary>
public double ItemWidth
{
get { return (double)this.GetValue(ItemWidthProperty); }
set { this.SetValue(ItemWidthProperty, value); }
}
/// <summary>Identifies the <see cref="ItemWidth"/> dependency property.</summary>
public static readonly DependencyProperty ItemWidthProperty =
DependencyProperty.Register(nameof(ItemWidth), typeof(double),
typeof(GalleryGroupContainer), new PropertyMetadata(DoubleBoxes.NaN));
#endregion
#region ItemHeight
/// <summary>
/// Gets or sets a value that specifies the height of
/// all items that are contained within
/// </summary>
public double ItemHeight
{
get { return (double)this.GetValue(ItemHeightProperty); }
set { this.SetValue(ItemHeightProperty, value); }
}
/// <summary>Identifies the <see cref="ItemHeight"/> dependency property.</summary>
public static readonly DependencyProperty ItemHeightProperty =
DependencyProperty.Register(nameof(ItemHeight), typeof(double),
typeof(GalleryGroupContainer), new PropertyMetadata(DoubleBoxes.NaN));
#endregion
#region MinItemsInRow
/// <summary>
/// Gets or sets minimum items in which should be placed in one row.
/// </summary>
public int MinItemsInRow
{
get { return (int)this.GetValue(MinItemsInRowProperty); }
set { this.SetValue(MinItemsInRowProperty, value); }
}
/// <summary>Identifies the <see cref="MinItemsInRow"/> dependency property.</summary>
public static readonly DependencyProperty MinItemsInRowProperty =
DependencyProperty.Register(nameof(MinItemsInRow), typeof(int),
typeof(GalleryGroupContainer), new FrameworkPropertyMetadata(IntBoxes.Zero, FrameworkPropertyMetadataOptions.AffectsMeasure));
#endregion
#region MaxItemsInRow
/// <summary>
/// Gets or sets maximum items in which should be placed in one row.
/// </summary>
public int MaxItemsInRow
{
get { return (int)this.GetValue(MaxItemsInRowProperty); }
set { this.SetValue(MaxItemsInRowProperty, value); }
}
/// <summary>Identifies the <see cref="MaxItemsInRow"/> dependency property.</summary>
public static readonly DependencyProperty MaxItemsInRowProperty = DependencyProperty.Register(nameof(MaxItemsInRow), typeof(int), typeof(GalleryGroupContainer), new FrameworkPropertyMetadata(IntBoxes.Zero, FrameworkPropertyMetadataOptions.AffectsMeasure));
#endregion
#endregion
#region Initialization
/// <summary>
/// Static constructor
/// </summary>
static GalleryGroupContainer()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(GalleryGroupContainer), new FrameworkPropertyMetadata(typeof(GalleryGroupContainer)));
}
/// <inheritdoc />
protected override void OnItemsPanelChanged(ItemsPanelTemplate oldItemsPanel, ItemsPanelTemplate newItemsPanel)
{
base.OnItemsPanelChanged(oldItemsPanel, newItemsPanel);
this.InvalidateMeasure();
}
#endregion
}
}

View File

@@ -0,0 +1,41 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Windows;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents gallery group filter definition
/// </summary>
public class GalleryGroupFilter : DependencyObject
{
/// <summary>
/// Gets or sets title of filter
/// </summary>
public string Title
{
get { return (string)this.GetValue(TitleProperty); }
set { this.SetValue(TitleProperty, value); }
}
/// <summary>Identifies the <see cref="Title"/> dependency property.</summary>
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register(nameof(Title), typeof(string),
typeof(GalleryGroupFilter), new PropertyMetadata("GalleryGroupFilter"));
/// <summary>
/// Gets or sets list pf groups splitted by comma
/// </summary>
public string Groups
{
get { return (string)this.GetValue(GroupsProperty); }
set { this.SetValue(GroupsProperty, value); }
}
/// <summary>Identifies the <see cref="Groups"/> dependency property.</summary>
public static readonly DependencyProperty GroupsProperty =
#pragma warning disable WPF0010 // Default value type must match registered type.
DependencyProperty.Register(nameof(Groups), typeof(string), typeof(GalleryGroupFilter), new PropertyMetadata(StringBoxes.Empty));
#pragma warning restore WPF0010 // Default value type must match registered type.
}
}

View File

@@ -0,0 +1,401 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using Fluent.Extensions;
using Fluent.Helpers;
using Fluent.Internal;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents gallery item
/// </summary>
public class GalleryItem : ListBoxItem, IKeyTipedControl, ICommandSource
{
#region Properties
#region KeyTip
/// <inheritdoc />
public string KeyTip
{
get { return (string)this.GetValue(KeyTipProperty); }
set { this.SetValue(KeyTipProperty, value); }
}
/// <summary>
/// Using a DependencyProperty as the backing store for Keys.
/// This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty KeyTipProperty = Fluent.KeyTip.KeysProperty.AddOwner(typeof(GalleryItem));
#endregion
/// <summary>
/// Gets a value that indicates whether a Button is currently activated.
/// This is a dependency property.
/// </summary>
public bool IsPressed
{
get { return (bool)this.GetValue(IsPressedProperty); }
private set { this.SetValue(IsPressedPropertyKey, value); }
}
private static readonly DependencyPropertyKey IsPressedPropertyKey =
DependencyProperty.RegisterReadOnly(nameof(IsPressed), typeof(bool),
typeof(GalleryItem), new PropertyMetadata(BooleanBoxes.FalseBox));
/// <summary>Identifies the <see cref="IsPressed"/> dependency property.</summary>
public static readonly DependencyProperty IsPressedProperty = IsPressedPropertyKey.DependencyProperty;
/// <summary>
/// Gets or sets GalleryItem group
/// </summary>
public string Group
{
get { return (string)this.GetValue(GroupProperty); }
set { this.SetValue(GroupProperty, value); }
}
/// <summary>Identifies the <see cref="Group"/> dependency property.</summary>
public static readonly DependencyProperty GroupProperty =
DependencyProperty.Register(nameof(Group), typeof(string),
typeof(GalleryItem), new PropertyMetadata());
/// <summary>
/// Gets or sets whether ribbon control click must close backstage or popup.
/// </summary>
public bool IsDefinitive
{
get { return (bool)this.GetValue(IsDefinitiveProperty); }
set { this.SetValue(IsDefinitiveProperty, value); }
}
/// <summary>Identifies the <see cref="IsDefinitive"/> dependency property.</summary>
public static readonly DependencyProperty IsDefinitiveProperty =
DependencyProperty.Register(nameof(IsDefinitive), typeof(bool), typeof(GalleryItem), new PropertyMetadata(BooleanBoxes.TrueBox));
#region Command
private bool currentCanExecute = true;
/// <inheritdoc />
[Category("Action")]
[Localizability(LocalizationCategory.NeverLocalize)]
[Bindable(true)]
public ICommand Command
{
get
{
return (ICommand)this.GetValue(CommandProperty);
}
set
{
this.SetValue(CommandProperty, value);
}
}
/// <inheritdoc />
[Bindable(true)]
[Localizability(LocalizationCategory.NeverLocalize)]
[Category("Action")]
public object CommandParameter
{
get
{
return this.GetValue(CommandParameterProperty);
}
set
{
this.SetValue(CommandParameterProperty, value);
}
}
/// <inheritdoc />
[Bindable(true)]
[Category("Action")]
public IInputElement CommandTarget
{
get
{
return (IInputElement)this.GetValue(CommandTargetProperty);
}
set
{
this.SetValue(CommandTargetProperty, value);
}
}
/// <summary>Identifies the <see cref="CommandParameter"/> dependency property.</summary>
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(nameof(CommandParameter), typeof(object), typeof(GalleryItem), new PropertyMetadata());
/// <summary>Identifies the <see cref="Command"/> dependency property.</summary>
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(GalleryItem), new PropertyMetadata(OnCommandChanged));
/// <summary>Identifies the <see cref="CommandTarget"/> dependency property.</summary>
public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register(nameof(CommandTarget), typeof(IInputElement), typeof(GalleryItem), new PropertyMetadata());
/// <summary>
/// Gets or sets the command to invoke when mouse enters or leaves this button. The commandparameter will be the <see cref="GalleryItem"/> instance.
/// This is a dependency property.
/// </summary>
[Bindable(true)]
[Category("Action")]
public ICommand PreviewCommand
{
get { return (ICommand)this.GetValue(PreviewCommandProperty); }
set { this.SetValue(PreviewCommandProperty, value); }
}
/// <summary>Identifies the <see cref="PreviewCommand"/> dependency property.</summary>
public static readonly DependencyProperty PreviewCommandProperty =
DependencyProperty.Register(nameof(PreviewCommand), typeof(ICommand), typeof(GalleryItem), new PropertyMetadata());
/// <summary>
/// Gets or sets the command to invoke when mouse enters or leaves this button. The commandparameter will be the <see cref="GalleryItem"/> instance.
/// This is a dependency property.
/// </summary>
[Bindable(true)]
[Category("Action")]
public ICommand CancelPreviewCommand
{
get { return (ICommand)this.GetValue(CancelPreviewCommandProperty); }
set { this.SetValue(CancelPreviewCommandProperty, value); }
}
/// <summary>Identifies the <see cref="CancelPreviewCommand"/> dependency property.</summary>
public static readonly DependencyProperty CancelPreviewCommandProperty =
DependencyProperty.Register(nameof(CancelPreviewCommand), typeof(ICommand), typeof(GalleryItem), new PropertyMetadata());
/// <summary>
/// Handles Command changed
/// </summary>
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as GalleryItem;
if (control is null)
{
return;
}
if (e.OldValue is ICommand oldCommand)
{
oldCommand.CanExecuteChanged -= control.OnCommandCanExecuteChanged;
}
if (e.NewValue is ICommand newCommand)
{
newCommand.CanExecuteChanged += control.OnCommandCanExecuteChanged;
}
control.UpdateCanExecute();
}
/// <summary>
/// Handles Command CanExecute changed
/// </summary>
private void OnCommandCanExecuteChanged(object sender, EventArgs e)
{
this.UpdateCanExecute();
}
private void UpdateCanExecute()
{
var canExecute = this.Command != null
&& this.CanExecuteCommand();
if (this.currentCanExecute != canExecute)
{
this.currentCanExecute = canExecute;
this.CoerceValue(IsEnabledProperty);
}
}
#endregion
#region IsEnabled
/// <inheritdoc />
protected override bool IsEnabledCore => base.IsEnabledCore && (this.currentCanExecute || this.Command is null);
#endregion
#endregion
#region Events
#region Click
/// <summary>
/// Occurs when a RibbonControl is clicked.
/// </summary>
[Category("Behavior")]
public event RoutedEventHandler Click
{
add
{
this.AddHandler(ClickEvent, value);
}
remove
{
this.RemoveHandler(ClickEvent, value);
}
}
/// <summary>
/// Identifies the RibbonControl.Click routed event.
/// </summary>
public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent(nameof(Click), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(GalleryItem));
/// <summary>
/// Raises click event
/// </summary>
public void RaiseClick()
{
this.RaiseEvent(new RoutedEventArgs(ClickEvent, this));
}
#endregion
#endregion
#region Constructors
/// <summary>
/// Static constructor
/// </summary>
static GalleryItem()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(GalleryItem), new FrameworkPropertyMetadata(typeof(GalleryItem)));
IsSelectedProperty.AddOwner(typeof(GalleryItem), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, FrameworkPropertyMetadataOptions.None, OnIsSelectedChanged));
}
private static void OnIsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
((GalleryItem)d).BringIntoView();
if (ItemsControlHelper.ItemsControlFromItemContainer(d) is Selector parentSelector)
{
var item = parentSelector.ItemContainerGenerator.ItemFromContainerOrContainerContent(d);
if (ReferenceEquals(parentSelector.SelectedItem, item) == false)
{
parentSelector.SelectedItem = item;
}
}
}
}
/// <summary>
/// Default constructor
/// </summary>
public GalleryItem()
{
this.Click += this.OnClick;
}
#endregion
#region Overrides
/// <inheritdoc />
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
this.IsPressed = true;
Mouse.Capture(this);
e.Handled = true;
}
/// <inheritdoc />
protected override void OnLostMouseCapture(MouseEventArgs e)
{
base.OnLostMouseCapture(e);
this.IsPressed = false;
}
/// <inheritdoc />
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
this.IsPressed = false;
if (ReferenceEquals(Mouse.Captured, this))
{
Mouse.Capture(null);
}
var position = Mouse.PrimaryDevice.GetPosition(this);
if (position.X >= 0.0 && position.X <= this.ActualWidth && position.Y >= 0.0 && position.Y <= this.ActualHeight && e.ClickCount == 1)
{
this.RaiseClick();
e.Handled = true;
}
e.Handled = true;
}
/// <inheritdoc />
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
CommandHelper.Execute(this.PreviewCommand, this, null);
}
/// <inheritdoc />
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
CommandHelper.Execute(this.CancelPreviewCommand, this, null);
}
#endregion
#region Protected methods
/// <summary>
/// Handles click event
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">The event data</param>
protected virtual void OnClick(object sender, RoutedEventArgs e)
{
// Close popup on click
if (this.IsDefinitive)
{
PopupService.RaiseDismissPopupEvent(sender, DismissPopupMode.Always);
}
this.ExecuteCommand();
this.IsSelected = true;
e.Handled = true;
}
#endregion
/// <inheritdoc />
public KeyTipPressedResult OnKeyTipPressed()
{
this.RaiseClick();
return KeyTipPressedResult.Empty;
}
/// <inheritdoc />
public void OnKeyTipBack()
{
}
}
}

View File

@@ -0,0 +1,67 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Windows;
/// <summary>
/// Represents internal class to use it in
/// GalleryPanel as placeholder for GalleryItems
/// </summary>
internal class GalleryItemPlaceholder : UIElement
{
#region Properties
/// <summary>
/// Gets the target of the placeholder
/// </summary>
public UIElement Target { get; }
public Size ArrangedSize { get; private set; }
#endregion
#region Initialization
/// <summary>
/// Constructor
/// </summary>
/// <param name="target">Target</param>
public GalleryItemPlaceholder(UIElement target)
{
this.Target = target;
}
#endregion
#region Methods
/// <inheritdoc />
protected override Size MeasureCore(Size availableSize)
{
this.Target.Measure(availableSize);
return this.Target.DesiredSize;
}
/// <inheritdoc />
protected override void ArrangeCore(Rect finalRect)
{
base.ArrangeCore(finalRect);
// Remember arranged size to arrange
// targets in GalleryPanel lately
this.ArrangedSize = finalRect.Size;
}
#endregion
#region Debug
/* FOR DEGUG */
//protected override void OnRender(DrawingContext drawingContext)
//{
// drawingContext.DrawRectangle(null, new Pen(Brushes.Red, 1), new Rect(this.RenderSize));
//}
#endregion
}
}

View File

@@ -0,0 +1,527 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Threading;
using Fluent.Extensions;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents panel for Gallery and InRibbonGallery with grouping and filtering capabilities
/// </summary>
public class GalleryPanel : StackPanel
{
// todo: localization
private const string Undefined = "Undefined";
#region Fields
// Currently used group containers
private readonly List<GalleryGroupContainer> galleryGroupContainers = new List<GalleryGroupContainer>();
// Designate that gallery panel must be refreshed its groups
private bool needsRefresh;
#endregion
#region Properties
#region IsGrouped
/// <summary>
/// Gets or sets whether gallery panel shows groups
/// (Filter property still works as usual)
/// </summary>
public bool IsGrouped
{
get { return (bool)this.GetValue(IsGroupedProperty); }
set { this.SetValue(IsGroupedProperty, value); }
}
/// <summary>Identifies the <see cref="IsGrouped"/> dependency property.</summary>
public static readonly DependencyProperty IsGroupedProperty =
DependencyProperty.Register(nameof(IsGrouped), typeof(bool), typeof(GalleryPanel),
new PropertyMetadata(BooleanBoxes.FalseBox, OnIsGroupedChanged));
private static void OnIsGroupedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var galleryPanel = (GalleryPanel)d;
galleryPanel.RefreshAsync();
}
#endregion
#region GroupBy
/// <summary>
/// Gets or sets property name to group items
/// </summary>
public string GroupBy
{
get { return (string)this.GetValue(GroupByProperty); }
set { this.SetValue(GroupByProperty, value); }
}
/// <summary>Identifies the <see cref="GroupBy"/> dependency property.</summary>
public static readonly DependencyProperty GroupByProperty = DependencyProperty.Register(nameof(GroupBy), typeof(string), typeof(GalleryPanel), new PropertyMetadata(OnGroupByChanged));
private static void OnGroupByChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var galleryPanel = (GalleryPanel)d;
galleryPanel.RefreshAsync();
}
#endregion
#region GroupByAdvanced
/// <summary>
/// Gets or sets name of property which
/// will use to group items in the Gallery.
/// </summary>
public Func<object, string> GroupByAdvanced
{
get { return (Func<object, string>)this.GetValue(GroupByAdvancedProperty); }
set { this.SetValue(GroupByAdvancedProperty, value); }
}
/// <summary>Identifies the <see cref="GroupByAdvanced"/> dependency property.</summary>
public static readonly DependencyProperty GroupByAdvancedProperty = DependencyProperty.Register(nameof(GroupByAdvanced), typeof(Func<object, string>), typeof(GalleryPanel), new PropertyMetadata(OnGroupByAdvancedChanged));
private static void OnGroupByAdvancedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var galleryPanel = (GalleryPanel)d;
galleryPanel.RefreshAsync();
}
#endregion
#region ItemContainerGenerator
/// <summary>
/// Gets or sets ItemContainerGenerator which generates the
/// user interface (UI) on behalf of its host, such as an ItemsControl.
/// </summary>
public ItemContainerGenerator ItemContainerGenerator
{
get { return (ItemContainerGenerator)this.GetValue(ItemContainerGeneratorProperty); }
set { this.SetValue(ItemContainerGeneratorProperty, value); }
}
/// <summary>Identifies the <see cref="ItemContainerGenerator"/> dependency property.</summary>
public static readonly DependencyProperty ItemContainerGeneratorProperty =
DependencyProperty.Register(nameof(ItemContainerGenerator), typeof(ItemContainerGenerator),
typeof(GalleryPanel), new PropertyMetadata(OnItemContainerGeneratorChanged));
private static void OnItemContainerGeneratorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var galleryPanel = (GalleryPanel)d;
galleryPanel.RefreshAsync();
}
#endregion
#region ItemWidth
/// <summary>
/// Gets or sets a value that specifies the width of
/// all items that are contained within
/// </summary>
public double ItemWidth
{
get { return (double)this.GetValue(ItemWidthProperty); }
set { this.SetValue(ItemWidthProperty, value); }
}
/// <summary>Identifies the <see cref="ItemWidth"/> dependency property.</summary>
public static readonly DependencyProperty ItemWidthProperty =
DependencyProperty.Register(nameof(ItemWidth), typeof(double),
typeof(GalleryPanel), new PropertyMetadata(DoubleBoxes.NaN));
#endregion
#region ItemHeight
/// <summary>
/// Gets or sets a value that specifies the height of
/// all items that are contained within
/// </summary>
public double ItemHeight
{
get { return (double)this.GetValue(ItemHeightProperty); }
set { this.SetValue(ItemHeightProperty, value); }
}
/// <summary>Identifies the <see cref="ItemHeight"/> dependency property.</summary>
public static readonly DependencyProperty ItemHeightProperty =
DependencyProperty.Register(nameof(ItemHeight), typeof(double),
typeof(GalleryPanel), new PropertyMetadata(DoubleBoxes.NaN));
#endregion
#region Filter
/// <summary>
/// Gets or sets groups names separated by comma which must be shown
/// </summary>
public string Filter
{
get { return (string)this.GetValue(FilterProperty); }
set { this.SetValue(FilterProperty, value); }
}
/// <summary>Identifies the <see cref="Filter"/> dependency property.</summary>
public static readonly DependencyProperty FilterProperty =
DependencyProperty.Register(nameof(Filter), typeof(string),
typeof(GalleryPanel), new PropertyMetadata(OnFilterChanged));
private static void OnFilterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var galleryPanel = (GalleryPanel)d;
galleryPanel.RefreshAsync();
}
#endregion
#region MinItemsInRow
/// <summary>
/// Gets or sets maximum items quantity in row
/// </summary>
public int MinItemsInRow
{
get { return (int)this.GetValue(MinItemsInRowProperty); }
set { this.SetValue(MinItemsInRowProperty, value); }
}
/// <summary>Identifies the <see cref="MinItemsInRow"/> dependency property.</summary>
public static readonly DependencyProperty MinItemsInRowProperty =
DependencyProperty.Register(nameof(MinItemsInRow), typeof(int),
typeof(GalleryPanel), new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsMeasure));
#endregion
#region MaxItemsInRow
/// <summary>
/// Gets or sets maximum items quantity in row
/// </summary>
public int MaxItemsInRow
{
get { return (int)this.GetValue(MaxItemsInRowProperty); }
set { this.SetValue(MaxItemsInRowProperty, value); }
}
/// <summary>Identifies the <see cref="MaxItemsInRow"/> dependency property.</summary>
public static readonly DependencyProperty MaxItemsInRowProperty = DependencyProperty.Register(nameof(MaxItemsInRow), typeof(int), typeof(GalleryPanel), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsMeasure));
#endregion
#endregion
#region Initialization
/// <summary>
/// Default constructor
/// </summary>
public GalleryPanel()
{
this.visualCollection = new VisualCollection(this);
this.Loaded += this.HandleGalleryPanel_Loaded;
}
private void HandleGalleryPanel_Loaded(object sender, RoutedEventArgs e)
{
this.Loaded -= this.HandleGalleryPanel_Loaded;
this.Refresh();
}
#endregion
#region Visual Tree
private readonly VisualCollection visualCollection;
/// <inheritdoc />
protected override int VisualChildrenCount => base.VisualChildrenCount + this.visualCollection.Count;
/// <inheritdoc />
protected override Visual GetVisualChild(int index)
{
if (index < base.VisualChildrenCount)
{
return base.GetVisualChild(index);
}
return this.visualCollection[index - base.VisualChildrenCount];
}
#endregion
#region Refresh
private bool areUpdatesSuspsended;
/// <summary>
/// Suspends updates.
/// </summary>
public void SuspendUpdates()
{
this.areUpdatesSuspsended = true;
}
/// <summary>
/// Resumes updates.
/// </summary>
public void ResumeUpdates()
{
this.areUpdatesSuspsended = false;
}
/// <summary>
/// Resumes updates and calls <see cref="Refresh"/>.
/// </summary>
public void ResumeUpdatesRefresh()
{
this.ResumeUpdates();
this.Refresh();
}
private void RefreshAsync()
{
if (this.needsRefresh
|| this.areUpdatesSuspsended)
{
return;
}
this.needsRefresh = true;
this.RunInDispatcherAsync(() =>
{
if (this.needsRefresh == false)
{
return;
}
this.Refresh();
this.needsRefresh = false;
}, DispatcherPriority.Send);
}
private void Refresh()
{
if (this.areUpdatesSuspsended)
{
return;
}
// Clear currently used group containers
// and supply with new generated ones
foreach (var galleryGroupContainer in this.galleryGroupContainers)
{
BindingOperations.ClearAllBindings(galleryGroupContainer);
this.visualCollection.Remove(galleryGroupContainer);
}
this.galleryGroupContainers.Clear();
// Gets filters
var filter = this.Filter?.Split(',');
var dictionary = new Dictionary<string, GalleryGroupContainer>();
foreach (UIElement item in this.InternalChildren)
{
if (item is null)
{
continue;
}
// Resolve group name
string propertyValue = null;
if (this.GroupByAdvanced != null)
{
propertyValue = this.ItemContainerGenerator is null
? this.GroupByAdvanced(item)
: this.GroupByAdvanced(this.ItemContainerGenerator.ItemFromContainerOrContainerContent(item));
}
else if (string.IsNullOrEmpty(this.GroupBy) == false)
{
propertyValue = this.ItemContainerGenerator is null
? this.GetPropertyValueAsString(item)
: this.GetPropertyValueAsString(this.ItemContainerGenerator.ItemFromContainerOrContainerContent(item));
}
if (propertyValue is null)
{
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
&& filter.Contains(propertyValue) == false)
{
continue;
}
// To put all items in one group in case of IsGrouped = False
if (this.IsGrouped == false)
{
propertyValue = Undefined;
}
if (dictionary.ContainsKey(propertyValue) == false)
{
var galleryGroupContainer = new GalleryGroupContainer
{
Header = propertyValue
};
RibbonControl.Bind(this, galleryGroupContainer, nameof(this.Orientation), GalleryGroupContainer.OrientationProperty, BindingMode.OneWay);
RibbonControl.Bind(this, galleryGroupContainer, nameof(this.ItemWidth), GalleryGroupContainer.ItemWidthProperty, BindingMode.OneWay);
RibbonControl.Bind(this, galleryGroupContainer, nameof(this.ItemHeight), GalleryGroupContainer.ItemHeightProperty, BindingMode.OneWay);
RibbonControl.Bind(this, galleryGroupContainer, nameof(this.MaxItemsInRow), GalleryGroupContainer.MaxItemsInRowProperty, BindingMode.OneWay);
RibbonControl.Bind(this, galleryGroupContainer, nameof(this.MinItemsInRow), GalleryGroupContainer.MinItemsInRowProperty, BindingMode.OneWay);
dictionary.Add(propertyValue, galleryGroupContainer);
this.galleryGroupContainers.Add(galleryGroupContainer);
this.visualCollection.Add(galleryGroupContainer);
}
var galleryItemPlaceholder = new GalleryItemPlaceholder(item);
dictionary[propertyValue].Items.Add(galleryItemPlaceholder);
}
if ((this.IsGrouped == false || (this.GroupBy is null && this.GroupByAdvanced is null))
&& this.galleryGroupContainers.Count != 0)
{
// Make it without headers if there is only one group or if we are not supposed to group
this.galleryGroupContainers[0].IsHeadered = false;
}
this.InvalidateMeasure();
}
/// <inheritdoc />
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
{
base.OnVisualChildrenChanged(visualAdded, visualRemoved);
if (visualRemoved is GalleryGroupContainer)
{
return;
}
if (visualAdded is GalleryGroupContainer)
{
return;
}
this.RefreshAsync();
}
#endregion
#region Layout Overrides
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize)
{
double width = 0;
double height = 0;
foreach (var child in this.galleryGroupContainers)
{
child.Measure(availableSize);
height += child.DesiredSize.Height;
width = Math.Max(width, child.DesiredSize.Width);
}
var size = new Size(width, height);
return size;
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize)
{
var finalRect = new Rect(finalSize);
foreach (var item in this.galleryGroupContainers)
{
finalRect.Height = item.DesiredSize.Height;
finalRect.Width = Math.Max(finalSize.Width, item.DesiredSize.Width);
// Arrange a container to arrange placeholders
item.Arrange(finalRect);
finalRect.Y += item.DesiredSize.Height;
// Now arrange our actual items using arranged size of placeholders
foreach (GalleryItemPlaceholder placeholder in item.Items)
{
var leftTop = placeholder.TranslatePoint(default, this);
placeholder.Target.Arrange(new Rect(leftTop.X, leftTop.Y, placeholder.ArrangedSize.Width, placeholder.ArrangedSize.Height));
}
}
return finalSize;
}
#endregion
#region Private Methods
private string GetPropertyValueAsString(object item)
{
if (item is null
|| this.GroupBy is null)
{
return Undefined;
}
var property = item.GetType().GetProperty(this.GroupBy, BindingFlags.Public | BindingFlags.Instance);
var result = property?.GetValue(item, null);
if (result is null)
{
return Undefined;
}
return result.ToString();
}
#endregion
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var count = this.VisualChildrenCount;
for (var i = 0; i < count; i++)
{
yield return this.GetVisualChild(i);
}
}
}
}
}

View File

@@ -0,0 +1,27 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Windows;
using System.Windows.Markup;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents group separator menu item
/// </summary>
[ContentProperty(nameof(Header))]
public class GroupSeparatorMenuItem : MenuItem
{
static GroupSeparatorMenuItem()
{
var type = typeof(GroupSeparatorMenuItem);
DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(type));
IsEnabledProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, null, CoerceIsEnabledAndTabStop));
IsTabStopProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, null, CoerceIsEnabledAndTabStop));
}
private static object CoerceIsEnabledAndTabStop(DependencyObject d, object basevalue)
{
return false;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,197 @@
#nullable enable
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Windows;
using System.Windows.Controls;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents KeyTip control
/// </summary>
public class KeyTip : Label
{
#region Keys Attached Property
/// <summary>
/// Using a DependencyProperty as the backing store for Keys.
/// This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty KeysProperty =
DependencyProperty.RegisterAttached("Keys", typeof(string), typeof(KeyTip), new PropertyMetadata(OnKeysChanged));
private static void OnKeysChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
/// <summary>
/// Sets value of attached property Keys for the given element
/// </summary>
/// <param name="element">The given element</param>
/// <param name="value">Value</param>
public static void SetKeys(DependencyObject element, string? value)
{
element.SetValue(KeysProperty, value);
}
/// <summary>
/// Gets value of the attached property Keys of the given element
/// </summary>
/// <param name="element">The given element</param>
[System.ComponentModel.DisplayName("Keys")]
[AttachedPropertyBrowsableForChildren(IncludeDescendants = true)]
[System.ComponentModel.Category("KeyTips")]
[System.ComponentModel.Description("Key sequence for the given element")]
public static string? GetKeys(DependencyObject element)
{
return (string?)element.GetValue(KeysProperty);
}
#endregion
#region AutoPlacement Attached Property
/// <summary>
/// Using a DependencyProperty as the backing store for AutoPlacement.
/// This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty AutoPlacementProperty =
DependencyProperty.RegisterAttached("AutoPlacement", typeof(bool), typeof(KeyTip), new PropertyMetadata(BooleanBoxes.TrueBox));
/// <summary>
/// Sets whether key tip placement is auto
/// or defined by alignment and margin properties
/// </summary>
/// <param name="element">The given element</param>
/// <param name="value">Value</param>
public static void SetAutoPlacement(DependencyObject element, bool value)
{
element.SetValue(AutoPlacementProperty, value);
}
/// <summary>
/// Gets whether key tip placement is auto
/// or defined by alignment and margin properties
/// </summary>
/// <param name="element">The given element</param>
[System.ComponentModel.DisplayName("AutoPlacement")]
[AttachedPropertyBrowsableForChildren(IncludeDescendants = true)]
[System.ComponentModel.Category("KeyTips")]
[System.ComponentModel.Description("Whether key tip placement is auto or defined by alignment and margin properties")]
public static bool GetAutoPlacement(DependencyObject element)
{
return (bool)element.GetValue(AutoPlacementProperty);
}
#endregion
#region HorizontalAlignment Attached Property
/// <summary>
/// Using a DependencyProperty as the backing store for HorizontalAlignment.
/// This enables animation, styling, binding, etc...
/// </summary>
public static new readonly DependencyProperty HorizontalAlignmentProperty =
DependencyProperty.RegisterAttached(nameof(HorizontalAlignment), typeof(HorizontalAlignment), typeof(KeyTip), new PropertyMetadata(HorizontalAlignment.Center));
/// <summary>
/// Sets Horizontal Alignment of the key tip
/// </summary>
/// <param name="element">The given element</param>
/// <param name="value">Value</param>
public static void SetHorizontalAlignment(DependencyObject element, HorizontalAlignment value)
{
element.SetValue(HorizontalAlignmentProperty, value);
}
/// <summary>
/// Gets Horizontal alignment of the key tip
/// </summary>
/// <param name="element">The given element</param>
[System.ComponentModel.DisplayName("HorizontalAlignment")]
[AttachedPropertyBrowsableForChildren(IncludeDescendants = true)]
[System.ComponentModel.Category("KeyTips")]
[System.ComponentModel.Description("Horizontal alignment of the key tip")]
public static HorizontalAlignment GetHorizontalAlignment(DependencyObject element)
{
return (HorizontalAlignment)element.GetValue(HorizontalAlignmentProperty);
}
#endregion
#region VerticalAlignment Attached Property
/// <summary>
/// Gets vertical alignment of the key tip
/// </summary>
/// <param name="element">The given element</param>
[System.ComponentModel.DisplayName("VerticalAlignment")]
[AttachedPropertyBrowsableForChildren(IncludeDescendants = true)]
[System.ComponentModel.Category("KeyTips")]
[System.ComponentModel.Description("Vertical alignment of the key tip")]
public static VerticalAlignment GetVerticalAlignment(DependencyObject element)
{
return (VerticalAlignment)element.GetValue(VerticalAlignmentProperty);
}
/// <summary>
/// Sets vertical alignment of the key tip
/// </summary>
/// <param name="obj">The given element</param>
/// <param name="value">Value</param>
public static void SetVerticalAlignment(DependencyObject obj, VerticalAlignment value)
{
obj.SetValue(VerticalAlignmentProperty, value);
}
/// <summary>
/// Using a DependencyProperty as the backing store for VerticalAlignment.
/// This enables animation, styling, binding, etc...
/// </summary>
public static new readonly DependencyProperty VerticalAlignmentProperty =
DependencyProperty.RegisterAttached(nameof(VerticalAlignment), typeof(VerticalAlignment), typeof(KeyTip), new PropertyMetadata(VerticalAlignment.Center));
#endregion
#region Margin Attached Property
/// <summary>
/// Gets margin of the key tip
/// </summary>
/// <param name="obj">The key tip</param>
/// <returns>Margin</returns>
[System.ComponentModel.DisplayName("Margin")]
[AttachedPropertyBrowsableForChildren(IncludeDescendants = true)]
[System.ComponentModel.Category("KeyTips")]
[System.ComponentModel.Description("Margin of the key tip")]
public static Thickness GetMargin(DependencyObject obj)
{
return (Thickness)obj.GetValue(MarginProperty);
}
/// <summary>
/// Sets margin of the key tip
/// </summary>
/// <param name="obj">The key tip</param>
/// <param name="value">Value</param>
public static void SetMargin(DependencyObject obj, Thickness value)
{
obj.SetValue(MarginProperty, value);
}
/// <summary>
/// Using a DependencyProperty as the backing store for Margin.
/// This enables animation, styling, binding, etc...
/// </summary>
public static new readonly DependencyProperty MarginProperty =
DependencyProperty.RegisterAttached(nameof(Margin), typeof(Thickness), typeof(KeyTip), new PropertyMetadata(default(Thickness)));
#endregion
static KeyTip()
{
// Override metadata to allow styling
DefaultStyleKeyProperty.OverrideMetadata(typeof(KeyTip), new FrameworkPropertyMetadata(typeof(KeyTip)));
}
}
}

View File

@@ -0,0 +1,810 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Threading;
using Fluent.Extensions;
using Fluent.Helpers;
using Fluent.Internal;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents menu item
/// </summary>
[ContentProperty(nameof(Items))]
[TemplatePart(Name = "PART_ResizeVerticalThumb", Type = typeof(Thumb))]
[TemplatePart(Name = "PART_ResizeBothThumb", Type = typeof(Thumb))]
[TemplatePart(Name = "PART_ScrollViewer", Type = typeof(ScrollViewer))]
[TemplatePart(Name = "PART_MenuPanel", Type = typeof(Panel))]
public class MenuItem : System.Windows.Controls.MenuItem, IQuickAccessItemProvider, IRibbonControl, IDropDownControl, IToggleButton
{
#region Fields
// Thumb to resize in both directions
private Thumb resizeBothThumb;
// Thumb to resize vertical
private Thumb resizeVerticalThumb;
private Panel menuPanel;
private ScrollViewer scrollViewer;
#endregion
#region Properties
private bool IsItemsControlMenuBase => (ItemsControlHelper.ItemsControlFromItemContainer(this) ?? VisualTreeHelper.GetParent(this)) is MenuBase;
#region Size
/// <inheritdoc />
public RibbonControlSize Size
{
get { return (RibbonControlSize)this.GetValue(SizeProperty); }
set { this.SetValue(SizeProperty, value); }
}
/// <summary>Identifies the <see cref="Size"/> dependency property.</summary>
public static readonly DependencyProperty SizeProperty = RibbonProperties.SizeProperty.AddOwner(typeof(MenuItem));
#endregion
#region SizeDefinition
/// <inheritdoc />
public RibbonControlSizeDefinition SizeDefinition
{
get { return (RibbonControlSizeDefinition)this.GetValue(SizeDefinitionProperty); }
set { this.SetValue(SizeDefinitionProperty, value); }
}
/// <summary>Identifies the <see cref="SizeDefinition"/> dependency property.</summary>
public static readonly DependencyProperty SizeDefinitionProperty = RibbonProperties.SizeDefinitionProperty.AddOwner(typeof(MenuItem));
#endregion
#region KeyTip
/// <inheritdoc />
public string KeyTip
{
get { return (string)this.GetValue(KeyTipProperty); }
set { this.SetValue(KeyTipProperty, value); }
}
/// <summary>
/// Using a DependencyProperty as the backing store for Keys.
/// This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty KeyTipProperty = Fluent.KeyTip.KeysProperty.AddOwner(typeof(MenuItem));
#endregion
/// <inheritdoc />
public Popup DropDownPopup { get; private set; }
/// <inheritdoc />
public bool IsContextMenuOpened { get; set; }
#region Description
/// <summary>
/// Useless property only used in secon level application menu items
/// </summary>
public string Description
{
get { return (string)this.GetValue(DescriptionProperty); }
set { this.SetValue(DescriptionProperty, value); }
}
/// <summary>Identifies the <see cref="Description"/> dependency property.</summary>
public static readonly DependencyProperty DescriptionProperty =
DependencyProperty.Register(nameof(Description), typeof(string), typeof(MenuItem), new PropertyMetadata(default(string)));
#endregion
#region IsDropDownOpen
/// <inheritdoc />
public bool IsDropDownOpen
{
get { return this.IsSubmenuOpen; }
set { this.IsSubmenuOpen = value; }
}
#endregion
#region IsDefinitive
/// <summary>
/// Gets or sets whether ribbon control click must close backstage
/// </summary>
public bool IsDefinitive
{
get { return (bool)this.GetValue(IsDefinitiveProperty); }
set { this.SetValue(IsDefinitiveProperty, value); }
}
/// <summary>Identifies the <see cref="IsDefinitive"/> dependency property.</summary>
public static readonly DependencyProperty IsDefinitiveProperty =
DependencyProperty.Register(nameof(IsDefinitive), typeof(bool), typeof(MenuItem), new PropertyMetadata(BooleanBoxes.TrueBox));
#endregion
#region ResizeMode
/// <summary>
/// Gets or sets context menu resize mode
/// </summary>
public ContextMenuResizeMode ResizeMode
{
get { return (ContextMenuResizeMode)this.GetValue(ResizeModeProperty); }
set { this.SetValue(ResizeModeProperty, value); }
}
/// <summary>Identifies the <see cref="ResizeMode"/> dependency property.</summary>
public static readonly DependencyProperty ResizeModeProperty =
DependencyProperty.Register(nameof(ResizeMode), typeof(ContextMenuResizeMode),
typeof(MenuItem), new PropertyMetadata(ContextMenuResizeMode.None));
#endregion
#region MaxDropDownHeight
/// <summary>
/// Get or sets max height of drop down popup
/// </summary>
public double MaxDropDownHeight
{
get { return (double)this.GetValue(MaxDropDownHeightProperty); }
set { this.SetValue(MaxDropDownHeightProperty, value); }
}
/// <summary>Identifies the <see cref="MaxDropDownHeight"/> dependency property.</summary>
public static readonly DependencyProperty MaxDropDownHeightProperty =
DependencyProperty.Register(nameof(MaxDropDownHeight), typeof(double), typeof(MenuItem), new PropertyMetadata(SystemParameters.PrimaryScreenHeight / 3.0));
#endregion
#region IsSplited
/// <summary>
/// Gets or sets a value indicating whether menu item is splited
/// </summary>
public bool IsSplited
{
get { return (bool)this.GetValue(IsSplitedProperty); }
set { this.SetValue(IsSplitedProperty, value); }
}
/// <summary>Identifies the <see cref="IsSplited"/> dependency property.</summary>
public static readonly DependencyProperty IsSplitedProperty =
DependencyProperty.Register(nameof(IsSplited), typeof(bool), typeof(MenuItem), new PropertyMetadata(BooleanBoxes.FalseBox));
#endregion
#region GroupName
/// <inheritdoc />
public string GroupName
{
get { return (string)this.GetValue(GroupNameProperty); }
set { this.SetValue(GroupNameProperty, value); }
}
/// <inheritdoc />
bool? IToggleButton.IsChecked
{
get { return this.IsChecked; }
set { this.IsChecked = value == true; }
}
/// <summary>Identifies the <see cref="GroupName"/> dependency property.</summary>
public static readonly DependencyProperty GroupNameProperty = DependencyProperty.Register(nameof(GroupName), typeof(string), typeof(MenuItem), new PropertyMetadata(ToggleButtonHelper.OnGroupNameChanged));
#endregion
#endregion
#region Events
/// <inheritdoc />
public event EventHandler DropDownOpened;
/// <inheritdoc />
public event EventHandler DropDownClosed;
#endregion
#region Constructors
/// <summary>
/// Initializes static members of the <see cref="MenuItem"/> class.
/// </summary>
static MenuItem()
{
var type = typeof(MenuItem);
ToolTipService.Attach(type);
//PopupService.Attach(type);
ContextMenuService.Attach(type);
DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(type));
IsCheckedProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, ToggleButtonHelper.OnIsCheckedChanged));
IconProperty.OverrideMetadata(typeof(MenuItem), new FrameworkPropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
}
/// <summary>
/// Initializes a new instance of the <see cref="MenuItem"/> class.
/// </summary>
public MenuItem()
{
ContextMenuService.Coerce(this);
}
/// <inheritdoc />
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
// Fix to raise MouseWhele event
(this.Parent as ListBox)?.RaiseEvent(e);
}
#endregion
/// <summary>Identifies the <see cref="RecognizesAccessKey"/> dependency property.</summary>
public static readonly DependencyProperty RecognizesAccessKeyProperty = DependencyProperty.RegisterAttached(
nameof(RecognizesAccessKey), typeof(bool), typeof(MenuItem), new PropertyMetadata(BooleanBoxes.TrueBox));
/// <summary>Helper for setting <see cref="RecognizesAccessKeyProperty"/> on <paramref name="element"/>.</summary>
/// <param name="element"><see cref="DependencyObject"/> to set <see cref="RecognizesAccessKeyProperty"/> on.</param>
/// <param name="value">RecognizesAccessKey property value.</param>
public static void SetRecognizesAccessKey(DependencyObject element, bool value)
{
element.SetValue(RecognizesAccessKeyProperty, value);
}
/// <summary>Helper for getting <see cref="RecognizesAccessKeyProperty"/> from <paramref name="element"/>.</summary>
/// <param name="element"><see cref="DependencyObject"/> to read <see cref="RecognizesAccessKeyProperty"/> from.</param>
/// <returns>RecognizesAccessKey property value.</returns>
public static bool GetRecognizesAccessKey(DependencyObject element)
{
return (bool)element.GetValue(RecognizesAccessKeyProperty);
}
/// <summary>
/// Defines if access keys should be recognized.
/// </summary>
public bool RecognizesAccessKey
{
get { return (bool)this.GetValue(RecognizesAccessKeyProperty); }
set { this.SetValue(RecognizesAccessKeyProperty, value); }
}
#region QuickAccess
/// <inheritdoc />
public virtual FrameworkElement CreateQuickAccessItem()
{
if (this.HasItems)
{
if (this.IsSplited)
{
var button = new SplitButton
{
CanAddButtonToQuickAccessToolBar = false
};
RibbonControl.BindQuickAccessItem(this, button);
RibbonControl.Bind(this, button, nameof(this.ResizeMode), ResizeModeProperty, BindingMode.Default);
RibbonControl.Bind(this, button, nameof(this.MaxDropDownHeight), MaxDropDownHeightProperty, BindingMode.Default);
RibbonControl.Bind(this, button, nameof(this.DisplayMemberPath), DisplayMemberPathProperty, BindingMode.OneWay);
RibbonControl.Bind(this, button, nameof(this.GroupStyleSelector), GroupStyleSelectorProperty, BindingMode.OneWay);
RibbonControl.Bind(this, button, nameof(this.ItemContainerStyle), ItemContainerStyleProperty, BindingMode.OneWay);
RibbonControl.Bind(this, button, nameof(this.ItemsPanel), ItemsPanelProperty, BindingMode.OneWay);
RibbonControl.Bind(this, button, nameof(this.ItemStringFormat), ItemStringFormatProperty, BindingMode.OneWay);
RibbonControl.Bind(this, button, nameof(this.ItemTemplate), ItemTemplateProperty, BindingMode.OneWay);
button.DropDownOpened += this.OnQuickAccessOpened;
return button;
}
else
{
var button = new DropDownButton();
RibbonControl.BindQuickAccessItem(this, button);
RibbonControl.Bind(this, button, nameof(this.ResizeMode), ResizeModeProperty, BindingMode.Default);
RibbonControl.Bind(this, button, nameof(this.MaxDropDownHeight), MaxDropDownHeightProperty, BindingMode.Default);
RibbonControl.Bind(this, button, nameof(this.DisplayMemberPath), DisplayMemberPathProperty, BindingMode.OneWay);
RibbonControl.Bind(this, button, nameof(this.GroupStyleSelector), GroupStyleSelectorProperty, BindingMode.OneWay);
RibbonControl.Bind(this, button, nameof(this.ItemContainerStyle), ItemContainerStyleProperty, BindingMode.OneWay);
RibbonControl.Bind(this, button, nameof(this.ItemsPanel), ItemsPanelProperty, BindingMode.OneWay);
RibbonControl.Bind(this, button, nameof(this.ItemStringFormat), ItemStringFormatProperty, BindingMode.OneWay);
RibbonControl.Bind(this, button, nameof(this.ItemTemplate), ItemTemplateProperty, BindingMode.OneWay);
button.DropDownOpened += this.OnQuickAccessOpened;
return button;
}
}
else
{
var button = new Button();
RibbonControl.BindQuickAccessItem(this, button);
return button;
}
}
/// <summary>
/// Handles quick access button drop down menu opened
/// </summary>
protected void OnQuickAccessOpened(object sender, EventArgs e)
{
var buttonInQuickAccess = (DropDownButton)sender;
buttonInQuickAccess.DropDownClosed += this.OnQuickAccessMenuClosedOrUnloaded;
buttonInQuickAccess.Unloaded += this.OnQuickAccessMenuClosedOrUnloaded;
ItemsControlHelper.MoveItemsToDifferentControl(this, buttonInQuickAccess);
}
/// <summary>
/// Handles quick access button drop down menu closed
/// </summary>
protected void OnQuickAccessMenuClosedOrUnloaded(object sender, EventArgs e)
{
var buttonInQuickAccess = (DropDownButton)sender;
buttonInQuickAccess.DropDownClosed -= this.OnQuickAccessMenuClosedOrUnloaded;
buttonInQuickAccess.Unloaded -= this.OnQuickAccessMenuClosedOrUnloaded;
ItemsControlHelper.MoveItemsToDifferentControl(buttonInQuickAccess, this);
}
/// <inheritdoc />
public bool CanAddToQuickAccessToolBar
{
get { return (bool)this.GetValue(CanAddToQuickAccessToolBarProperty); }
set { this.SetValue(CanAddToQuickAccessToolBarProperty, value); }
}
/// <summary>Identifies the <see cref="CanAddToQuickAccessToolBar"/> dependency property.</summary>
public static readonly DependencyProperty CanAddToQuickAccessToolBarProperty = RibbonControl.CanAddToQuickAccessToolBarProperty.AddOwner(typeof(MenuItem));
private bool isContextMenuOpening;
#endregion
#region Public
/// <inheritdoc />
public virtual KeyTipPressedResult OnKeyTipPressed()
{
if (this.HasItems == false)
{
this.OnClick();
return KeyTipPressedResult.Empty;
}
else
{
Keyboard.Focus(this);
this.IsDropDownOpen = true;
return new KeyTipPressedResult(true, true);
}
}
/// <inheritdoc />
public void OnKeyTipBack()
{
this.IsDropDownOpen = false;
}
#endregion
#region Overrides
/// <inheritdoc />
protected override DependencyObject GetContainerForItemOverride()
{
return new MenuItem();
}
/// <inheritdoc />
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is FrameworkElement;
}
#region Non MenuBase ItemsControl workarounds
/// <summary>
/// Returns logical parent; either Parent or ItemsControlFromItemContainer(this).
/// </summary>
/// <remarks>
/// Copied from <see cref="System.Windows.Controls.MenuItem"/>.
/// </remarks>
public object LogicalParent
{
get
{
if (this.Parent != null)
{
return this.Parent;
}
return ItemsControlFromItemContainer(this);
}
}
/// <inheritdoc />
protected override void OnIsKeyboardFocusedChanged(DependencyPropertyChangedEventArgs e)
{
base.OnIsKeyboardFocusedChanged(e);
if (this.IsItemsControlMenuBase == false)
{
this.IsHighlighted = this.IsKeyboardFocused;
}
}
/// <inheritdoc />
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
if (this.IsItemsControlMenuBase == false
&& this.isContextMenuOpening == false)
{
if (this.HasItems
&& this.LogicalParent is DropDownButton)
{
this.IsSubmenuOpen = true;
}
}
}
/// <inheritdoc />
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
if (this.IsItemsControlMenuBase == false
&& this.isContextMenuOpening == false)
{
if (this.HasItems
&& this.LogicalParent is DropDownButton // prevent too slow close on regular DropDown
&& this.LogicalParent is ApplicationMenu == false) // prevent eager close on ApplicationMenu
{
this.IsSubmenuOpen = false;
}
}
}
/// <inheritdoc />
protected override void OnContextMenuOpening(ContextMenuEventArgs e)
{
this.isContextMenuOpening = true;
// We have to close the sub menu as soon as the context menu gets opened
// but only if it should be opened on ourself
if (ReferenceEquals(this, e.Source))
{
this.IsSubmenuOpen = false;
}
base.OnContextMenuOpening(e);
}
/// <inheritdoc />
protected override void OnContextMenuClosing(ContextMenuEventArgs e)
{
this.isContextMenuOpening = false;
base.OnContextMenuClosing(e);
}
#endregion Non MenuBase ItemsControl workarounds
/// <inheritdoc />
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
if (e.ClickCount == 1)
{
if (this.IsSplited)
{
if (this.GetTemplateChild("PART_ButtonBorder") is Border buttonBorder
&& PopupService.IsMousePhysicallyOver(buttonBorder))
{
this.OnClick();
}
}
else if (this.HasItems)
{
this.IsSubmenuOpen = !this.IsSubmenuOpen;
}
}
base.OnMouseLeftButtonUp(e);
}
/// <inheritdoc />
protected override void OnClick()
{
// Close popup on click
if (this.IsDefinitive
&& (!this.HasItems || this.IsSplited))
{
PopupService.RaiseDismissPopupEventAsync(this, DismissPopupMode.Always);
}
var revertIsChecked = false;
// Rewriting everthing contained in base.OnClick causes a lot of trouble.
// In case IsCheckable is true and GroupName is not empty we revert the value for IsChecked back to true to prevent unchecking all items in the group
if (this.IsCheckable
&& string.IsNullOrEmpty(this.GroupName) == false)
{
// If checked revert the IsChecked value back to true after forwarding the click to base
if (this.IsChecked)
{
revertIsChecked = true;
}
}
base.OnClick();
if (revertIsChecked)
{
this.RunInDispatcherAsync(() => this.SetCurrentValue(IsCheckedProperty, BooleanBoxes.TrueBox), DispatcherPriority.Background);
}
}
/// <inheritdoc />
public override void OnApplyTemplate()
{
if (this.DropDownPopup != null)
{
this.DropDownPopup.Opened -= this.OnDropDownOpened;
this.DropDownPopup.Closed -= this.OnDropDownClosed;
}
this.DropDownPopup = this.GetTemplateChild("PART_Popup") as Popup;
if (this.DropDownPopup != null)
{
this.DropDownPopup.Opened += this.OnDropDownOpened;
this.DropDownPopup.Closed += this.OnDropDownClosed;
KeyboardNavigation.SetControlTabNavigation(this.DropDownPopup, KeyboardNavigationMode.Cycle);
KeyboardNavigation.SetDirectionalNavigation(this.DropDownPopup, KeyboardNavigationMode.Cycle);
KeyboardNavigation.SetTabNavigation(this.DropDownPopup, KeyboardNavigationMode.Cycle);
}
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.scrollViewer = this.GetTemplateChild("PART_ScrollViewer") as ScrollViewer;
this.menuPanel = this.GetTemplateChild("PART_MenuPanel") as Panel;
}
/// <inheritdoc />
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
if (this.IsSubmenuOpen)
{
this.IsSubmenuOpen = false;
}
else
{
this.CloseParentDropDownOrMenuItem();
}
e.Handled = true;
}
else
{
#region Non MenuBase ItemsControl workarounds
if (this.IsItemsControlMenuBase == false)
{
var key = e.Key;
if (this.FlowDirection == FlowDirection.RightToLeft)
{
if (key == Key.Right)
{
key = Key.Left;
}
else if (key == Key.Left)
{
key = Key.Right;
}
}
if (key == Key.Right
&& this.menuPanel != null)
{
this.IsSubmenuOpen = true;
this.menuPanel.MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
e.Handled = true;
}
else if (key == Key.Left)
{
if (this.IsSubmenuOpen)
{
this.IsSubmenuOpen = false;
}
else
{
var parentMenuItem = UIHelper.GetParent<System.Windows.Controls.MenuItem>(this);
if (parentMenuItem != null)
{
parentMenuItem.IsSubmenuOpen = false;
}
}
e.Handled = true;
}
if (e.Handled)
{
return;
}
}
#endregion Non MenuBase ItemsControl workarounds
base.OnKeyDown(e);
}
}
private void CloseParentDropDownOrMenuItem()
{
var parent = UIHelper.GetParent<DependencyObject>(this, x => x is IDropDownControl || x is System.Windows.Controls.MenuItem);
if (parent is null)
{
return;
}
if (parent is IDropDownControl dropDown)
{
dropDown.IsDropDownOpen = false;
}
else
{
((System.Windows.Controls.MenuItem)parent).IsSubmenuOpen = false;
}
}
#endregion
#region Methods
// Handles resize both drag
private void OnResizeBothDelta(object sender, DragDeltaEventArgs e)
{
if (this.scrollViewer != null)
{
this.scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
}
if (this.menuPanel != null)
{
if (double.IsNaN(this.menuPanel.Width))
{
this.menuPanel.Width = this.menuPanel.ActualWidth;
}
if (double.IsNaN(this.menuPanel.Height))
{
this.menuPanel.Height = this.menuPanel.ActualHeight;
}
this.menuPanel.Width = Math.Max(this.menuPanel.MinWidth, this.menuPanel.Width + e.HorizontalChange);
this.menuPanel.Height = Math.Min(Math.Max(this.menuPanel.MinHeight, this.menuPanel.Height + e.VerticalChange), this.MaxDropDownHeight);
}
}
// Handles resize vertical drag
private void OnResizeVerticalDelta(object sender, DragDeltaEventArgs e)
{
if (this.scrollViewer != null)
{
this.scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
}
if (this.menuPanel != null)
{
if (double.IsNaN(this.menuPanel.Height))
{
this.menuPanel.Height = this.menuPanel.ActualHeight;
}
this.menuPanel.Height = Math.Min(Math.Max(this.menuPanel.MinHeight, this.menuPanel.Height + e.VerticalChange), this.MaxDropDownHeight);
}
}
// Handles drop down opened
private void OnDropDownClosed(object sender, EventArgs e)
{
this.DropDownClosed?.Invoke(this, e);
}
// Handles drop down closed
private void OnDropDownOpened(object sender, EventArgs e)
{
if (this.scrollViewer != null
&& this.ResizeMode != ContextMenuResizeMode.None)
{
this.scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
}
if (this.menuPanel != null)
{
this.menuPanel.Width = double.NaN;
this.menuPanel.Height = double.NaN;
}
this.DropDownOpened?.Invoke(this, e);
}
#endregion
/// <inheritdoc />
void ILogicalChildSupport.AddLogicalChild(object child)
{
this.AddLogicalChild(child);
}
/// <inheritdoc />
void ILogicalChildSupport.RemoveLogicalChild(object child)
{
this.RemoveLogicalChild(child);
}
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.Icon != null)
{
yield return this.Icon;
}
}
}
}
}

View File

@@ -0,0 +1,273 @@
#pragma warning disable SA1402 // File may only contain a single class
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
using System.Windows.Media;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// This interface must be implemented for controls
/// which are intended to insert to quick access toolbar
/// </summary>
public interface IQuickAccessItemProvider
{
/// <summary>
/// Gets control which represents shortcut item.
/// This item MUST be syncronized with the original
/// and send command to original one control.
/// </summary>
/// <returns>Control which represents shortcut item</returns>
FrameworkElement CreateQuickAccessItem();
/// <summary>
/// Gets or sets a value indicating whether control can be added to quick access toolbar
/// </summary>
bool CanAddToQuickAccessToolBar { get; set; }
}
/// <summary>
/// Peresents quick access shortcut to another control
/// </summary>
[ContentProperty(nameof(Target))]
public class QuickAccessMenuItem : MenuItem
{
#region Fields
internal Ribbon Ribbon { get; set; }
#endregion
#region Initialization
static QuickAccessMenuItem()
{
IsCheckableProperty.AddOwner(typeof(QuickAccessMenuItem), new FrameworkPropertyMetadata(BooleanBoxes.TrueBox));
}
/// <summary>
/// Default constructor
/// </summary>
public QuickAccessMenuItem()
{
this.Checked += this.OnChecked;
this.Unchecked += this.OnUnchecked;
this.Loaded += this.OnFirstLoaded;
this.Loaded += this.OnItemLoaded;
}
#endregion
#region Target Property
/// <summary>
/// Gets or sets shortcut to the target control
/// </summary>
public UIElement Target
{
get { return (UIElement)this.GetValue(TargetProperty); }
set { this.SetValue(TargetProperty, value); }
}
/// <summary>Identifies the <see cref="Target"/> dependency property.</summary>
public static readonly DependencyProperty TargetProperty =
DependencyProperty.Register(nameof(Target), typeof(UIElement), typeof(QuickAccessMenuItem), new PropertyMetadata(OnTargetChanged));
private static void OnTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var quickAccessMenuItem = (QuickAccessMenuItem)d;
var ribbonControl = e.NewValue as IRibbonControl;
if (quickAccessMenuItem.Header is null
&& ribbonControl != null)
{
// Set Default Text Value
RibbonControl.Bind(ribbonControl, quickAccessMenuItem, nameof(IRibbonControl.Header), HeaderProperty, BindingMode.OneWay);
}
if (ribbonControl != null)
{
var parent = LogicalTreeHelper.GetParent((DependencyObject)ribbonControl);
if (parent is null)
{
quickAccessMenuItem.AddLogicalChild(ribbonControl);
}
}
if (e.OldValue is IRibbonControl oldRibbonControl)
{
var parent = LogicalTreeHelper.GetParent((DependencyObject)oldRibbonControl);
if (ReferenceEquals(parent, quickAccessMenuItem))
{
quickAccessMenuItem.RemoveLogicalChild(oldRibbonControl);
}
}
}
#endregion
#region Overrides
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.Target != null)
{
var parent = LogicalTreeHelper.GetParent(this.Target);
if (ReferenceEquals(parent, this))
{
yield return this.Target;
}
}
}
}
#endregion
#region Event Handlers
private void OnChecked(object sender, RoutedEventArgs e)
{
this.Ribbon?.AddToQuickAccessToolBar(this.Target);
}
private void OnUnchecked(object sender, RoutedEventArgs e)
{
if (this.IsLoaded == false)
{
return;
}
this.Ribbon?.RemoveFromQuickAccessToolBar(this.Target);
}
private void OnItemLoaded(object sender, RoutedEventArgs e)
{
if (this.IsLoaded == false)
{
return;
}
if (this.Ribbon != null)
{
this.IsChecked = this.Ribbon.IsInQuickAccessToolBar(this.Target);
}
}
private void OnFirstLoaded(object sender, RoutedEventArgs e)
{
this.Loaded -= this.OnFirstLoaded;
if (this.IsChecked)
{
this.Ribbon?.AddToQuickAccessToolBar(this.Target);
}
}
#endregion
}
/// <summary>
/// The class responds to mine controls for QuickAccessToolBar
/// </summary>
internal static class QuickAccessItemsProvider
{
#region Public Methods
/// <summary>
/// Determines whether the given control can provide a quick access toolbar item
/// </summary>
/// <param name="element">Control</param>
/// <returns>True if this control is able to provide
/// a quick access toolbar item, false otherwise</returns>
public static bool IsSupported(UIElement element)
{
if (element is IQuickAccessItemProvider provider
&& provider.CanAddToQuickAccessToolBar)
{
return true;
}
return false;
}
/// <summary>
/// Gets control which represents quick access toolbar item
/// </summary>
/// <param name="element">Host control</param>
/// <returns>Control which represents quick access toolbar item</returns>
public static FrameworkElement GetQuickAccessItem(UIElement element)
{
FrameworkElement result = null;
// If control supports the interface just return what it provides
if (element is IQuickAccessItemProvider provider
&& provider.CanAddToQuickAccessToolBar)
{
result = provider.CreateQuickAccessItem();
}
// The control isn't supported
if (result is null)
{
throw new ArgumentException("The contol " + element.GetType().Name + " is not able to provide a quick access toolbar item");
}
RibbonProperties.SetIsElementInQuickAccessToolBar(result, true);
if (BindingOperations.IsDataBound(result, UIElement.VisibilityProperty) == false)
{
RibbonControl.Bind(element, result, nameof(UIElement.Visibility), UIElement.VisibilityProperty, BindingMode.OneWay);
}
if (BindingOperations.IsDataBound(result, UIElement.IsEnabledProperty) == false)
{
RibbonControl.Bind(element, result, nameof(UIElement.IsEnabled), UIElement.IsEnabledProperty, BindingMode.OneWay);
}
return result;
}
/// <summary>
/// Finds the top supported control
/// </summary>
public static FrameworkElement FindSupportedControl(Visual visual, Point point)
{
var result = VisualTreeHelper.HitTest(visual, point);
if (result is null)
{
return null;
}
// Try to find in visual (or logical) tree
var element = result.VisualHit as FrameworkElement;
while (element != null)
{
if (IsSupported(element))
{
return element;
}
var visualParent = VisualTreeHelper.GetParent(element) as FrameworkElement;
var logicalParent = LogicalTreeHelper.GetParent(element) as FrameworkElement;
element = visualParent ?? logicalParent;
}
return null;
}
#endregion
}
}

View File

@@ -0,0 +1,718 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Markup;
using Fluent.Collections;
using Fluent.Extensions;
using Fluent.Internal;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents quick access toolbar
/// </summary>
[TemplatePart(Name = "PART_ShowAbove", Type = typeof(MenuItem))]
[TemplatePart(Name = "PART_ShowBelow", Type = typeof(MenuItem))]
[TemplatePart(Name = "PART_MenuPanel", Type = typeof(Panel))]
[TemplatePart(Name = "PART_RootPanel", Type = typeof(Panel))]
[ContentProperty(nameof(QuickAccessItems))]
[TemplatePart(Name = "PART_MenuDownButton", Type = typeof(DropDownButton))]
[TemplatePart(Name = "PART_ToolbarDownButton", Type = typeof(DropDownButton))]
[TemplatePart(Name = "PART_ToolBarPanel", Type = typeof(Panel))]
[TemplatePart(Name = "PART_ToolBarOverflowPanel", Type = typeof(Panel))]
public class QuickAccessToolBar : Control, ILogicalChildSupport
{
#region Events
/// <summary>
/// Occured when items are added or removed from Quick Access toolbar
/// </summary>
public event NotifyCollectionChangedEventHandler ItemsChanged;
#endregion
#region Fields
private DropDownButton toolBarDownButton;
internal DropDownButton MenuDownButton { get; private set; }
// Show above menu item
private MenuItem showAbove;
// Show below menu item
private MenuItem showBelow;
// Items of quick access menu
private ItemCollectionWithLogicalTreeSupport<QuickAccessMenuItem> quickAccessItems;
// Root panel
private Panel rootPanel;
// ToolBar panel
private Panel toolBarPanel;
// ToolBar overflow panel
private Panel toolBarOverflowPanel;
// Items of quick access menu
private ObservableCollection<UIElement> items;
private Size cachedConstraint;
private int cachedNonOverflowItemsCount = -1;
// Itemc collection was changed
private bool itemsHadChanged;
private double cachedMenuDownButtonWidth;
private double cachedOverflowDownButtonWidth;
#endregion
#region Properties
#region Items
/// <summary>
/// Gets items collection
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public ObservableCollection<UIElement> Items
{
get
{
if (this.items is null)
{
this.items = new ObservableCollection<UIElement>();
this.items.CollectionChanged += this.OnItemsCollectionChanged;
}
return this.items;
}
}
private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
this.cachedNonOverflowItemsCount = this.GetNonOverflowItemsCount(this.DesiredSize.Width);
this.UpdateHasOverflowItems();
this.itemsHadChanged = true;
this.Refresh();
this.UpdateKeyTips();
if (e.OldItems != null)
{
foreach (var item in e.OldItems.OfType<FrameworkElement>())
{
item.SizeChanged -= this.OnChildSizeChanged;
}
}
if (e.NewItems != null)
{
foreach (var item in e.NewItems.OfType<FrameworkElement>())
{
item.SizeChanged += this.OnChildSizeChanged;
}
}
if (e.Action == NotifyCollectionChangedAction.Reset)
{
foreach (var item in this.Items.OfType<FrameworkElement>())
{
item.SizeChanged -= this.OnChildSizeChanged;
}
}
// Raise items changed event
this.ItemsChanged?.Invoke(this, e);
if (this.Items.Count == 0
&& this.toolBarDownButton != null)
{
this.toolBarDownButton.IsDropDownOpen = false;
}
}
private void OnChildSizeChanged(object sender, SizeChangedEventArgs e)
{
this.InvalidateMeasureOfTitleBar();
}
#endregion
#region HasOverflowItems
/// <summary>
/// Gets whether QuickAccessToolBar has overflow items
/// </summary>
public bool HasOverflowItems
{
get { return (bool)this.GetValue(HasOverflowItemsProperty); }
private set { this.SetValue(HasOverflowItemsPropertyKey, value); }
}
// ReSharper disable once InconsistentNaming
private static readonly DependencyPropertyKey HasOverflowItemsPropertyKey =
DependencyProperty.RegisterReadOnly(nameof(HasOverflowItems), typeof(bool), typeof(QuickAccessToolBar), new PropertyMetadata(BooleanBoxes.FalseBox));
/// <summary>Identifies the <see cref="HasOverflowItems"/> dependency property.</summary>
public static readonly DependencyProperty HasOverflowItemsProperty = HasOverflowItemsPropertyKey.DependencyProperty;
#endregion
#region QuickAccessItems
/// <summary>
/// Gets quick access menu items
/// </summary>
public ItemCollectionWithLogicalTreeSupport<QuickAccessMenuItem> QuickAccessItems
{
get
{
if (this.quickAccessItems is null)
{
this.quickAccessItems = new ItemCollectionWithLogicalTreeSupport<QuickAccessMenuItem>(this);
this.quickAccessItems.CollectionChanged += this.OnQuickAccessItemsCollectionChanged;
}
return this.quickAccessItems;
}
}
/// <summary>
/// Handles collection of quick access menu items changes
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">The event data</param>
private void OnQuickAccessItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (this.MenuDownButton is null)
{
return;
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var item in e.NewItems.OfType<QuickAccessMenuItem>())
{
var index = this.QuickAccessItems.IndexOf(item);
this.MenuDownButton.Items.Insert(index + 1, item);
this.QuickAccessItems[index].InvalidateProperty(QuickAccessMenuItem.TargetProperty);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (var item in e.OldItems.OfType<QuickAccessMenuItem>())
{
this.MenuDownButton.Items.Remove(item);
item.InvalidateProperty(QuickAccessMenuItem.TargetProperty);
}
break;
case NotifyCollectionChangedAction.Replace:
foreach (var item in e.OldItems.OfType<QuickAccessMenuItem>())
{
this.MenuDownButton.Items.Remove(item);
item.InvalidateProperty(QuickAccessMenuItem.TargetProperty);
}
foreach (var item in e.NewItems.OfType<QuickAccessMenuItem>())
{
var index = this.QuickAccessItems.IndexOf(item);
this.MenuDownButton.Items.Insert(index + 1, item);
this.QuickAccessItems[index].InvalidateProperty(QuickAccessMenuItem.TargetProperty);
}
break;
}
}
#endregion
#region ShowAboveRibbon
/// <summary>
/// Gets or sets whether quick access toolbar showes above ribbon
/// </summary>
public bool ShowAboveRibbon
{
get { return (bool)this.GetValue(ShowAboveRibbonProperty); }
set { this.SetValue(ShowAboveRibbonProperty, value); }
}
/// <summary>Identifies the <see cref="ShowAboveRibbon"/> dependency property.</summary>
public static readonly DependencyProperty ShowAboveRibbonProperty =
DependencyProperty.Register(nameof(ShowAboveRibbon), typeof(bool),
typeof(QuickAccessToolBar), new PropertyMetadata(BooleanBoxes.TrueBox));
#endregion
#region CanQuickAccessLocationChanging
/// <summary>
/// Gets or sets whether user can change location of QAT
/// </summary>
public bool CanQuickAccessLocationChanging
{
get { return (bool)this.GetValue(CanQuickAccessLocationChangingProperty); }
set { this.SetValue(CanQuickAccessLocationChangingProperty, value); }
}
/// <summary>Identifies the <see cref="CanQuickAccessLocationChanging"/> dependency property.</summary>
public static readonly DependencyProperty CanQuickAccessLocationChangingProperty =
DependencyProperty.Register(nameof(CanQuickAccessLocationChanging), typeof(bool), typeof(QuickAccessToolBar), new PropertyMetadata(BooleanBoxes.TrueBox));
#endregion
#region DropDownVisibility
/// <summary>
/// Gets or sets whether the Menu-DropDown is visible or not.
/// </summary>
public bool IsMenuDropDownVisible
{
get { return (bool)this.GetValue(IsMenuDropDownVisibleProperty); }
set { this.SetValue(IsMenuDropDownVisibleProperty, value); }
}
/// <summary>Identifies the <see cref="IsMenuDropDownVisible"/> dependency property.</summary>
public static readonly DependencyProperty IsMenuDropDownVisibleProperty =
DependencyProperty.Register(nameof(IsMenuDropDownVisible), typeof(bool), typeof(QuickAccessToolBar), new FrameworkPropertyMetadata(BooleanBoxes.TrueBox, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure, OnIsMenuDropDownVisibleChanged));
private static void OnIsMenuDropDownVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (QuickAccessToolBar)d;
if ((bool)e.NewValue == false)
{
control.cachedMenuDownButtonWidth = 0;
}
}
#endregion DropDownVisibility
#endregion
#region Initialization
/// <summary>
/// Static constructor
/// </summary>
static QuickAccessToolBar()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(QuickAccessToolBar), new FrameworkPropertyMetadata(typeof(QuickAccessToolBar)));
}
/// <summary>
/// Creates a new instance.
/// </summary>
public QuickAccessToolBar()
{
this.Loaded += (sender, args) => this.InvalidateMeasureOfTitleBar();
}
#endregion
#region Override
/// <inheritdoc />
public override void OnApplyTemplate()
{
if (this.showAbove != null)
{
this.showAbove.Click -= this.OnShowAboveClick;
}
if (this.showBelow != null)
{
this.showBelow.Click -= this.OnShowBelowClick;
}
this.showAbove = this.GetTemplateChild("PART_ShowAbove") as MenuItem;
this.showBelow = this.GetTemplateChild("PART_ShowBelow") as MenuItem;
if (this.showAbove != null)
{
this.showAbove.Click += this.OnShowAboveClick;
}
if (this.showBelow != null)
{
this.showBelow.Click += this.OnShowBelowClick;
}
if (this.MenuDownButton != null)
{
foreach (var item in this.QuickAccessItems)
{
this.MenuDownButton.Items.Remove(item);
item.InvalidateProperty(QuickAccessMenuItem.TargetProperty);
}
this.QuickAccessItems.AquireLogicalOwnership();
}
this.MenuDownButton = this.GetTemplateChild("PART_MenuDownButton") as DropDownButton;
if (this.MenuDownButton != null)
{
this.QuickAccessItems.ReleaseLogicalOwnership();
for (var i = 0; i < this.QuickAccessItems.Count; i++)
{
this.MenuDownButton.Items.Insert(i + 1, this.QuickAccessItems[i]);
this.QuickAccessItems[i].InvalidateProperty(QuickAccessMenuItem.TargetProperty);
}
}
this.toolBarDownButton = this.GetTemplateChild("PART_ToolbarDownButton") as DropDownButton;
// ToolBar panels
this.toolBarPanel = this.GetTemplateChild("PART_ToolBarPanel") as Panel;
this.toolBarOverflowPanel = this.GetTemplateChild("PART_ToolBarOverflowPanel") as Panel;
if (this.rootPanel != null)
{
this.RemoveLogicalChild(this.rootPanel);
}
this.rootPanel = this.GetTemplateChild("PART_RootPanel") as Panel;
if (this.rootPanel != null)
{
this.AddLogicalChild(this.rootPanel);
}
// Clears cache
this.cachedMenuDownButtonWidth = 0;
this.cachedOverflowDownButtonWidth = 0;
this.cachedNonOverflowItemsCount = this.GetNonOverflowItemsCount(this.ActualWidth);
this.cachedConstraint = default;
}
/// <summary>
/// Handles show below menu item click
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">The event data</param>
private void OnShowBelowClick(object sender, RoutedEventArgs e)
{
this.ShowAboveRibbon = false;
}
/// <summary>
/// Handles show above menu item click
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">The event data</param>
private void OnShowAboveClick(object sender, RoutedEventArgs e)
{
this.ShowAboveRibbon = true;
}
/// <inheritdoc />
protected override Size MeasureOverride(Size constraint)
{
if (this.IsLoaded == false)
{
return base.MeasureOverride(constraint);
}
if ((this.cachedConstraint == constraint)
&& !this.itemsHadChanged)
{
return base.MeasureOverride(constraint);
}
var nonOverflowItemsCount = this.GetNonOverflowItemsCount(constraint.Width);
if (this.itemsHadChanged == false
&& nonOverflowItemsCount == this.cachedNonOverflowItemsCount)
{
return base.MeasureOverride(constraint);
}
this.cachedNonOverflowItemsCount = nonOverflowItemsCount;
this.UpdateHasOverflowItems();
this.cachedConstraint = constraint;
// Clear overflow panel to prevent items from having a visual/logical parent
this.toolBarOverflowPanel.Children.Clear();
if (this.itemsHadChanged)
{
// Refill toolbar
this.toolBarPanel.Children.Clear();
for (var i = 0; i < this.cachedNonOverflowItemsCount; i++)
{
this.toolBarPanel.Children.Add(this.Items[i]);
}
this.itemsHadChanged = false;
}
else
{
if (this.cachedNonOverflowItemsCount > this.toolBarPanel.Children.Count)
{
// Add needed items
var savedCount = this.toolBarPanel.Children.Count;
for (var i = savedCount; i < this.cachedNonOverflowItemsCount; i++)
{
this.toolBarPanel.Children.Add(this.Items[i]);
}
}
else
{
// Remove nonneeded items
for (var i = this.toolBarPanel.Children.Count - 1; i >= this.cachedNonOverflowItemsCount; i--)
{
this.toolBarPanel.Children.Remove(this.Items[i]);
}
}
}
// Move overflowing items to overflow panel
for (var i = this.cachedNonOverflowItemsCount; i < this.Items.Count; i++)
{
this.toolBarOverflowPanel.Children.Add(this.Items[i]);
}
if (constraint.Equals(SizeConstants.Infinite))
{
this.toolBarPanel.Measure(constraint);
}
else
{
// It seems strange that we have to explicitly measure the toolbar panel, but if we don't do that the base measure call does not seem to measure correctly...
if (this.cachedNonOverflowItemsCount > 0)
{
this.toolBarPanel.Measure(new Size(Math.Max(0, constraint.Width - this.cachedMenuDownButtonWidth), constraint.Height));
}
else
{
this.toolBarPanel.Measure(new Size(Math.Max(0, constraint.Width - this.cachedOverflowDownButtonWidth), constraint.Height));
}
}
return base.MeasureOverride(constraint);
}
/// <summary>
/// We have to use this function because setting a <see cref="DependencyProperty"/> very frequently is quite expensive
/// </summary>
private void UpdateHasOverflowItems()
{
var newValue = this.cachedNonOverflowItemsCount < this.Items.Count;
// ReSharper disable RedundantCheckBeforeAssignment
if (this.HasOverflowItems != newValue)
// ReSharper restore RedundantCheckBeforeAssignment
{
// todo: code runs very often on startup
this.HasOverflowItems = newValue;
}
}
#endregion
#region Methods
/// <summary>
/// First calls <see cref="UIElement.InvalidateMeasure"/> and then <see cref="InvalidateMeasureOfTitleBar"/>
/// </summary>
public void Refresh()
{
this.InvalidateMeasure();
this.InvalidateMeasureOfTitleBar();
}
private void InvalidateMeasureOfTitleBar()
{
if (this.IsLoaded == false)
{
return;
}
var titleBar = RibbonControl.GetParentRibbon(this)?.TitleBar
?? UIHelper.GetParent<RibbonTitleBar>(this);
titleBar?.ForceMeasureAndArrange();
}
/// <summary>
/// Gets or sets a custom action to generate KeyTips for items in this control.
/// </summary>
public Action<QuickAccessToolBar> UpdateKeyTipsAction
{
get { return (Action<QuickAccessToolBar>)this.GetValue(UpdateKeyTipsActionProperty); }
set { this.SetValue(UpdateKeyTipsActionProperty, value); }
}
/// <summary>Identifies the <see cref="UpdateKeyTipsAction"/> dependency property.</summary>
public static readonly DependencyProperty UpdateKeyTipsActionProperty =
DependencyProperty.Register(nameof(UpdateKeyTipsAction), typeof(Action<QuickAccessToolBar>), typeof(QuickAccessToolBar), new PropertyMetadata(OnUpdateKeyTipsActionChanged));
private static void OnUpdateKeyTipsActionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var quickAccessToolBar = (QuickAccessToolBar)d;
quickAccessToolBar.UpdateKeyTips();
}
private void UpdateKeyTips()
{
if (this.UpdateKeyTipsAction is null)
{
DefaultUpdateKeyTips(this);
return;
}
this.UpdateKeyTipsAction(this);
}
// Updates keys for keytip access
private static void DefaultUpdateKeyTips(QuickAccessToolBar quickAccessToolBar)
{
for (var i = 0; i < Math.Min(9, quickAccessToolBar.Items.Count); i++)
{
// 1, 2, 3, ... , 9
KeyTip.SetKeys(quickAccessToolBar.Items[i], (i + 1).ToString(CultureInfo.InvariantCulture));
}
for (var i = 9; i < Math.Min(18, quickAccessToolBar.Items.Count); i++)
{
// 09, 08, 07, ... , 01
KeyTip.SetKeys(quickAccessToolBar.Items[i], "0" + (18 - i).ToString(CultureInfo.InvariantCulture));
}
var startChar = 'A';
for (var i = 18; i < Math.Min(9 + 9 + 26, quickAccessToolBar.Items.Count); i++)
{
// 0A, 0B, 0C, ... , 0Z
KeyTip.SetKeys(quickAccessToolBar.Items[i], "0" + startChar++);
}
}
private int GetNonOverflowItemsCount(in double width)
{
// Cache width of menuDownButton
if (DoubleUtil.AreClose(this.cachedMenuDownButtonWidth, 0)
&& this.rootPanel != null
&& this.MenuDownButton != null
&& this.IsMenuDropDownVisible)
{
this.rootPanel.Measure(SizeConstants.Infinite);
this.cachedMenuDownButtonWidth = this.MenuDownButton.DesiredSize.Width;
}
// Cache width of toolBarDownButton
if (DoubleUtil.AreClose(this.cachedOverflowDownButtonWidth, 0)
&& this.rootPanel != null
&& this.MenuDownButton != null)
{
this.rootPanel.Measure(SizeConstants.Infinite);
this.cachedOverflowDownButtonWidth = this.toolBarDownButton.DesiredSize.Width;
}
// If IsMenuDropDownVisible is true we have less width available
var widthReductionWhenNotCompressed = this.IsMenuDropDownVisible ? this.cachedMenuDownButtonWidth : 0;
return CalculateNonOverflowItems(this.Items, width, widthReductionWhenNotCompressed, this.cachedOverflowDownButtonWidth);
}
private static int CalculateNonOverflowItems(IList<UIElement> items, double maxAvailableWidth, double widthReductionWhenNotCompressed, double widthReductionWhenCompressed)
{
// Calculate how many items we can fit into the available width
var maxPossibleItems = GetMaxPossibleItems(maxAvailableWidth - widthReductionWhenNotCompressed, true);
if (maxPossibleItems < items.Count)
{
// If we can't fit all items into the available width
// we have to reduce the available width as the overflow button also needs space.
var availableWidth = maxAvailableWidth - widthReductionWhenCompressed;
return GetMaxPossibleItems(availableWidth, false);
}
return items.Count;
int GetMaxPossibleItems(double availableWidth, bool measureItems)
{
var currentWidth = 0D;
for (var i = 0; i < items.Count; i++)
{
var currentItem = items[i];
if (measureItems)
{
currentItem.Measure(SizeConstants.Infinite);
}
currentWidth += currentItem.DesiredSize.Width;
if (currentWidth > availableWidth)
{
return i;
}
}
return items.Count;
}
}
#endregion
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() => new Fluent.Automation.Peers.RibbonQuickAccessToolBarAutomationPeer(this);
/// <inheritdoc />
void ILogicalChildSupport.AddLogicalChild(object child)
{
this.AddLogicalChild(child);
}
/// <inheritdoc />
void ILogicalChildSupport.RemoveLogicalChild(object child)
{
this.RemoveLogicalChild(child);
}
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
yield return this.rootPanel;
foreach (var item in this.QuickAccessItems.GetLogicalChildren())
{
yield return item;
}
}
}
}
}

View File

@@ -0,0 +1,218 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Collections;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Data;
using System.Windows.Markup;
using Fluent.Helpers;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents Fluent UI specific RadioButton
/// </summary>
[ContentProperty(nameof(Header))]
public class RadioButton : System.Windows.Controls.RadioButton, IRibbonControl, IQuickAccessItemProvider, ILargeIconProvider
{
#region Properties
#region Size
/// <inheritdoc />
public RibbonControlSize Size
{
get { return (RibbonControlSize)this.GetValue(SizeProperty); }
set { this.SetValue(SizeProperty, value); }
}
/// <summary>Identifies the <see cref="Size"/> dependency property.</summary>
public static readonly DependencyProperty SizeProperty = RibbonProperties.SizeProperty.AddOwner(typeof(RadioButton));
#endregion
#region SizeDefinition
/// <inheritdoc />
public RibbonControlSizeDefinition SizeDefinition
{
get { return (RibbonControlSizeDefinition)this.GetValue(SizeDefinitionProperty); }
set { this.SetValue(SizeDefinitionProperty, value); }
}
/// <summary>Identifies the <see cref="SizeDefinition"/> dependency property.</summary>
public static readonly DependencyProperty SizeDefinitionProperty = RibbonProperties.SizeDefinitionProperty.AddOwner(typeof(RadioButton));
#endregion
#region KeyTip
/// <inheritdoc />
public string KeyTip
{
get { return (string)this.GetValue(KeyTipProperty); }
set { this.SetValue(KeyTipProperty, value); }
}
/// <summary>
/// Using a DependencyProperty as the backing store for Keys.
/// This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty KeyTipProperty = Fluent.KeyTip.KeysProperty.AddOwner(typeof(RadioButton));
#endregion
#region Header
/// <inheritdoc />
public object Header
{
get { return this.GetValue(HeaderProperty); }
set { this.SetValue(HeaderProperty, value); }
}
/// <summary>Identifies the <see cref="Header"/> dependency property.</summary>
public static readonly DependencyProperty HeaderProperty = RibbonControl.HeaderProperty.AddOwner(typeof(RadioButton), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region Icon
/// <inheritdoc />
public object Icon
{
get { return this.GetValue(IconProperty); }
set { this.SetValue(IconProperty, value); }
}
/// <summary>Identifies the <see cref="Icon"/> dependency property.</summary>
public static readonly DependencyProperty IconProperty = RibbonControl.IconProperty.AddOwner(typeof(RadioButton), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region LargeIcon
/// <inheritdoc />
public object LargeIcon
{
get { return this.GetValue(LargeIconProperty); }
set { this.SetValue(LargeIconProperty, value); }
}
/// <summary>Identifies the <see cref="LargeIcon"/> dependency property.</summary>
public static readonly DependencyProperty LargeIconProperty = LargeIconProviderProperties.LargeIconProperty.AddOwner(typeof(RadioButton), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#endregion
#region Constructors
/// <summary>
/// Static constructor
/// </summary>
static RadioButton()
{
var type = typeof(RadioButton);
DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(type));
ContextMenuService.Attach(type);
ToolTipService.Attach(type);
}
/// <summary>
/// Default constructor
/// </summary>
public RadioButton()
{
ContextMenuService.Coerce(this);
}
#endregion
#region Quick Access Item Creating
/// <inheritdoc />
public virtual FrameworkElement CreateQuickAccessItem()
{
var button = new RadioButton();
RibbonControl.Bind(this, button, nameof(this.IsChecked), IsCheckedProperty, BindingMode.TwoWay);
button.Click += (sender, e) => this.RaiseEvent(e);
RibbonControl.BindQuickAccessItem(this, button);
return button;
}
/// <inheritdoc />
public bool CanAddToQuickAccessToolBar
{
get { return (bool)this.GetValue(CanAddToQuickAccessToolBarProperty); }
set { this.SetValue(CanAddToQuickAccessToolBarProperty, value); }
}
/// <summary>Identifies the <see cref="CanAddToQuickAccessToolBar"/> dependency property.</summary>
public static readonly DependencyProperty CanAddToQuickAccessToolBarProperty = RibbonControl.CanAddToQuickAccessToolBarProperty.AddOwner(typeof(RadioButton), new PropertyMetadata(BooleanBoxes.TrueBox, RibbonControl.OnCanAddToQuickAccessToolBarChanged));
#endregion
#region Implementation of IKeyTipedControl
/// <inheritdoc />
public KeyTipPressedResult OnKeyTipPressed()
{
this.OnClick();
return KeyTipPressedResult.Empty;
}
/// <inheritdoc />
public void OnKeyTipBack()
{
}
#endregion
/// <inheritdoc />
void ILogicalChildSupport.AddLogicalChild(object child)
{
this.AddLogicalChild(child);
}
/// <inheritdoc />
void ILogicalChildSupport.RemoveLogicalChild(object child)
{
this.RemoveLogicalChild(child);
}
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.Icon != null)
{
yield return this.Icon;
}
if (this.LargeIcon != null)
{
yield return this.LargeIcon;
}
if (this.Header != null)
{
yield return this.Header;
}
}
}
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() => new Fluent.Automation.Peers.RibbonRadioButtonAutomationPeer(this);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,134 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Fluent.Internal;
/// <summary>
/// Represents contextual groups container
/// </summary>
public class RibbonContextualGroupsContainer : Panel
{
private readonly List<Size> sizes = new List<Size>();
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize)
{
var finalRect = new Rect(finalSize);
var index = 0;
foreach (UIElement item in this.InternalChildren)
{
finalRect.Width = this.sizes[index].Width; //item.DesiredSize.Width;
finalRect.Height = Math.Max(finalSize.Height, this.sizes[index].Height); //Math.Max(finalSize.Height, item.DesiredSize.Height);
item.Arrange(finalRect);
finalRect.X += this.sizes[index].Width; // item.DesiredSize.Width;
index++;
}
return finalSize;
}
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize)
{
var allGroupsWidth = 0D;
this.sizes.Clear();
var availableSizeHeight = availableSize.Height;
if (double.IsPositiveInfinity(availableSizeHeight))
{
availableSizeHeight = 0;
}
foreach (RibbonContextualTabGroup contextualGroup in this.InternalChildren)
{
// Calculate width of tab items of the group
var tabsWidth = 0D;
// We have to look at visible and items which already got measured only
var visibleItems = contextualGroup.Items.Where(item => item.Visibility == Visibility.Visible && DoubleUtil.AreClose(item.DesiredSize.Width, 0) == false).ToList();
foreach (var item in visibleItems)
{
tabsWidth += item.DesiredSize.Width;
}
contextualGroup.Measure(SizeConstants.Infinite);
var groupWidth = contextualGroup.DesiredSize.Width;
var tabWasChanged = false;
if (groupWidth > tabsWidth)
{
// If tab's width is less than group's width we have to stretch tabs
var delta = (groupWidth - tabsWidth) / visibleItems.Count;
foreach (var item in visibleItems)
{
var newDesiredWidth = item.DesiredSize.Width + delta;
// Update cached DesiredWidth
if (DoubleUtil.AreClose(newDesiredWidth, item.DesiredWidth) == false)
{
item.DesiredWidth = newDesiredWidth;
item.Measure(new Size(item.DesiredWidth, item.DesiredSize.Height));
tabWasChanged = true;
}
}
}
if (tabWasChanged)
{
// If we have changed tabs layout we have
// to invalidate down to RibbonTabsContainer
var visual = visibleItems[0] as Visual;
while (visual != null)
{
if (visual is UIElement uiElement)
{
if (uiElement is RibbonTabsContainer)
{
uiElement.InvalidateMeasure();
break;
}
uiElement.InvalidateMeasure();
}
visual = VisualTreeHelper.GetParent(visual) as Visual;
}
tabsWidth = 0;
foreach (var item in visibleItems)
{
tabsWidth += item.DesiredSize.Width;
}
}
// Calc final width and measure the group using it
var finalWidth = tabsWidth;
allGroupsWidth += finalWidth;
if (allGroupsWidth > availableSize.Width)
{
finalWidth -= allGroupsWidth - availableSize.Width;
allGroupsWidth = availableSize.Width;
}
contextualGroup.Measure(new Size(Math.Max(0, finalWidth), availableSizeHeight));
this.sizes.Add(new Size(Math.Max(0, finalWidth), availableSizeHeight));
}
return new Size(allGroupsWidth, availableSizeHeight);
}
}
}

View File

@@ -0,0 +1,324 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Fluent.Extensions;
using Fluent.Internal;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents contextual tab group
/// </summary>
public class RibbonContextualTabGroup : Control
{
#region Properties
/// <summary>Identifies the <see cref="TabItemSelectedForeground"/> dependency property.</summary>
public static readonly DependencyProperty TabItemSelectedForegroundProperty = DependencyProperty.Register(nameof(TabItemSelectedForeground), typeof(Brush), typeof(RibbonContextualTabGroup), new PropertyMetadata(default(Brush)));
/// <summary>
/// Gets or sets the foreground brush to be used for a selected <see cref="RibbonTabItem"/> belonging to this group.
/// </summary>
public Brush TabItemSelectedForeground
{
get { return (Brush)this.GetValue(TabItemSelectedForegroundProperty); }
set { this.SetValue(TabItemSelectedForegroundProperty, value); }
}
/// <summary>Identifies the <see cref="TabItemMouseOverForeground"/> dependency property.</summary>
public static readonly DependencyProperty TabItemMouseOverForegroundProperty = DependencyProperty.Register(nameof(TabItemMouseOverForeground), typeof(Brush), typeof(RibbonContextualTabGroup), new PropertyMetadata(default(Brush)));
/// <summary>
/// Gets or sets the foreground brush to be used when the mouse is over a <see cref="RibbonTabItem"/> belonging to this group.
/// </summary>
public Brush TabItemMouseOverForeground
{
get { return (Brush)this.GetValue(TabItemMouseOverForegroundProperty); }
set { this.SetValue(TabItemMouseOverForegroundProperty, value); }
}
/// <summary>Identifies the <see cref="TabItemSelectedMouseOverForeground"/> dependency property.</summary>
public static readonly DependencyProperty TabItemSelectedMouseOverForegroundProperty = DependencyProperty.Register(nameof(TabItemSelectedMouseOverForeground), typeof(Brush), typeof(RibbonContextualTabGroup), new PropertyMetadata(default(Brush)));
/// <summary>
/// Gets or sets the foreground brush to be used when the mouse is over a selected <see cref="RibbonTabItem"/> belonging to this group.
/// </summary>
public Brush TabItemSelectedMouseOverForeground
{
get { return (Brush)this.GetValue(TabItemSelectedMouseOverForegroundProperty); }
set { this.SetValue(TabItemSelectedMouseOverForegroundProperty, value); }
}
/// <summary>
/// Gets or sets group header
/// </summary>
public string Header
{
get { return (string)this.GetValue(HeaderProperty); }
set { this.SetValue(HeaderProperty, value); }
}
/// <summary>Identifies the <see cref="Header"/> dependency property.</summary>
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register(nameof(Header), typeof(string), typeof(RibbonContextualTabGroup),
new PropertyMetadata("RibbonContextualTabGroup", OnHeaderChanged));
/// <summary>
/// Handles header chages
/// </summary>
/// <param name="d">Object</param>
/// <param name="e">The event data.</param>
private static void OnHeaderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
/// <summary>
/// Gets collection of tab items
/// </summary>
public List<RibbonTabItem> Items { get; } = new List<RibbonTabItem>();
/// <summary>
/// Gets or sets the visibility this group for internal use (this enables us to hide this group when all items in this group are hidden)
/// </summary>
public Visibility InnerVisibility
{
get { return (Visibility)this.GetValue(InnerVisibilityProperty); }
private set { this.SetValue(InnerVisibilityPropertyKey, value); }
}
private static readonly DependencyPropertyKey InnerVisibilityPropertyKey =
DependencyProperty.RegisterReadOnly(nameof(InnerVisibility), typeof(Visibility), typeof(RibbonContextualTabGroup), new PropertyMetadata(Visibility.Visible, OnInnerVisibilityChanged));
/// <summary>Identifies the <see cref="InnerVisibility"/> dependency property.</summary>
public static readonly DependencyProperty InnerVisibilityProperty = InnerVisibilityPropertyKey.DependencyProperty;
private static void OnInnerVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var contextGroup = (RibbonContextualTabGroup)d;
if (contextGroup.IsLoaded == false)
{
return;
}
// Delaying forced redraw fixes #536
contextGroup.RunInDispatcherAsync(() => ForceRedraw(contextGroup));
}
/// <summary>
/// Gets the first visible TabItem in this group
/// </summary>
public RibbonTabItem FirstVisibleItem => this.GetFirstVisibleItem();
/// <summary>
/// Gets the first visible TabItem in this group
/// </summary>
public RibbonTabItem FirstVisibleAndEnabledItem => this.GetFirstVisibleAndEnabledItem();
/// <summary>
/// Gets the last visible TabItem in this group
/// </summary>
public RibbonTabItem LastVisibleItem => this.GetLastVisibleItem();
#endregion
#region Initialization
/// <summary>
/// Static constructor
/// </summary>
static RibbonContextualTabGroup()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonContextualTabGroup), new FrameworkPropertyMetadata(typeof(RibbonContextualTabGroup)));
VisibilityProperty.OverrideMetadata(typeof(RibbonContextualTabGroup), new PropertyMetadata(Visibility.Collapsed, OnVisibilityChanged));
}
/// <summary>
/// Handles visibility prioperty changed
/// </summary>
/// <param name="d">Object</param>
/// <param name="e">The event data</param>
private static void OnVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var group = (RibbonContextualTabGroup)d;
group.UpdateInnerVisiblityAndGroupBorders();
ForceRedraw(group);
}
/// <summary>
/// Default constructor
/// </summary>
public RibbonContextualTabGroup()
{
this.Loaded += this.OnLoaded;
this.Unloaded += this.OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
this.SubscribeEvents();
this.UpdateInnerVisibility();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
this.UnSubscribeEvents();
}
private void SubscribeEvents()
{
// Always unsubscribe events to ensure we don't subscribe twice
this.UnSubscribeEvents();
}
private void UnSubscribeEvents()
{
}
#endregion
#region Internal Methods
/// <summary>
/// Appends tab item
/// </summary>
/// <param name="item">Ribbon tab item</param>
internal void AppendTabItem(RibbonTabItem item)
{
this.Items.Add(item);
this.UpdateInnerVisiblityAndGroupBorders();
}
/// <summary>
/// Removes tab item
/// </summary>
/// <param name="item">Ribbon tab item</param>
internal void RemoveTabItem(RibbonTabItem item)
{
this.Items.Remove(item);
this.UpdateInnerVisiblityAndGroupBorders();
}
private RibbonTabItem GetFirstVisibleItem()
{
return this.Items.FirstOrDefault(item => item.Visibility == Visibility.Visible);
}
private RibbonTabItem GetLastVisibleItem()
{
return this.Items.LastOrDefault(item => item.Visibility == Visibility.Visible);
}
private RibbonTabItem GetFirstVisibleAndEnabledItem()
{
return this.Items.FirstOrDefault(item => item.Visibility == Visibility.Visible && item.IsEnabled);
}
/// <summary>
/// Updates the group border
/// </summary>
public void UpdateInnerVisiblityAndGroupBorders()
{
this.UpdateInnerVisibility();
var leftset = false;
var rightset = false;
for (var i = 0; i < this.Items.Count; i++)
{
//if (i == 0) items[i].HasLeftGroupBorder = true;
//else items[i].HasLeftGroupBorder = false;
//if (i == items.Count - 1) items[i].HasRightGroupBorder = true;
//else items[i].HasRightGroupBorder = false;
//Workaround so you can have inivisible Tabs on a Group
if (this.Items[i].Visibility == Visibility.Visible
&& leftset == false)
{
this.Items[i].HasLeftGroupBorder = true;
leftset = true;
}
else
{
this.Items[i].HasLeftGroupBorder = false;
}
if (this.Items[this.Items.Count - 1 - i].Visibility == Visibility.Visible
&& rightset == false)
{
this.Items[this.Items.Count - 1 - i].HasRightGroupBorder = true;
rightset = true;
}
else
{
this.Items[this.Items.Count - 1 - i].HasRightGroupBorder = false;
}
}
}
#endregion
#region Override
/// <inheritdoc />
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
var firstVisibleItem = this.FirstVisibleAndEnabledItem;
if (e.ClickCount == 1
&& firstVisibleItem != null)
{
if (firstVisibleItem.TabControlParent?.SelectedItem is RibbonTabItem currentSelectedItem)
{
currentSelectedItem.IsSelected = false;
}
e.Handled = true;
if (firstVisibleItem.TabControlParent != null)
{
if (firstVisibleItem.TabControlParent.IsMinimized)
{
firstVisibleItem.TabControlParent.IsMinimized = false;
}
firstVisibleItem.IsSelected = true;
}
}
base.OnMouseLeftButtonUp(e);
}
#endregion
/// <summary>
/// Updates the Visibility of the inner container
/// </summary>
private void UpdateInnerVisibility()
{
this.InnerVisibility = this.Visibility == Visibility.Visible && this.Items.Any(item => item.Visibility == Visibility.Visible)
? Visibility.Visible
: Visibility.Collapsed;
}
private static void ForceRedraw(RibbonContextualTabGroup contextGroup)
{
contextGroup.ForceMeasure();
foreach (var ribbonTabItem in contextGroup.Items)
{
ribbonTabItem.ForceMeasure();
}
var ribbonTitleBar = UIHelper.GetParent<RibbonTitleBar>(contextGroup);
ribbonTitleBar?.ForceMeasureAndArrange();
}
}
}

View File

@@ -0,0 +1,506 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using ControlzEx.Standard;
using Fluent.Extensions;
using Fluent.Helpers;
using Fluent.Internal;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represent base class for Fluent controls
/// </summary>
public abstract class RibbonControl : Control, ICommandSource, IQuickAccessItemProvider, IRibbonControl
{
#region KeyTip
/// <inheritdoc />
public string KeyTip
{
get { return (string)this.GetValue(KeyTipProperty); }
set { this.SetValue(KeyTipProperty, value); }
}
/// <summary>
/// Using a DependencyProperty as the backing store for Keys.
/// This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty KeyTipProperty = Fluent.KeyTip.KeysProperty.AddOwner(typeof(RibbonControl));
#endregion
#region Header
/// <inheritdoc />
public object Header
{
get { return this.GetValue(HeaderProperty); }
set { this.SetValue(HeaderProperty, value); }
}
/// <summary>Identifies the <see cref="Header"/> dependency property.</summary>
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register(nameof(Header), typeof(object), typeof(RibbonControl), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region Icon
/// <inheritdoc />
public object Icon
{
get { return this.GetValue(IconProperty); }
set { this.SetValue(IconProperty, value); }
}
/// <summary>Identifies the <see cref="Icon"/> dependency property.</summary>
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(object), typeof(RibbonControl), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region Command
private bool currentCanExecute = true;
/// <inheritdoc />
[Category("Action")]
[Localizability(LocalizationCategory.NeverLocalize)]
[Bindable(true)]
public ICommand Command
{
get
{
return (ICommand)this.GetValue(CommandProperty);
}
set
{
this.SetValue(CommandProperty, value);
}
}
/// <inheritdoc />
[Bindable(true)]
[Localizability(LocalizationCategory.NeverLocalize)]
[Category("Action")]
public object CommandParameter
{
get
{
return this.GetValue(CommandParameterProperty);
}
set
{
this.SetValue(CommandParameterProperty, value);
}
}
/// <inheritdoc />
[Bindable(true)]
[Category("Action")]
public IInputElement CommandTarget
{
get
{
return (IInputElement)this.GetValue(CommandTargetProperty);
}
set
{
this.SetValue(CommandTargetProperty, value);
}
}
/// <summary>Identifies the <see cref="CommandParameter"/> dependency property.</summary>
public static readonly DependencyProperty CommandParameterProperty = ButtonBase.CommandParameterProperty.AddOwner(typeof(RibbonControl), new PropertyMetadata());
/// <summary>Identifies the <see cref="Command"/> dependency property.</summary>
public static readonly DependencyProperty CommandProperty = ButtonBase.CommandProperty.AddOwner(typeof(RibbonControl), new PropertyMetadata(OnCommandChanged));
/// <summary>Identifies the <see cref="CommandTarget"/> dependency property.</summary>
public static readonly DependencyProperty CommandTargetProperty = ButtonBase.CommandTargetProperty.AddOwner(typeof(RibbonControl), new PropertyMetadata());
/// <summary>
/// Handles Command changed
/// </summary>
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as RibbonControl;
if (control is null)
{
return;
}
if (e.OldValue is ICommand oldCommand)
{
oldCommand.CanExecuteChanged -= control.OnCommandCanExecuteChanged;
}
if (e.NewValue is ICommand newCommand)
{
newCommand.CanExecuteChanged += control.OnCommandCanExecuteChanged;
if (e.NewValue is RoutedUICommand routedUiCommand
&& control.Header is null)
{
control.Header = routedUiCommand.Text;
}
}
control.UpdateCanExecute();
}
/// <summary>
/// Handles Command CanExecute changed
/// </summary>
private void OnCommandCanExecuteChanged(object sender, EventArgs e)
{
this.UpdateCanExecute();
}
private void UpdateCanExecute()
{
var canExecute = this.Command != null
&& this.CanExecuteCommand();
if (this.currentCanExecute != canExecute)
{
this.currentCanExecute = canExecute;
this.CoerceValue(IsEnabledProperty);
}
}
#endregion
#region IsEnabled
/// <inheritdoc />
protected override bool IsEnabledCore => base.IsEnabledCore && (this.currentCanExecute || this.Command is null);
#endregion
#region Size
/// <inheritdoc />
public RibbonControlSize Size
{
get { return (RibbonControlSize)this.GetValue(SizeProperty); }
set { this.SetValue(SizeProperty, value); }
}
/// <summary>Identifies the <see cref="Size"/> dependency property.</summary>
public static readonly DependencyProperty SizeProperty = RibbonProperties.SizeProperty.AddOwner(typeof(RibbonControl));
#endregion
#region SizeDefinition
/// <inheritdoc />
public RibbonControlSizeDefinition SizeDefinition
{
get { return (RibbonControlSizeDefinition)this.GetValue(SizeDefinitionProperty); }
set { this.SetValue(SizeDefinitionProperty, value); }
}
/// <summary>Identifies the <see cref="SizeDefinition"/> dependency property.</summary>
public static readonly DependencyProperty SizeDefinitionProperty = RibbonProperties.SizeDefinitionProperty.AddOwner(typeof(RibbonControl));
#endregion
#region Constructors
/// <summary>
/// Static constructor
/// </summary>
static RibbonControl()
{
var type = typeof(RibbonControl);
ContextMenuService.Attach(type);
ToolTipService.Attach(type);
}
/// <summary>
/// Default Constructor
/// </summary>
protected RibbonControl()
{
ContextMenuService.Coerce(this);
}
#endregion
#region QuickAccess
/// <inheritdoc />
public abstract FrameworkElement CreateQuickAccessItem();
/// <summary>
/// Binds default properties of control to quick access element
/// </summary>
/// <param name="source">Source item</param>
/// <param name="element">Toolbar item</param>
public static void BindQuickAccessItem(FrameworkElement source, FrameworkElement element)
{
Bind(source, element, nameof(source.DataContext), DataContextProperty, BindingMode.OneWay);
if (source is ICommandSource)
{
if (source is MenuItem)
{
Bind(source, element, nameof(ICommandSource.CommandParameter), System.Windows.Controls.MenuItem.CommandParameterProperty, BindingMode.OneWay);
Bind(source, element, nameof(ICommandSource.CommandTarget), System.Windows.Controls.MenuItem.CommandTargetProperty, BindingMode.OneWay);
Bind(source, element, nameof(ICommandSource.Command), System.Windows.Controls.MenuItem.CommandProperty, BindingMode.OneWay);
}
else
{
Bind(source, element, nameof(ICommandSource.CommandParameter), ButtonBase.CommandParameterProperty, BindingMode.OneWay);
Bind(source, element, nameof(ICommandSource.CommandTarget), ButtonBase.CommandTargetProperty, BindingMode.OneWay);
Bind(source, element, nameof(ICommandSource.Command), ButtonBase.CommandProperty, BindingMode.OneWay);
}
}
Bind(source, element, nameof(FontFamily), FontFamilyProperty, BindingMode.OneWay);
Bind(source, element, nameof(FontSize), FontSizeProperty, BindingMode.OneWay);
Bind(source, element, nameof(FontStretch), FontStretchProperty, BindingMode.OneWay);
Bind(source, element, nameof(FontStyle), FontStyleProperty, BindingMode.OneWay);
Bind(source, element, nameof(FontWeight), FontWeightProperty, BindingMode.OneWay);
Bind(source, element, nameof(Foreground), ForegroundProperty, BindingMode.OneWay);
Bind(source, element, nameof(IsEnabled), IsEnabledProperty, BindingMode.OneWay);
Bind(source, element, nameof(Opacity), OpacityProperty, BindingMode.OneWay);
Bind(source, element, nameof(SnapsToDevicePixels), SnapsToDevicePixelsProperty, BindingMode.OneWay);
Bind(source, element, new PropertyPath(FocusManager.IsFocusScopeProperty), FocusManager.IsFocusScopeProperty, BindingMode.OneWay);
if (source is IHeaderedControl headeredControl)
{
if (headeredControl is HeaderedItemsControl)
{
Bind(source, element, nameof(HeaderedItemsControl.Header), HeaderedItemsControl.HeaderProperty, BindingMode.OneWay);
Bind(source, element, nameof(HeaderedItemsControl.HeaderStringFormat), HeaderedItemsControl.HeaderStringFormatProperty, BindingMode.OneWay);
Bind(source, element, nameof(HeaderedItemsControl.HeaderTemplate), HeaderedItemsControl.HeaderTemplateProperty, BindingMode.OneWay);
Bind(source, element, nameof(HeaderedItemsControl.HeaderTemplateSelector), HeaderedItemsControl.HeaderTemplateSelectorProperty, BindingMode.OneWay);
}
else
{
Bind(source, element, nameof(IHeaderedControl.Header), HeaderProperty, BindingMode.OneWay);
}
if (source.ToolTip != null
|| BindingOperations.IsDataBound(source, ToolTipProperty))
{
Bind(source, element, nameof(ToolTip), ToolTipProperty, BindingMode.OneWay);
}
else
{
Bind(source, element, nameof(IHeaderedControl.Header), ToolTipProperty, BindingMode.OneWay);
}
}
var ribbonControl = source as IRibbonControl;
if (ribbonControl?.Icon != null)
{
if (ribbonControl.Icon is Visual iconVisual)
{
var rect = new Rectangle
{
Width = 16,
Height = 16,
Fill = new VisualBrush(iconVisual)
};
((IRibbonControl)element).Icon = rect;
}
else
{
Bind(source, element, nameof(IRibbonControl.Icon), IconProperty, BindingMode.OneWay);
}
}
RibbonProperties.SetSize(element, RibbonControlSize.Small);
}
/// <inheritdoc />
public bool CanAddToQuickAccessToolBar
{
get { return (bool)this.GetValue(CanAddToQuickAccessToolBarProperty); }
set { this.SetValue(CanAddToQuickAccessToolBarProperty, value); }
}
/// <summary>Identifies the <see cref="CanAddToQuickAccessToolBar"/> dependency property.</summary>
public static readonly DependencyProperty CanAddToQuickAccessToolBarProperty =
DependencyProperty.Register(nameof(CanAddToQuickAccessToolBar), typeof(bool), typeof(RibbonControl), new PropertyMetadata(BooleanBoxes.TrueBox, OnCanAddToQuickAccessToolBarChanged));
/// <summary>
/// Occurs then CanAddToQuickAccessToolBar property changed
/// </summary>
public static void OnCanAddToQuickAccessToolBarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(ContextMenuProperty);
}
#endregion
#region Binding
internal static void Bind(object source, FrameworkElement target, string path, DependencyProperty property, BindingMode mode)
{
Bind(source, target, new PropertyPath(path), property, mode);
}
internal static void Bind(object source, FrameworkElement target, string path, DependencyProperty property, BindingMode mode, UpdateSourceTrigger updateSourceTrigger)
{
Bind(source, target, new PropertyPath(path), property, mode, updateSourceTrigger);
}
internal static void Bind(object source, FrameworkElement target, PropertyPath path, DependencyProperty property, BindingMode mode)
{
Bind(source, target, path, property, mode, UpdateSourceTrigger.Default);
}
internal static void Bind(object source, FrameworkElement target, PropertyPath path, DependencyProperty property, BindingMode mode, UpdateSourceTrigger updateSourceTrigger)
{
var binding = new Binding
{
Path = path,
Source = source,
Mode = mode,
UpdateSourceTrigger = updateSourceTrigger
};
target.SetBinding(property, binding);
}
#endregion
#region Methods
/// <inheritdoc />
public virtual KeyTipPressedResult OnKeyTipPressed()
{
return KeyTipPressedResult.Empty;
}
/// <inheritdoc />
public virtual void OnKeyTipBack()
{
}
#endregion
#region StaticMethods
/// <summary>
/// Returns screen workarea in witch control is placed
/// </summary>
/// <param name="control">Control</param>
/// <returns>Workarea in witch control is placed</returns>
public static Rect GetControlWorkArea(FrameworkElement control)
{
var tabItemPos = control.PointToScreen(new Point(0, 0));
#pragma warning disable 618
var tabItemRect = new RECT
{
Left = (int)tabItemPos.X,
Top = (int)tabItemPos.Y,
Right = (int)tabItemPos.X + (int)control.ActualWidth,
Bottom = (int)tabItemPos.Y + (int)control.ActualHeight
};
var monitor = NativeMethods.MonitorFromRect(ref tabItemRect, MonitorOptions.MONITOR_DEFAULTTONEAREST);
if (monitor != IntPtr.Zero)
{
var monitorInfo = NativeMethods.GetMonitorInfo(monitor);
return new Rect(monitorInfo.rcWork.Left, monitorInfo.rcWork.Top, monitorInfo.rcWork.Right - monitorInfo.rcWork.Left, monitorInfo.rcWork.Bottom - monitorInfo.rcWork.Top);
}
#pragma warning restore 618
return default;
}
/// <summary>
/// Returns monitor in witch control is placed
/// </summary>
/// <param name="control">Control</param>
/// <returns>Workarea in witch control is placed</returns>
public static Rect GetControlMonitor(FrameworkElement control)
{
var tabItemPos = control.PointToScreen(new Point(0, 0));
#pragma warning disable 618
var tabItemRect = new RECT
{
Left = (int)tabItemPos.X,
Top = (int)tabItemPos.Y,
Right = (int)tabItemPos.X + (int)control.ActualWidth,
Bottom = (int)tabItemPos.Y + (int)control.ActualHeight
};
var monitor = NativeMethods.MonitorFromRect(ref tabItemRect, MonitorOptions.MONITOR_DEFAULTTONEAREST);
if (monitor != IntPtr.Zero)
{
var monitorInfo = NativeMethods.GetMonitorInfo(monitor);
return new Rect(monitorInfo.rcMonitor.Left, monitorInfo.rcMonitor.Top, monitorInfo.rcMonitor.Right - monitorInfo.rcMonitor.Left, monitorInfo.rcMonitor.Bottom - monitorInfo.rcMonitor.Top);
}
#pragma warning restore 618
return default;
}
/// <summary>
/// Get the parent <see cref="Ribbon"/>.
/// </summary>
/// <returns>The found <see cref="Ribbon"/> or <c>null</c> of no parent <see cref="Ribbon"/> could be found.</returns>
public static Ribbon GetParentRibbon(DependencyObject obj)
{
return UIHelper.GetParent<Ribbon>(obj);
}
#endregion
/// <inheritdoc />
void ILogicalChildSupport.AddLogicalChild(object child)
{
this.AddLogicalChild(child);
}
/// <inheritdoc />
void ILogicalChildSupport.RemoveLogicalChild(object child)
{
this.RemoveLogicalChild(child);
}
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.Icon != null)
{
yield return this.Icon;
}
if (this.Header != null)
{
yield return this.Header;
}
}
}
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() => new Fluent.Automation.Peers.RibbonControlAutomationPeer(this);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,395 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Fluent.Internal;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Special wrap panel for <see cref="RibbonGroupBox"/>.
/// </summary>
public class RibbonGroupBoxWrapPanel : Panel
{
private const Orientation DefaultOrientation = Orientation.Vertical;
private Orientation orientation;
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonGroupBoxWrapPanel()
{
this.orientation = DefaultOrientation;
}
/// <summary>
/// Attached <see cref="DependencyProperty"/> for <c>SharedSizeGroupName</c>.
/// </summary>
public static readonly DependencyProperty SharedSizeGroupNameProperty =
DependencyProperty.RegisterAttached(
"SharedSizeGroupName",
typeof(string),
typeof(RibbonGroupBoxWrapPanel),
new PropertyMetadata(default(string)));
/// <summary>
/// Sets <see cref="SharedSizeGroupNameProperty"/> for <paramref name="element"/>.
/// </summary>
public static void SetSharedSizeGroupName(DependencyObject element, string value)
{
element.SetValue(SharedSizeGroupNameProperty, value);
}
/// <summary>
/// Gets <see cref="SharedSizeGroupNameProperty"/> for <paramref name="element"/>.
/// </summary>
public static string GetSharedSizeGroupName(DependencyObject element)
{
return (string)element.GetValue(SharedSizeGroupNameProperty);
}
/// <summary>
/// Attached <see cref="DependencyProperty"/> for <c>SharedSizeGroupName</c>.
/// </summary>
public static readonly DependencyProperty ExcludeFromSharedSizeProperty =
DependencyProperty.RegisterAttached(
"ExcludeFromSharedSize",
typeof(bool),
typeof(RibbonGroupBoxWrapPanel),
new FrameworkPropertyMetadata(BooleanBoxes.FalseBox,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsParentArrange));
/// <summary>
/// Sets <see cref="ExcludeFromSharedSizeProperty"/> for <paramref name="element"/>.
/// </summary>
public static void SetExcludeFromSharedSize(DependencyObject element, bool value)
{
element.SetValue(ExcludeFromSharedSizeProperty, value);
}
/// <summary>
/// Gets <see cref="ExcludeFromSharedSizeProperty"/> for <paramref name="element"/>.
/// </summary>
public static bool GetExcludeFromSharedSize(DependencyObject element)
{
return (bool)element.GetValue(ExcludeFromSharedSizeProperty);
}
private static bool ValidateItemWidth(object value)
{
var v = (double)value;
return ValidateWidthOrHeight(v);
}
private static bool ValidateItemHeight(object value)
{
var v = (double)value;
return ValidateWidthOrHeight(v);
}
private static bool ValidateWidthOrHeight(double v)
{
return double.IsNaN(v) || (v >= 0.0d && !double.IsPositiveInfinity(v));
}
/// <summary>Identifies the <see cref="ItemWidth"/> dependency property.</summary>
public static readonly DependencyProperty ItemWidthProperty =
DependencyProperty.Register(nameof(ItemWidth),
typeof(double),
typeof(RibbonGroupBoxWrapPanel),
new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure),
ValidateItemWidth);
/// <summary>
/// The ItemWidth and ItemHeight properties specify the size of all items in the WrapPanel.
/// Note that children of
/// WrapPanel may have their own Width/Height properties set - the ItemWidth/ItemHeight
/// specifies the size of "layout partition" reserved by WrapPanel for the child.
/// If this property is not set (or set to "Auto" in markup or Double.NaN in code) - the size of layout
/// partition is equal to DesiredSize of the child element.
/// </summary>
[TypeConverter(typeof(LengthConverter))]
public double ItemWidth
{
get { return (double)this.GetValue(ItemWidthProperty); }
set { this.SetValue(ItemWidthProperty, value); }
}
/// <summary>Identifies the <see cref="ItemHeight"/> dependency property.</summary>
public static readonly DependencyProperty ItemHeightProperty =
DependencyProperty.Register(nameof(ItemHeight),
typeof(double),
typeof(RibbonGroupBoxWrapPanel),
new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure),
ValidateItemHeight);
/// <summary>
/// The ItemWidth and ItemHeight properties specify the size of all items in the WrapPanel.
/// Note that children of
/// WrapPanel may have their own Width/Height properties set - the ItemWidth/ItemHeight
/// specifies the size of "layout partition" reserved by WrapPanel for the child.
/// If this property is not set (or set to "Auto" in markup or Double.NaN in code) - the size of layout
/// partition is equal to DesiredSize of the child element.
/// </summary>
[TypeConverter(typeof(LengthConverter))]
public double ItemHeight
{
get { return (double)this.GetValue(ItemHeightProperty); }
set { this.SetValue(ItemHeightProperty, value); }
}
/// <summary>Identifies the <see cref="Orientation"/> dependency property.</summary>
public static readonly DependencyProperty OrientationProperty =
StackPanel.OrientationProperty.AddOwner(
typeof(RibbonGroupBoxWrapPanel),
new FrameworkPropertyMetadata(Orientation.Horizontal,
FrameworkPropertyMetadataOptions.AffectsMeasure,
OnOrientationChanged));
/// <summary>
/// Specifies dimension of children positioning in absence of wrapping.
/// Wrapping occurs in orthogonal direction. For example, if Orientation is Horizontal,
/// the items try to form horizontal rows first and if needed are wrapped and form vertical stack of rows.
/// If Orientation is Vertical, items first positioned in a vertical column, and if there is
/// not enough space - wrapping creates additional columns in horizontal dimension.
/// </summary>
public Orientation Orientation
{
get { return this.orientation; }
set { this.SetValue(OrientationProperty, value); }
}
/// <summary>
/// <see cref="PropertyMetadata.PropertyChangedCallback"/>
/// </summary>
private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var p = (RibbonGroupBoxWrapPanel)d;
p.orientation = (Orientation)e.NewValue;
}
private struct UvSize
{
internal UvSize(Orientation orientation, double width, double height)
{
this.U = this.V = 0d;
this.orientation = orientation;
this.Width = width;
this.Height = height;
}
internal UvSize(Orientation orientation)
{
this.U = this.V = 0d;
this.orientation = orientation;
}
internal double U;
internal double V;
private Orientation orientation;
internal double Width
{
get { return this.orientation == Orientation.Horizontal ? this.U : this.V; }
set
{
if (this.orientation == Orientation.Horizontal)
{
this.U = value;
}
else
{
this.V = value;
}
}
}
internal double Height
{
get { return this.orientation == Orientation.Horizontal ? this.V : this.U; }
set
{
if (this.orientation == Orientation.Horizontal)
{
this.V = value;
}
else
{
this.U = value;
}
}
}
}
/// <inheritdoc />
protected override Size MeasureOverride(Size constraint)
{
var curLineSize = new UvSize(this.Orientation);
var panelSize = new UvSize(this.Orientation);
var uvConstraint = new UvSize(this.Orientation, constraint.Width, constraint.Height);
var itemWidth = this.ItemWidth;
var itemHeight = this.ItemHeight;
var itemWidthSet = !double.IsNaN(itemWidth);
var itemHeightSet = !double.IsNaN(itemHeight);
var childConstraint = new Size(
itemWidthSet ? itemWidth : constraint.Width,
itemHeightSet ? itemHeight : constraint.Height);
var children = this.InternalChildren;
for (int i = 0, count = children.Count; i < count; i++)
{
var child = children[i];
if (child is null)
{
continue;
}
//Flow passes its own constrint to children
child.Measure(childConstraint);
//this is the size of the child in UV space
var sz = new UvSize(
this.Orientation,
itemWidthSet ? itemWidth : child.DesiredSize.Width,
itemHeightSet ? itemHeight : child.DesiredSize.Height);
if (DoubleUtil.GreaterThan(curLineSize.U + sz.U, uvConstraint.U)) //need to switch to another line
{
panelSize.U = Math.Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
curLineSize = sz;
if (DoubleUtil.GreaterThan(sz.U, uvConstraint.U)) //the element is wider then the constraint - give it a separate line
{
panelSize.U = Math.Max(sz.U, panelSize.U);
panelSize.V += sz.V;
curLineSize = new UvSize(this.Orientation);
}
}
else //continue to accumulate a line
{
curLineSize.U += sz.U;
curLineSize.V = Math.Max(sz.V, curLineSize.V);
}
}
//the last line size, if any should be added
panelSize.U = Math.Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
//go from UV space to W/H space
return new Size(panelSize.Width, panelSize.Height);
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize)
{
var parentRibbonGroupBox = UIHelper.GetParent<RibbonGroupBox>(this);
var isParentRibbonGroupBoxSharedSizeScope = parentRibbonGroupBox != null && Grid.GetIsSharedSizeScope(parentRibbonGroupBox);
var firstInLine = 0;
var itemWidth = this.ItemWidth;
var itemHeight = this.ItemHeight;
double accumulatedV = 0;
var itemU = this.Orientation == Orientation.Horizontal ? itemWidth : itemHeight;
var curLineSize = new UvSize(this.Orientation);
var uvFinalSize = new UvSize(this.Orientation, finalSize.Width, finalSize.Height);
var itemWidthSet = !double.IsNaN(itemWidth);
var itemHeightSet = !double.IsNaN(itemHeight);
var useItemU = this.Orientation == Orientation.Horizontal ? itemWidthSet : itemHeightSet;
var children = this.InternalChildren;
var currentColumn = 1;
for (int i = 0, count = children.Count; i < count; i++)
{
var child = children[i];
if (child is null)
{
continue;
}
var sz = new UvSize(
this.Orientation,
itemWidthSet ? itemWidth : child.DesiredSize.Width,
itemHeightSet ? itemHeight : child.DesiredSize.Height);
if (DoubleUtil.GreaterThan(curLineSize.U + sz.U, uvFinalSize.U)) //need to switch to another line
{
this.ArrangeLine(accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU);
accumulatedV += curLineSize.V;
curLineSize = sz;
if (DoubleUtil.GreaterThan(sz.U, uvFinalSize.U)) //the element is wider then the constraint - give it a separate line
{
//switch to next line which only contain one element
this.ArrangeLine(accumulatedV, sz.V, i, ++i, useItemU, itemU);
accumulatedV += sz.V;
curLineSize = new UvSize(this.Orientation);
}
firstInLine = i;
++currentColumn;
}
else //continue to accumulate a line
{
curLineSize.U += sz.U;
curLineSize.V = Math.Max(sz.V, curLineSize.V);
}
if (isParentRibbonGroupBoxSharedSizeScope
&& GetExcludeFromSharedSize(child) == false)
{
SetSharedSizeGroupName(child, $"SharedSizeGroup_Column_{currentColumn}");
}
else
{
SetSharedSizeGroupName(child, null);
}
}
//arrange the last line, if any
if (firstInLine < children.Count)
{
this.ArrangeLine(accumulatedV, curLineSize.V, firstInLine, children.Count, useItemU, itemU);
}
return finalSize;
}
private void ArrangeLine(double v, double lineV, int start, int end, bool useItemU, double itemU)
{
double u = 0;
var isHorizontal = this.Orientation == Orientation.Horizontal;
var children = this.InternalChildren;
for (var i = start; i < end; i++)
{
var child = children[i];
if (child != null)
{
var childSize = new UvSize(this.Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
var layoutSlotU = useItemU ? itemU : childSize.U;
child.Arrange(new Rect(
isHorizontal ? u : v,
isHorizontal ? v : u,
isHorizontal ? layoutSlotU : lineV,
isHorizontal ? lineV : layoutSlotU));
u += layoutSlotU;
}
}
}
}
}

View File

@@ -0,0 +1,624 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using Fluent.Internal;
/// <summary>
/// Represent panel with ribbon group.
/// It is automatically adjusting size of controls
/// </summary>
public class RibbonGroupsContainer : Panel, IScrollInfo
{
private struct MeasureCache
{
public MeasureCache(Size availableSize, Size desiredSize)
{
this.AvailableSize = availableSize;
this.DesiredSize = desiredSize;
}
public Size AvailableSize { get; }
public Size DesiredSize { get; }
}
private MeasureCache measureCache;
#region Reduce Order
/// <summary>
/// Gets or sets reduce order of group in the ribbon panel.
/// It must be enumerated with comma from the first to reduce to
/// the last to reduce (use Control.Name as group name in the enum).
/// Enclose in parentheses as (Control.Name) to reduce/enlarge
/// scalable elements in the given group
/// </summary>
public string ReduceOrder
{
get { return (string)this.GetValue(ReduceOrderProperty); }
set { this.SetValue(ReduceOrderProperty, value); }
}
/// <summary>Identifies the <see cref="ReduceOrder"/> dependency property.</summary>
public static readonly DependencyProperty ReduceOrderProperty =
DependencyProperty.Register(nameof(ReduceOrder), typeof(string), typeof(RibbonGroupsContainer), new PropertyMetadata(OnReduceOrderChanged));
// handles ReduseOrder property changed
private static void OnReduceOrderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ribbonPanel = (RibbonGroupsContainer)d;
for (var i = ribbonPanel.reduceOrderIndex; i < ribbonPanel.reduceOrder.Length - 1; i++)
{
ribbonPanel.IncreaseGroupBoxSize(ribbonPanel.reduceOrder[i]);
}
ribbonPanel.reduceOrder = ((string)e.NewValue).Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
var newReduceOrderIndex = ribbonPanel.reduceOrder.Length - 1;
ribbonPanel.reduceOrderIndex = newReduceOrderIndex;
ribbonPanel.InvalidateMeasure();
ribbonPanel.InvalidateArrange();
}
#endregion
#region Fields
private string[] reduceOrder = new string[0];
private int reduceOrderIndex;
#endregion
#region Initialization
/// <summary>
/// Default constructor
/// </summary>
public RibbonGroupsContainer()
{
this.Focusable = false;
}
#endregion
#region Layout Overridings
/// <inheritdoc />
protected override UIElementCollection CreateUIElementCollection(FrameworkElement logicalParent)
{
return new UIElementCollection(this, /*Parent as FrameworkElement*/this);
}
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize)
{
var desiredSize = this.GetChildrenDesiredSizeIntermediate();
if (this.reduceOrder.Length == 0
// Check cached measure to prevent "flicker"
|| (this.measureCache.AvailableSize == availableSize && this.measureCache.DesiredSize == desiredSize))
{
this.VerifyScrollData(availableSize.Width, desiredSize.Width);
return desiredSize;
}
// If we have more available space - try to expand groups
while (desiredSize.Width <= availableSize.Width)
{
var hasMoreVariants = this.reduceOrderIndex < this.reduceOrder.Length - 1;
if (hasMoreVariants == false)
{
break;
}
// Increase size of another item
this.reduceOrderIndex++;
this.IncreaseGroupBoxSize(this.reduceOrder[this.reduceOrderIndex]);
desiredSize = this.GetChildrenDesiredSizeIntermediate();
}
// If not enough space - go to next variant
while (desiredSize.Width > availableSize.Width)
{
var hasMoreVariants = this.reduceOrderIndex >= 0;
if (hasMoreVariants == false)
{
break;
}
// Decrease size of another item
this.DecreaseGroupBoxSize(this.reduceOrder[this.reduceOrderIndex]);
this.reduceOrderIndex--;
desiredSize = this.GetChildrenDesiredSizeIntermediate();
}
// Set find values
foreach (var item in this.InternalChildren)
{
var groupBox = item as RibbonGroupBox;
if (groupBox is null)
{
continue;
}
if (groupBox.State != groupBox.StateIntermediate
|| groupBox.Scale != groupBox.ScaleIntermediate)
{
using (groupBox.CacheResetGuard.Start())
{
groupBox.State = groupBox.StateIntermediate;
groupBox.Scale = groupBox.ScaleIntermediate;
groupBox.InvalidateLayout();
groupBox.Measure(new Size(double.PositiveInfinity, availableSize.Height));
}
}
// Something wrong with cache?
if (groupBox.DesiredSizeIntermediate != groupBox.DesiredSize)
{
// Reset cache and reinvoke measure
groupBox.ClearCache();
return this.MeasureOverride(availableSize);
}
}
this.measureCache = new MeasureCache(availableSize, desiredSize);
this.VerifyScrollData(availableSize.Width, desiredSize.Width);
return desiredSize;
}
private Size GetChildrenDesiredSizeIntermediate()
{
double width = 0;
double height = 0;
foreach (UIElement child in this.InternalChildren)
{
var groupBox = child as RibbonGroupBox;
if (groupBox is null)
{
continue;
}
var desiredSize = groupBox.DesiredSizeIntermediate;
width += desiredSize.Width;
height = Math.Max(height, desiredSize.Height);
}
return new Size(width, height);
}
// Increase size of the item
private void IncreaseGroupBoxSize(string name)
{
var groupBox = this.FindGroup(name);
var scale = name.StartsWith("(", StringComparison.OrdinalIgnoreCase);
if (groupBox is null)
{
return;
}
if (scale)
{
groupBox.ScaleIntermediate++;
}
else
{
groupBox.StateIntermediate = groupBox.StateIntermediate != RibbonGroupBoxState.Large
? groupBox.StateIntermediate - 1
: RibbonGroupBoxState.Large;
}
}
// Decrease size of the item
private void DecreaseGroupBoxSize(string name)
{
var groupBox = this.FindGroup(name);
var scale = name.StartsWith("(", StringComparison.OrdinalIgnoreCase);
if (groupBox is null)
{
return;
}
if (scale)
{
groupBox.ScaleIntermediate--;
}
else
{
groupBox.StateIntermediate = groupBox.StateIntermediate != RibbonGroupBoxState.Collapsed
? groupBox.StateIntermediate + 1
: groupBox.StateIntermediate;
}
}
private RibbonGroupBox FindGroup(string name)
{
if (name.StartsWith("(", StringComparison.OrdinalIgnoreCase))
{
name = name.Substring(1, name.Length - 2);
}
foreach (FrameworkElement child in this.InternalChildren)
{
if (child.Name == name)
{
return child as RibbonGroupBox;
}
}
return null;
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize)
{
var finalRect = new Rect(finalSize)
{
X = -this.HorizontalOffset
};
foreach (UIElement item in this.InternalChildren)
{
finalRect.Width = item.DesiredSize.Width;
finalRect.Height = Math.Max(finalSize.Height, item.DesiredSize.Height);
item.Arrange(finalRect);
finalRect.X += item.DesiredSize.Width;
}
return finalSize;
}
#endregion
#region IScrollInfo Members
/// <inheritdoc />
public ScrollViewer ScrollOwner
{
get { return this.ScrollData.ScrollOwner; }
set { this.ScrollData.ScrollOwner = value; }
}
/// <inheritdoc />
public void SetHorizontalOffset(double offset)
{
var newValue = CoerceOffset(ValidateInputOffset(offset, nameof(this.HorizontalOffset)), this.scrollData.ExtentWidth, this.scrollData.ViewportWidth);
if (DoubleUtil.AreClose(this.ScrollData.OffsetX, newValue) == false)
{
this.scrollData.OffsetX = newValue;
this.InvalidateArrange();
}
}
/// <inheritdoc />
public double ExtentWidth
{
get { return this.ScrollData.ExtentWidth; }
}
/// <inheritdoc />
public double HorizontalOffset
{
get { return this.ScrollData.OffsetX; }
}
/// <inheritdoc />
public double ViewportWidth
{
get { return this.ScrollData.ViewportWidth; }
}
/// <inheritdoc />
public void LineLeft()
{
this.SetHorizontalOffset(this.HorizontalOffset - 16.0);
}
/// <inheritdoc />
public void LineRight()
{
this.SetHorizontalOffset(this.HorizontalOffset + 16.0);
}
/// <inheritdoc />
public Rect MakeVisible(Visual visual, Rect rectangle)
{
// We can only work on visuals that are us or children.
// An empty rect has no size or position. We can't meaningfully use it.
if (rectangle.IsEmpty
|| visual is null
|| ReferenceEquals(visual, this)
|| !this.IsAncestorOf(visual))
{
return Rect.Empty;
}
// Compute the child's rect relative to (0,0) in our coordinate space.
var childTransform = visual.TransformToAncestor(this);
rectangle = childTransform.TransformBounds(rectangle);
// Initialize the viewport
var viewport = new Rect(this.HorizontalOffset, rectangle.Top, this.ViewportWidth, rectangle.Height);
rectangle.X += viewport.X;
// Compute the offsets required to minimally scroll the child maximally into view.
var minX = ComputeScrollOffsetWithMinimalScroll(viewport.Left, viewport.Right, rectangle.Left, rectangle.Right);
// We have computed the scrolling offsets; scroll to them.
this.SetHorizontalOffset(minX);
// Compute the visible rectangle of the child relative to the viewport.
viewport.X = minX;
rectangle.Intersect(viewport);
rectangle.X -= viewport.X;
// Return the rectangle
return rectangle;
}
private static double ComputeScrollOffsetWithMinimalScroll(
double topView,
double bottomView,
double topChild,
double bottomChild)
{
// # CHILD POSITION CHILD SIZE SCROLL REMEDY
// 1 Above viewport <= viewport Down Align top edge of child & viewport
// 2 Above viewport > viewport Down Align bottom edge of child & viewport
// 3 Below viewport <= viewport Up Align bottom edge of child & viewport
// 4 Below viewport > viewport Up Align top edge of child & viewport
// 5 Entirely within viewport NA No scroll.
// 6 Spanning viewport NA No scroll.
//
// Note: "Above viewport" = childTop above viewportTop, childBottom above viewportBottom
// "Below viewport" = childTop below viewportTop, childBottom below viewportBottom
// These child thus may overlap with the viewport, but will scroll the same direction
/*bool fAbove = DoubleUtil.LessThan(topChild, topView) && DoubleUtil.LessThan(bottomChild, bottomView);
bool fBelow = DoubleUtil.GreaterThan(bottomChild, bottomView) && DoubleUtil.GreaterThan(topChild, topView);*/
var fAbove = (topChild < topView) && (bottomChild < bottomView);
var fBelow = (bottomChild > bottomView) && (topChild > topView);
var fLarger = bottomChild - topChild > bottomView - topView;
// Handle Cases: 1 & 4 above
if ((fAbove && !fLarger)
|| (fBelow && fLarger))
{
return topChild;
}
// Handle Cases: 2 & 3 above
if (fAbove || fBelow)
{
return bottomChild - (bottomView - topView);
}
// Handle cases: 5 & 6 above.
return topView;
}
/// <summary>
/// Not implemented
/// </summary>
public void MouseWheelDown()
{
}
/// <summary>
/// Not implemented
/// </summary>
public void MouseWheelLeft()
{
}
/// <summary>
/// Not implemented
/// </summary>
public void MouseWheelRight()
{
}
/// <summary>
/// Not implemented
/// </summary>
public void MouseWheelUp()
{
}
/// <summary>
/// Not implemented
/// </summary>
public void LineDown()
{
}
/// <summary>
/// Not implemented
/// </summary>
public void LineUp()
{
}
/// <summary>
/// Not implemented
/// </summary>
public void PageDown()
{
}
/// <summary>
/// Not implemented
/// </summary>
public void PageLeft()
{
}
/// <summary>
/// Not implemented
/// </summary>
public void PageRight()
{
}
/// <summary>
/// Not implemented
/// </summary>
public void PageUp()
{
}
/// <summary>
/// Not implemented
/// </summary>
public void SetVerticalOffset(double offset)
{
}
/// <inheritdoc />
public bool CanVerticallyScroll
{
get { return false; }
set { }
}
/// <inheritdoc />
public bool CanHorizontallyScroll
{
get { return true; }
set { }
}
/// <summary>
/// Not implemented
/// </summary>
public double ExtentHeight
{
get { return 0.0; }
}
/// <summary>
/// Not implemented
/// </summary>
public double VerticalOffset
{
get { return 0.0; }
}
/// <summary>
/// Not implemented
/// </summary>
public double ViewportHeight
{
get { return 0.0; }
}
// Gets scroll data info
private ScrollData ScrollData
{
get
{
return this.scrollData ?? (this.scrollData = new ScrollData());
}
}
// Scroll data info
private ScrollData scrollData;
// Validates input offset
private static double ValidateInputOffset(double offset, string parameterName)
{
if (double.IsNaN(offset))
{
throw new ArgumentOutOfRangeException(parameterName);
}
return Math.Max(0.0, offset);
}
// Verifies scrolling data using the passed viewport and extent as newly computed values.
// Checks the X/Y offset and coerces them into the range [0, Extent - ViewportSize]
// If extent, viewport, or the newly coerced offsets are different than the existing offset,
// cachces are updated and InvalidateScrollInfo() is called.
private void VerifyScrollData(double viewportWidth, double extentWidth)
{
var isValid = true;
if (double.IsInfinity(viewportWidth))
{
viewportWidth = extentWidth;
}
var offsetX = CoerceOffset(this.ScrollData.OffsetX, extentWidth, viewportWidth);
isValid &= DoubleUtil.AreClose(viewportWidth, this.ScrollData.ViewportWidth);
isValid &= DoubleUtil.AreClose(extentWidth, this.ScrollData.ExtentWidth);
isValid &= DoubleUtil.AreClose(this.ScrollData.OffsetX, offsetX);
this.ScrollData.ViewportWidth = viewportWidth;
this.ScrollData.ExtentWidth = extentWidth;
this.ScrollData.OffsetX = offsetX;
if (isValid == false)
{
this.ScrollOwner?.InvalidateScrollInfo();
}
}
// Returns an offset coerced into the [0, Extent - Viewport] range.
private static double CoerceOffset(double offset, double extent, double viewport)
{
if (offset > extent - viewport)
{
offset = extent - viewport;
}
if (offset < 0)
{
offset = 0;
}
return offset;
}
#endregion
// We have to reset the reduce order to it's initial value, clear all caches we keep here and invalidate measure/arrange
internal void GroupBoxCacheClearedAndStateAndScaleResetted(RibbonGroupBox ribbonGroupBox)
{
var ribbonPanel = this;
var newReduceOrderIndex = ribbonPanel.reduceOrder.Length - 1;
ribbonPanel.reduceOrderIndex = newReduceOrderIndex;
this.measureCache = default;
foreach (var item in this.InternalChildren)
{
var groupBox = item as RibbonGroupBox;
if (groupBox is null)
{
continue;
}
groupBox.TryClearCacheAndResetStateAndScale();
}
ribbonPanel.InvalidateMeasure();
ribbonPanel.InvalidateArrange();
}
}
}

View File

@@ -0,0 +1,47 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Fluent.Internal;
/// <summary>
/// Represents a <see cref="ScrollViewer" /> specific to <see cref="RibbonGroupsContainer" />.
/// </summary>
public class RibbonGroupsContainerScrollViewer : ScrollViewer
{
static RibbonGroupsContainerScrollViewer()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonGroupsContainerScrollViewer), new FrameworkPropertyMetadata(typeof(RibbonGroupsContainerScrollViewer)));
}
/// <inheritdoc />
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
if (e.Handled)
{
return;
}
if (this.ScrollInfo != null)
{
var horizontalOffsetBefore = this.ScrollInfo.HorizontalOffset;
var verticalOffsetBefore = this.ScrollInfo.VerticalOffset;
if (e.Delta < 0)
{
this.ScrollInfo.MouseWheelDown();
}
else
{
this.ScrollInfo.MouseWheelUp();
}
e.Handled = DoubleUtil.AreClose(horizontalOffsetBefore, this.ScrollInfo.HorizontalOffset) == false
|| DoubleUtil.AreClose(verticalOffsetBefore, this.ScrollInfo.VerticalOffset) == false;
}
}
}
}

View File

@@ -0,0 +1,41 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Markup;
/// <summary>
/// Represents menu in combo box and gallery
/// </summary>
[ContentProperty(nameof(Items))]
public class RibbonMenu : MenuBase
{
#region Constructors
static RibbonMenu()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonMenu), new FrameworkPropertyMetadata(typeof(RibbonMenu)));
}
#endregion
#region Overrides
/// <inheritdoc />
protected override DependencyObject GetContainerForItemOverride()
{
return new MenuItem();
}
/// <inheritdoc />
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is System.Windows.Controls.MenuItem
|| item is Separator;
}
#endregion
}
}

View File

@@ -0,0 +1,53 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Fluent.Internal;
/// <summary>
/// Represents <see cref="ScrollViewer"/> with modified hit test
/// </summary>
public class RibbonScrollViewer : ScrollViewer
{
/// <inheritdoc />
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
if (this.VisualChildrenCount > 0
&& this.GetVisualChild(0) is { } firstVisualChild)
{
return VisualTreeHelper.HitTest(firstVisualChild, hitTestParameters.HitPoint);
}
return base.HitTestCore(hitTestParameters);
}
/// <inheritdoc />
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
if (e.Handled)
{
return;
}
if (this.ScrollInfo != null)
{
var horizontalOffsetBefore = this.ScrollInfo.HorizontalOffset;
var verticalOffsetBefore = this.ScrollInfo.VerticalOffset;
if (e.Delta < 0)
{
this.ScrollInfo.MouseWheelDown();
}
else
{
this.ScrollInfo.MouseWheelUp();
}
e.Handled = DoubleUtil.AreClose(horizontalOffsetBefore, this.ScrollInfo.HorizontalOffset) == false
|| DoubleUtil.AreClose(verticalOffsetBefore, this.ScrollInfo.VerticalOffset) == false;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,781 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using Fluent.Automation.Peers;
using Fluent.Extensions;
using Fluent.Helpers;
using Fluent.Internal;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents ribbon tab item
/// </summary>
[TemplatePart(Name = "PART_HeaderContentHost", Type = typeof(FrameworkElement))]
[TemplatePart(Name = "PART_ContentContainer", Type = typeof(Border))]
[ContentProperty(nameof(Groups))]
[DefaultProperty(nameof(Groups))]
public class RibbonTabItem : Control, IKeyTipedControl, IHeaderedControl, ILogicalChildSupport
{
#region Fields
// Content container
private Border contentContainer;
// Desired width
private double desiredWidth;
// Collection of ribbon groups
private ObservableCollection<RibbonGroupBox> groups;
// Ribbon groups container
private readonly RibbonGroupsContainer groupsInnerContainer = new RibbonGroupsContainer();
// Cached width
private double cachedWidth;
#endregion
#region Properties
internal FrameworkElement HeaderContentHost { get; private set; }
#region Colors/Brushes
/// <summary>
/// Gets or sets the <see cref="Brush"/> which is used to render the background if this <see cref="RibbonTabItem"/> is the currently active/selected one.
/// </summary>
public Brush ActiveTabBackground
{
get { return (Brush)this.GetValue(ActiveTabBackgroundProperty); }
set { this.SetValue(ActiveTabBackgroundProperty, value); }
}
/// <summary>Identifies the <see cref="ActiveTabBackground"/> dependency property.</summary>
public static readonly DependencyProperty ActiveTabBackgroundProperty =
DependencyProperty.Register(nameof(ActiveTabBackground), typeof(Brush), typeof(RibbonTabItem), new PropertyMetadata());
/// <summary>
/// Gets or sets the <see cref="Brush"/> which is used to render the border if this <see cref="RibbonTabItem"/> is the currently active/selected one.
/// </summary>
public Brush ActiveTabBorderBrush
{
get { return (Brush)this.GetValue(ActiveTabBorderBrushProperty); }
set { this.SetValue(ActiveTabBorderBrushProperty, value); }
}
/// <summary>Identifies the <see cref="ActiveTabBorderBrush"/> dependency property.</summary>
public static readonly DependencyProperty ActiveTabBorderBrushProperty =
DependencyProperty.Register(nameof(ActiveTabBorderBrush), typeof(Brush), typeof(RibbonTabItem), new PropertyMetadata());
#endregion
#region KeyTip
/// <inheritdoc />
public string KeyTip
{
get { return (string)this.GetValue(KeyTipProperty); }
set { this.SetValue(KeyTipProperty, value); }
}
/// <summary>
/// Using a DependencyProperty as the backing store for Keys.
/// This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty KeyTipProperty = Fluent.KeyTip.KeysProperty.AddOwner(typeof(RibbonTabItem));
#endregion
/// <summary>
/// Gets ribbon groups container
/// </summary>
public ScrollViewer GroupsContainer { get; } = new RibbonGroupsContainerScrollViewer { VerticalScrollBarVisibility = ScrollBarVisibility.Disabled };
/// <summary>
/// Gets or sets whether ribbon is minimized
/// </summary>
public bool IsMinimized
{
get { return (bool)this.GetValue(IsMinimizedProperty); }
set { this.SetValue(IsMinimizedProperty, value); }
}
/// <summary>Identifies the <see cref="IsMinimized"/> dependency property.</summary>
public static readonly DependencyProperty IsMinimizedProperty = DependencyProperty.Register(nameof(IsMinimized), typeof(bool), typeof(RibbonTabItem), new PropertyMetadata(BooleanBoxes.FalseBox));
/// <summary>
/// Gets or sets whether ribbon is opened
/// </summary>
public bool IsOpen
{
get { return (bool)this.GetValue(IsOpenProperty); }
set { this.SetValue(IsOpenProperty, value); }
}
/// <summary>Identifies the <see cref="IsOpen"/> dependency property.</summary>
public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register(nameof(IsOpen), typeof(bool), typeof(RibbonTabItem), new PropertyMetadata(BooleanBoxes.FalseBox));
/// <summary>
/// Gets or sets reduce order
/// </summary>
public string ReduceOrder
{
get { return this.groupsInnerContainer.ReduceOrder; }
set { this.groupsInnerContainer.ReduceOrder = value; }
}
#region IsContextual
/// <summary>
/// Gets or sets whether tab item is contextual
/// </summary>
public bool IsContextual
{
get { return (bool)this.GetValue(IsContextualProperty); }
private set { this.SetValue(IsContextualPropertyKey, value); }
}
private static readonly DependencyPropertyKey IsContextualPropertyKey =
DependencyProperty.RegisterReadOnly(nameof(IsContextual), typeof(bool), typeof(RibbonTabItem), new PropertyMetadata(BooleanBoxes.FalseBox));
/// <summary>Identifies the <see cref="IsContextual"/> dependency property.</summary>
public static readonly DependencyProperty IsContextualProperty = IsContextualPropertyKey.DependencyProperty;
#endregion
/// <summary>
/// Gets or sets whether tab item is selected
/// </summary>
[Bindable(true)]
[Category("Appearance")]
public bool IsSelected
{
get
{
return (bool)this.GetValue(IsSelectedProperty);
}
set
{
this.SetValue(IsSelectedProperty, value);
}
}
/// <summary>
/// Using a DependencyProperty as the backing store for IsSelected.
/// This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty IsSelectedProperty = Selector.IsSelectedProperty.AddOwner(typeof(RibbonTabItem), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, FrameworkPropertyMetadataOptions.Journal | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.AffectsParentMeasure, OnIsSelectedChanged));
/// <summary>
/// Gets ribbon tab control parent
/// </summary>
internal RibbonTabControl TabControlParent => UIHelper.GetParent<RibbonTabControl>(this);
/// <summary>
/// Gets or sets the padding for the header.
/// </summary>
public Thickness HeaderPadding
{
get { return (Thickness)this.GetValue(HeaderPaddingProperty); }
set { this.SetValue(HeaderPaddingProperty, value); }
}
/// <summary>Identifies the <see cref="HeaderPadding"/> dependency property.</summary>
public static readonly DependencyProperty HeaderPaddingProperty =
DependencyProperty.Register(nameof(HeaderPadding), typeof(Thickness), typeof(RibbonTabItem), new PropertyMetadata(new Thickness(8, 5, 8, 5)));
/// <summary>
/// Gets or sets whether separator is visible
/// </summary>
public bool IsSeparatorVisible
{
get { return (bool)this.GetValue(IsSeparatorVisibleProperty); }
set { this.SetValue(IsSeparatorVisibleProperty, value); }
}
/// <summary>Identifies the <see cref="IsSeparatorVisible"/> dependency property.</summary>
public static readonly DependencyProperty IsSeparatorVisibleProperty =
DependencyProperty.Register(nameof(IsSeparatorVisible), typeof(bool), typeof(RibbonTabItem), new PropertyMetadata(BooleanBoxes.FalseBox));
/// <summary>
/// Gets or sets ribbon contextual tab group
/// </summary>
public RibbonContextualTabGroup Group
{
get { return (RibbonContextualTabGroup)this.GetValue(GroupProperty); }
set { this.SetValue(GroupProperty, value); }
}
/// <summary>Identifies the <see cref="Group"/> dependency property.</summary>
public static readonly DependencyProperty GroupProperty =
DependencyProperty.Register(nameof(Group), typeof(RibbonContextualTabGroup), typeof(RibbonTabItem), new PropertyMetadata(OnGroupChanged));
// handles Group property chanhged
private static void OnGroupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var tab = (RibbonTabItem)d;
((RibbonContextualTabGroup)e.OldValue)?.RemoveTabItem(tab);
if (e.NewValue != null)
{
var tabGroup = (RibbonContextualTabGroup)e.NewValue;
tabGroup.AppendTabItem(tab);
tab.IsContextual = true;
}
else
{
tab.IsContextual = false;
}
}
/// <summary>
/// Gets or sets desired width of the tab item.
/// </summary>
/// <remarks>This is needed in case the width of <see cref="Group"/> is larger than it's tabs.</remarks>
internal double DesiredWidth
{
get { return this.desiredWidth; }
set
{
this.desiredWidth = value;
this.InvalidateMeasure();
}
}
/// <summary>
/// Gets or sets whether tab item has left group border
/// </summary>
public bool HasLeftGroupBorder
{
get { return (bool)this.GetValue(HasLeftGroupBorderProperty); }
set { this.SetValue(HasLeftGroupBorderProperty, value); }
}
/// <summary>Identifies the <see cref="HasLeftGroupBorder"/> dependency property.</summary>
public static readonly DependencyProperty HasLeftGroupBorderProperty =
DependencyProperty.Register(nameof(HasLeftGroupBorder), typeof(bool), typeof(RibbonTabItem), new PropertyMetadata(BooleanBoxes.FalseBox));
/// <summary>
/// Gets or sets whether tab item has right group border
/// </summary>
public bool HasRightGroupBorder
{
get { return (bool)this.GetValue(HasRightGroupBorderProperty); }
set { this.SetValue(HasRightGroupBorderProperty, value); }
}
/// <summary>Identifies the <see cref="HasRightGroupBorder"/> dependency property.</summary>
public static readonly DependencyProperty HasRightGroupBorderProperty =
DependencyProperty.Register(nameof(HasRightGroupBorder), typeof(bool), typeof(RibbonTabItem), new PropertyMetadata(BooleanBoxes.FalseBox));
/// <summary>
/// get collection of ribbon groups
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ObservableCollection<RibbonGroupBox> Groups
{
get
{
if (this.groups is null)
{
this.groups = new ObservableCollection<RibbonGroupBox>();
this.groups.CollectionChanged += this.OnGroupsCollectionChanged;
}
return this.groups;
}
}
// handles ribbon groups collection changes
private void OnGroupsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (this.groupsInnerContainer is null)
{
return;
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
for (var i = 0; i < e.NewItems.Count; i++)
{
this.groupsInnerContainer.Children.Insert(e.NewStartingIndex + i, (UIElement)e.NewItems[i]);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (var item in e.OldItems.OfType<UIElement>())
{
this.groupsInnerContainer.Children.Remove(item);
}
break;
case NotifyCollectionChangedAction.Replace:
foreach (var item in e.OldItems.OfType<UIElement>())
{
this.groupsInnerContainer.Children.Remove(item);
}
foreach (var item in e.NewItems.OfType<UIElement>())
{
this.groupsInnerContainer.Children.Add(item);
}
break;
case NotifyCollectionChangedAction.Reset:
this.groupsInnerContainer.Children.Clear();
foreach (var group in this.groups)
{
this.groupsInnerContainer.Children.Add(group);
}
break;
}
}
#region Header Property
/// <inheritdoc />
public object Header
{
get { return this.GetValue(HeaderProperty); }
set { this.SetValue(HeaderProperty, value); }
}
/// <summary>Identifies the <see cref="Header"/> dependency property.</summary>
public static readonly DependencyProperty HeaderProperty = RibbonControl.HeaderProperty.AddOwner(typeof(RibbonTabItem), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsMeasure, LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region HeaderTemplate Property
/// <summary>
/// Gets or sets header template of tab item.
/// </summary>
public DataTemplate HeaderTemplate
{
get { return (DataTemplate)this.GetValue(HeaderTemplateProperty); }
set { this.SetValue(HeaderTemplateProperty, value); }
}
/// <summary>Identifies the <see cref="HeaderTemplate"/> dependency property.</summary>
public static readonly DependencyProperty HeaderTemplateProperty =
DependencyProperty.Register(nameof(HeaderTemplate), typeof(DataTemplate), typeof(RibbonTabItem), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsMeasure));
#endregion
#region Focusable
/// <summary>
/// Handles Focusable changes
/// </summary>
private static void OnFocusableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
/// <summary>
/// Coerces Focusable
/// </summary>
private static object CoerceFocusable(DependencyObject d, object basevalue)
{
var control = d as RibbonTabItem;
var ribbon = control?.FindParentRibbon();
if (ribbon != null)
{
return (bool)basevalue
&& ribbon.Focusable;
}
return basevalue;
}
// Find parent ribbon
private Ribbon FindParentRibbon()
{
var element = this.Parent;
while (element != null)
{
if (element is Ribbon ribbon)
{
return ribbon;
}
element = VisualTreeHelper.GetParent(element);
}
return null;
}
#endregion
#endregion
#region Initialize
/// <summary>
/// Static constructor
/// </summary>
static RibbonTabItem()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonTabItem), new FrameworkPropertyMetadata(typeof(RibbonTabItem)));
FocusableProperty.AddOwner(typeof(RibbonTabItem), new FrameworkPropertyMetadata(OnFocusableChanged, CoerceFocusable));
VisibilityProperty.AddOwner(typeof(RibbonTabItem), new FrameworkPropertyMetadata(OnVisibilityChanged));
KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(typeof(RibbonTabItem), new FrameworkPropertyMetadata(KeyboardNavigationMode.Contained));
KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(RibbonTabItem), new FrameworkPropertyMetadata(KeyboardNavigationMode.Local));
AutomationProperties.IsOffscreenBehaviorProperty.OverrideMetadata(typeof(RibbonTabItem), new FrameworkPropertyMetadata(IsOffscreenBehavior.FromClip));
}
// Handles visibility changes
private static void OnVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var item = d as RibbonTabItem;
if (item is null)
{
return;
}
item.Group?.UpdateInnerVisiblityAndGroupBorders();
if (item.IsSelected
&& (Visibility)e.NewValue == Visibility.Collapsed)
{
if (item.TabControlParent != null)
{
if (item.TabControlParent.IsMinimized)
{
item.IsSelected = false;
}
else
{
item.TabControlParent.SelectedItem = item.TabControlParent.GetFirstVisibleAndEnabledItem();
}
}
}
}
/// <summary>
/// Default constructor
/// </summary>
public RibbonTabItem()
{
this.AddLogicalChild(this.GroupsContainer);
this.GroupsContainer.Content = this.groupsInnerContainer;
// Force redirection of DataContext. This is needed, because we detach the container from the visual tree and attach it to a diffrent one (the popup/dropdown) when the ribbon is minimized.
this.groupsInnerContainer.SetBinding(DataContextProperty, new Binding(nameof(this.DataContext))
{
Source = this
});
ContextMenuService.Coerce(this);
this.Loaded += this.OnLoaded;
this.Unloaded += this.OnUnloaded;
}
#endregion
#region Overrides
/// <inheritdoc />
protected override Size MeasureOverride(Size constraint)
{
if (this.contentContainer is null)
{
return base.MeasureOverride(constraint);
}
if (this.IsContextual
&& this.Group?.Visibility == Visibility.Collapsed)
{
return Size.Empty;
}
var baseConstraint = base.MeasureOverride(constraint);
var totalWidth = this.contentContainer.DesiredSize.Width - (this.contentContainer.Margin.Left - this.contentContainer.Margin.Right);
this.contentContainer.Child.Measure(SizeConstants.Infinite);
var headerWidth = this.contentContainer.Child.DesiredSize.Width;
if (totalWidth > headerWidth + (this.HeaderPadding.Left + this.HeaderPadding.Right))
{
if (DoubleUtil.AreClose(this.desiredWidth, 0) == false)
{
// If header width is larger then tab increase tab width
if (constraint.Width > this.desiredWidth
&& this.desiredWidth > totalWidth)
{
baseConstraint.Width = this.desiredWidth;
}
else
{
baseConstraint.Width = headerWidth + (this.HeaderPadding.Left + this.HeaderPadding.Right) + (this.contentContainer.Margin.Left + this.contentContainer.Margin.Right);
}
}
}
if (DoubleUtil.AreClose(this.cachedWidth, baseConstraint.Width) == false
&& this.IsContextual
&& this.Group != null)
{
this.cachedWidth = baseConstraint.Width;
var contextualTabGroupContainer = UIHelper.GetParent<RibbonContextualGroupsContainer>(this.Group);
contextualTabGroupContainer?.InvalidateMeasure();
var ribbonTitleBar = UIHelper.GetParent<RibbonTitleBar>(this.Group);
ribbonTitleBar?.ForceMeasureAndArrange();
}
return baseConstraint;
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size arrangeBounds)
{
var result = base.ArrangeOverride(arrangeBounds);
var ribbonTitleBar = UIHelper.GetParent<RibbonTitleBar>(this.Group);
ribbonTitleBar?.ForceMeasureAndArrange();
return result;
}
/// <inheritdoc />
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.HeaderContentHost = this.GetTemplateChild("PART_HeaderContentHost") as FrameworkElement;
this.contentContainer = this.GetTemplateChild("PART_ContentContainer") as Border;
}
/// <inheritdoc />
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (ReferenceEquals(e.Source, this)
&& e.ClickCount == 2)
{
e.Handled = true;
if (this.TabControlParent != null)
{
var canMinimize = this.TabControlParent.CanMinimize;
if (canMinimize)
{
this.TabControlParent.IsMinimized = !this.TabControlParent.IsMinimized;
}
}
}
else if (ReferenceEquals(e.Source, this)
|| this.IsSelected == false)
{
if (this.Visibility == Visibility.Visible)
{
if (this.TabControlParent != null)
{
var newItem = this.TabControlParent.ItemContainerGenerator.ItemFromContainerOrContainerContent(this);
if (ReferenceEquals(this.TabControlParent.SelectedItem, newItem))
{
this.TabControlParent.IsDropDownOpen = !this.TabControlParent.IsDropDownOpen;
}
else
{
this.TabControlParent.SelectedItem = newItem;
}
this.TabControlParent.RaiseRequestBackstageClose();
}
else
{
this.IsSelected = true;
}
e.Handled = true;
}
}
}
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() => new Fluent.Automation.Peers.RibbonTabItemAutomationPeer(this);
#endregion
#region Private methods
// Handles IsSelected property changes
private static void OnIsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var container = (RibbonTabItem)d;
var newValue = (bool)e.NewValue;
if (newValue)
{
if (container.TabControlParent?.SelectedTabItem != null
&& ReferenceEquals(container.TabControlParent.SelectedTabItem, container) == false)
{
container.TabControlParent.SelectedTabItem.IsSelected = false;
}
container.OnSelected(new RoutedEventArgs(Selector.SelectedEvent, container));
}
else
{
container.OnUnselected(new RoutedEventArgs(Selector.UnselectedEvent, container));
}
// Raise UI automation events on this RibbonTabItem
if (AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementSelected)
|| AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementRemovedFromSelection))
{
//SelectorHelper.RaiseIsSelectedChangedAutomationEvent(container.TabControlParent, container, newValue);
var peer = UIElementAutomationPeer.CreatePeerForElement(container) as RibbonTabItemAutomationPeer;
peer?.RaiseTabSelectionEvents();
}
}
/// <summary>
/// Handles selected
/// </summary>
/// <param name="e">The event data</param>
protected virtual void OnSelected(RoutedEventArgs e)
{
this.HandleIsSelectedChanged(e);
}
/// <summary>
/// handles unselected
/// </summary>
/// <param name="e">The event data</param>
protected virtual void OnUnselected(RoutedEventArgs e)
{
this.HandleIsSelectedChanged(e);
}
#endregion
#region Event handling
// Handles IsSelected property changes
private void HandleIsSelectedChanged(RoutedEventArgs e)
{
this.RaiseEvent(e);
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
this.SubscribeEvents();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
this.UnSubscribeEvents();
}
private void SubscribeEvents()
{
// Always unsubscribe events to ensure we don't subscribe twice
this.UnSubscribeEvents();
if (this.groups != null)
{
this.groups.CollectionChanged += this.OnGroupsCollectionChanged;
}
}
private void UnSubscribeEvents()
{
if (this.groups != null)
{
this.groups.CollectionChanged -= this.OnGroupsCollectionChanged;
}
}
#endregion
/// <inheritdoc />
public KeyTipPressedResult OnKeyTipPressed()
{
if (this.TabControlParent?.SelectedItem is RibbonTabItem currentSelectedItem)
{
currentSelectedItem.IsSelected = false;
}
this.IsSelected = true;
// This way keytips for delay loaded elements work correctly. Partially fixes #244.
this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, new Action(() => { }));
return KeyTipPressedResult.Empty;
}
/// <inheritdoc />
public void OnKeyTipBack()
{
if (this.TabControlParent != null
&& this.TabControlParent.IsMinimized)
{
this.TabControlParent.IsDropDownOpen = false;
}
}
/// <inheritdoc />
void ILogicalChildSupport.AddLogicalChild(object child)
{
this.AddLogicalChild(child);
}
/// <inheritdoc />
void ILogicalChildSupport.RemoveLogicalChild(object child)
{
this.RemoveLogicalChild(child);
}
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
yield return this.GroupsContainer;
if (this.Header != null)
{
yield return this.Header;
}
}
}
}
}

View File

@@ -0,0 +1,591 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using Fluent.Internal;
/// <summary>
/// Represent panel with ribbon tab items.
/// It is automatically adjusting size of tabs
/// </summary>
public class RibbonTabsContainer : Panel, IScrollInfo
{
/// <summary>
/// Initializes a new instance of the <see cref="RibbonTabsContainer"/> class.
/// </summary>
public RibbonTabsContainer()
{
this.Focusable = false;
}
static RibbonTabsContainer()
{
KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(RibbonTabsContainer), new FrameworkPropertyMetadata(KeyboardNavigationMode.Once));
KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(typeof(RibbonTabsContainer), new FrameworkPropertyMetadata(KeyboardNavigationMode.Cycle));
}
#region Layout Overridings
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize)
{
if (this.InternalChildren.Count == 0)
{
return base.MeasureOverride(availableSize);
}
var desiredSize = this.MeasureChildrenDesiredSize(availableSize);
// Step 1. If all tabs already fit, just return.
if (availableSize.Width >= desiredSize.Width
|| DoubleUtil.AreClose(availableSize.Width, desiredSize.Width))
{
// Hide separator lines between tabs
this.UpdateSeparators(false, false);
this.VerifyScrollData(availableSize.Width, desiredSize.Width);
return desiredSize;
}
// Size reduction:
// - calculate the overflow width
// - get all visible tabs ordered by:
// - non context tabs first
// - largest tabs first
// - then loop over all tabs and reduce their size in steps
// - during each tabs reduction check if it's still the largest tab
// - if it's still the largest tab reduce it's size further till there is no larger tab left
var overflowWidth = desiredSize.Width - availableSize.Width;
var visibleTabs = this.InternalChildren.Cast<RibbonTabItem>()
.Where(x => x.Visibility != Visibility.Collapsed)
.OrderBy(x => x.IsContextual)
.ToList();
// did we change the size of any contextual tabs?
var contextualTabsSizeChanged = false;
// step size for reducing the size of tabs
const int sizeChangeStepSize = 4;
// loop while we got overflow left (still need to reduce more) and all tabs are larger than the minimum size
while (overflowWidth > 0
&& AreAnyTabsAboveMinimumSize(visibleTabs))
{
var tabsChangedInSize = 0;
foreach (var tab in visibleTabs.OrderByDescending(x => x.DesiredSize.Width))
{
var widthBeforeMeasure = tab.DesiredSize.Width;
// ignore tabs that are smaller or equal to the minimum size
if (widthBeforeMeasure < MinimumRegularTabWidth
|| DoubleUtil.AreClose(widthBeforeMeasure, MinimumRegularTabWidth))
{
continue;
}
var wasLargestTab = IsLargestTab(visibleTabs, tab.DesiredSize.Width, tab.IsContextual);
// measure with reduced size, but at least the minimum size
tab.Measure(new Size(Math.Max(MinimumRegularTabWidth, tab.DesiredSize.Width - sizeChangeStepSize), tab.DesiredSize.Height));
// calculate diff of measure before and after possible reduction
var widthDifference = widthBeforeMeasure - tab.DesiredSize.Width;
var didWidthChange = widthDifference > 0;
// count as changed if diff is greater than zero
tabsChangedInSize += didWidthChange
? 1
: 0;
// was it a changed contextual tab?
if (tab.IsContextual
&& didWidthChange)
{
contextualTabsSizeChanged = true;
}
// reduce remaining overflow width
overflowWidth -= widthDifference;
// break if no overflow width is left
if (overflowWidth <= 0)
{
break;
}
// if the current tab was the largest tab break to reduce it's size further
if (wasLargestTab
&& didWidthChange)
{
break;
}
}
// break if no tabs changed their size
if (tabsChangedInSize == 0)
{
break;
}
}
desiredSize = this.GetChildrenDesiredSize();
// Add separator lines between
// tabs to assist readability
this.UpdateSeparators(true, contextualTabsSizeChanged || AreAnyTabsAboveMinimumSize(visibleTabs) == false);
this.VerifyScrollData(availableSize.Width, desiredSize.Width);
return desiredSize;
}
private static bool AreAnyTabsAboveMinimumSize(List<RibbonTabItem> tabs)
{
return tabs.Any(item => item.DesiredSize.Width > MinimumRegularTabWidth);
}
private static bool IsLargestTab(List<RibbonTabItem> tabs, double width, bool isContextual)
{
return tabs.Count > 1 && tabs.Any(x => x.IsContextual == isContextual && x.DesiredSize.Width > width) == false;
}
private Size MeasureChildrenDesiredSize(Size availableSize)
{
double width = 0;
double height = 0;
foreach (UIElement child in this.InternalChildren)
{
child.Measure(availableSize);
width += child.DesiredSize.Width;
height = Math.Max(height, child.DesiredSize.Height);
}
return new Size(width, height);
}
private Size GetChildrenDesiredSize()
{
double width = 0;
double height = 0;
foreach (UIElement child in this.InternalChildren)
{
width += child.DesiredSize.Width;
height = Math.Max(height, child.DesiredSize.Height);
}
return new Size(width, height);
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize)
{
var finalRect = new Rect(finalSize)
{
X = -this.HorizontalOffset
};
var orderedChildren = this.InternalChildren.OfType<RibbonTabItem>()
.OrderBy(x => x.Group != null);
foreach (var item in orderedChildren)
{
finalRect.Width = item.DesiredSize.Width;
finalRect.Height = Math.Max(finalSize.Height, item.DesiredSize.Height);
item.Arrange(finalRect);
finalRect.X += item.DesiredSize.Width;
}
return finalSize;
}
/// <summary>
/// Updates separator visibility
/// </summary>
/// <param name="regularTabs">If this parameter true, regular tabs will have separators</param>
/// <param name="contextualTabs">If this parameter true, contextual tabs will have separators</param>
private void UpdateSeparators(bool regularTabs, bool contextualTabs)
{
foreach (RibbonTabItem tab in this.Children)
{
if (tab.IsContextual)
{
if (tab.IsSeparatorVisible != contextualTabs)
{
tab.IsSeparatorVisible = contextualTabs;
}
}
else if (tab.IsSeparatorVisible != regularTabs)
{
tab.IsSeparatorVisible = regularTabs;
}
}
}
#endregion
#region IScrollInfo Members
/// <inheritdoc />
public ScrollViewer ScrollOwner
{
get { return this.ScrollData.ScrollOwner; }
set { this.ScrollData.ScrollOwner = value; }
}
/// <inheritdoc />
public void SetHorizontalOffset(double offset)
{
var newValue = CoerceOffset(ValidateInputOffset(offset, nameof(this.HorizontalOffset)), this.scrollData.ExtentWidth, this.scrollData.ViewportWidth);
if (DoubleUtil.AreClose(this.ScrollData.OffsetX, newValue) == false)
{
this.scrollData.OffsetX = newValue;
this.InvalidateMeasure();
this.ScrollOwner?.InvalidateScrollInfo();
}
}
/// <inheritdoc />
public double ExtentWidth => this.ScrollData.ExtentWidth;
/// <inheritdoc />
public double HorizontalOffset => this.ScrollData.OffsetX;
/// <inheritdoc />
public double ViewportWidth => this.ScrollData.ViewportWidth;
/// <inheritdoc />
public void LineLeft()
{
this.SetHorizontalOffset(this.HorizontalOffset - 16.0);
}
/// <inheritdoc />
public void LineRight()
{
this.SetHorizontalOffset(this.HorizontalOffset + 16.0);
}
/// <inheritdoc />
public Rect MakeVisible(Visual visual, Rect rectangle)
{
// We can only work on visuals that are us or children.
// An empty rect has no size or position. We can't meaningfully use it.
if (rectangle.IsEmpty
|| visual is null
|| ReferenceEquals(visual, this)
|| this.IsAncestorOf(visual) == false)
{
return Rect.Empty;
}
// Compute the child's rect relative to (0,0) in our coordinate space.
var childTransform = visual.TransformToAncestor(this);
rectangle = childTransform.TransformBounds(rectangle);
// Initialize the viewport
var viewport = new Rect(this.HorizontalOffset, rectangle.Top, this.ViewportWidth, rectangle.Height);
rectangle.X += viewport.X;
// Compute the offsets required to minimally scroll the child maximally into view.
var minX = ComputeScrollOffsetWithMinimalScroll(viewport.Left, viewport.Right, rectangle.Left, rectangle.Right);
// We have computed the scrolling offsets; scroll to them.
this.SetHorizontalOffset(minX);
// Compute the visible rectangle of the child relative to the viewport.
viewport.X = minX;
rectangle.Intersect(viewport);
rectangle.X -= viewport.X;
// Return the rectangle
return rectangle;
}
private static double ComputeScrollOffsetWithMinimalScroll(
double topView,
double bottomView,
double topChild,
double bottomChild)
{
// # CHILD POSITION CHILD SIZE SCROLL REMEDY
// 1 Above viewport <= viewport Down Align top edge of child & viewport
// 2 Above viewport > viewport Down Align bottom edge of child & viewport
// 3 Below viewport <= viewport Up Align bottom edge of child & viewport
// 4 Below viewport > viewport Up Align top edge of child & viewport
// 5 Entirely within viewport NA No scroll.
// 6 Spanning viewport NA No scroll.
//
// Note: "Above viewport" = childTop above viewportTop, childBottom above viewportBottom
// "Below viewport" = childTop below viewportTop, childBottom below viewportBottom
// These child thus may overlap with the viewport, but will scroll the same direction
/*bool fAbove = DoubleUtil.LessThan(topChild, topView) && DoubleUtil.LessThan(bottomChild, bottomView);
bool fBelow = DoubleUtil.GreaterThan(bottomChild, bottomView) && DoubleUtil.GreaterThan(topChild, topView);*/
var fAbove = (topChild < topView) && (bottomChild < bottomView);
var fBelow = (bottomChild > bottomView) && (topChild > topView);
var fLarger = bottomChild - topChild > bottomView - topView;
// Handle Cases: 1 & 4 above
if ((fAbove && !fLarger)
|| (fBelow && fLarger))
{
return topChild;
}
// Handle Cases: 2 & 3 above
if (fAbove || fBelow)
{
return bottomChild - (bottomView - topView);
}
// Handle cases: 5 & 6 above.
return topView;
}
/// <summary>
/// Not implemented
/// </summary>
public void MouseWheelDown()
{
}
/// <inheritdoc />
public void MouseWheelLeft()
{
this.SetHorizontalOffset(this.HorizontalOffset - 16);
}
/// <inheritdoc />
public void MouseWheelRight()
{
this.SetHorizontalOffset(this.HorizontalOffset + 16);
}
/// <summary>
/// Not implemented
/// </summary>
public void MouseWheelUp()
{
}
/// <summary>
/// Not implemented
/// </summary>
public void LineDown()
{
}
/// <summary>
/// Not implemented
/// </summary>
public void LineUp()
{
}
/// <summary>
/// Not implemented
/// </summary>
public void PageDown()
{
}
/// <inheritdoc />
public void PageLeft()
{
this.SetHorizontalOffset(this.HorizontalOffset - this.ViewportWidth);
}
/// <inheritdoc />
public void PageRight()
{
this.SetHorizontalOffset(this.HorizontalOffset + this.ViewportWidth);
}
/// <summary>
/// Not implemented
/// </summary>
public void PageUp()
{
}
/// <summary>
/// Not implemented
/// </summary>
public void SetVerticalOffset(double offset)
{
}
/// <inheritdoc />
public bool CanVerticallyScroll
{
get => false;
set { }
}
/// <inheritdoc />
public bool CanHorizontallyScroll
{
get => true;
set { }
}
/// <summary>
/// Not implemented
/// </summary>
public double ExtentHeight => 0.0;
/// <summary>
/// Not implemented
/// </summary>
public double VerticalOffset => 0.0;
/// <summary>
/// Not implemented
/// </summary>
public double ViewportHeight => 0.0;
// Gets scroll data info
private ScrollData ScrollData => this.scrollData ?? (this.scrollData = new ScrollData());
// Scroll data info
private ScrollData scrollData;
private const double MinimumRegularTabWidth = 30D;
// Validates input offset
private static double ValidateInputOffset(double offset, string parameterName)
{
if (double.IsNaN(offset))
{
throw new ArgumentOutOfRangeException(parameterName);
}
return Math.Max(0.0, offset);
}
// Verifies scrolling data using the passed viewport and extent as newly computed values.
// Checks the X/Y offset and coerces them into the range [0, Extent - ViewportSize]
// If extent, viewport, or the newly coerced offsets are different than the existing offset,
// caches are updated and InvalidateScrollInfo() is called.
private void VerifyScrollData(double viewportWidth, double extentWidth)
{
var isValid = true;
if (double.IsInfinity(viewportWidth))
{
viewportWidth = extentWidth;
}
var offsetX = CoerceOffset(this.ScrollData.OffsetX, extentWidth, viewportWidth);
isValid &= DoubleUtil.AreClose(viewportWidth, this.ScrollData.ViewportWidth);
isValid &= DoubleUtil.AreClose(extentWidth, this.ScrollData.ExtentWidth);
isValid &= DoubleUtil.AreClose(this.ScrollData.OffsetX, offsetX);
this.ScrollData.ViewportWidth = viewportWidth;
// Prevent flickering by only using extentWidth if it's at least 2 larger than viewportWidth
if (viewportWidth + 2 < extentWidth)
{
this.scrollData.ExtentWidth = extentWidth;
}
else
{
// Or we show show the srollbar if all tabs are at their minimum width or smaller
// but do this early (if extent + 2 is equal or larger than the viewport, or they are equal)
if (extentWidth + 2 >= viewportWidth
|| DoubleUtil.AreClose(extentWidth, viewportWidth))
{
var visibleTabs = this.InternalChildren.Cast<RibbonTabItem>().Where(item => item.Visibility != Visibility.Collapsed).ToList();
var newExtentWidth = viewportWidth;
if (visibleTabs.Any()
&& visibleTabs.All(item => DoubleUtil.AreClose(item.DesiredSize.Width, MinimumRegularTabWidth) || item.DesiredSize.Width < MinimumRegularTabWidth))
{
if (DoubleUtil.AreClose(newExtentWidth, viewportWidth))
{
newExtentWidth += 1;
}
this.ScrollData.ExtentWidth = newExtentWidth;
}
else
{
this.scrollData.ExtentWidth = viewportWidth;
}
}
else
{
this.scrollData.ExtentWidth = viewportWidth;
}
}
this.ScrollData.OffsetX = offsetX;
if (isValid == false)
{
this.ScrollOwner?.InvalidateScrollInfo();
}
}
// Returns an offset coerced into the [0, Extent - Viewport] range.
private static double CoerceOffset(double offset, double extent, double viewport)
{
if (offset > extent - viewport)
{
offset = extent - viewport;
}
if (offset < 0)
{
offset = 0;
}
return offset;
}
#endregion
}
#region ScrollData
/// <summary>
/// Helper class to hold scrolling data.
/// This class exists to reduce working set when SCP is delegating to another implementation of ISI.
/// Standard "extra pointer always for less data sometimes" cache savings model:
/// </summary>
internal class ScrollData
{
/// <summary>
/// Scroll viewer
/// </summary>
internal ScrollViewer ScrollOwner { get; set; }
/// <summary>
/// Scroll offset
/// </summary>
internal double OffsetX { get; set; }
/// <summary>
/// ViewportSize is computed from our FinalSize, but may be in different units.
/// </summary>
internal double ViewportWidth { get; set; }
/// <summary>
/// Extent is the total size of our content.
/// </summary>
internal double ExtentWidth { get; set; }
}
#endregion ScrollData
}

View File

@@ -0,0 +1,510 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Linq;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Fluent.Extensions;
using Fluent.Helpers;
using Fluent.Internal;
using Fluent.Internal.KnownBoxes;
using WindowChrome = ControlzEx.Windows.Shell.WindowChrome;
/// <summary>
/// Represents title bar
/// </summary>
[StyleTypedProperty(Property = nameof(ItemContainerStyle), StyleTargetType = typeof(RibbonContextualTabGroup))]
[TemplatePart(Name = "PART_QuickAccessToolbarHolder", Type = typeof(FrameworkElement))]
[TemplatePart(Name = "PART_HeaderHolder", Type = typeof(FrameworkElement))]
[TemplatePart(Name = "PART_ItemsContainer", Type = typeof(Panel))]
public class RibbonTitleBar : HeaderedItemsControl
{
#region Fields
// Quick access toolbar holder
private FrameworkElement quickAccessToolbarHolder;
// Header holder
private FrameworkElement headerHolder;
// Items container
private Panel itemsContainer;
// Quick access toolbar rect
private Rect quickAccessToolbarRect;
// Header rect
private Rect headerRect;
// Items rect
private Rect itemsRect;
private Size lastMeasureConstraint;
#endregion
#region Properties
/// <summary>
/// Gets or sets quick access toolbar
/// </summary>
public FrameworkElement QuickAccessToolBar
{
get { return (FrameworkElement)this.GetValue(QuickAccessToolBarProperty); }
set { this.SetValue(QuickAccessToolBarProperty, value); }
}
/// <summary>Identifies the <see cref="QuickAccessToolBar"/> dependency property.</summary>
public static readonly DependencyProperty QuickAccessToolBarProperty =
DependencyProperty.Register(nameof(QuickAccessToolBar), typeof(FrameworkElement), typeof(RibbonTitleBar), new PropertyMetadata(OnQuickAccessToolBarChanged));
private static void OnQuickAccessToolBarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var titleBar = (RibbonTitleBar)d;
titleBar.ForceMeasureAndArrange();
}
/// <summary>
/// Gets or sets header alignment
/// </summary>
public HorizontalAlignment HeaderAlignment
{
get { return (HorizontalAlignment)this.GetValue(HeaderAlignmentProperty); }
set { this.SetValue(HeaderAlignmentProperty, value); }
}
/// <summary>Identifies the <see cref="HeaderAlignment"/> dependency property.</summary>
public static readonly DependencyProperty HeaderAlignmentProperty =
DependencyProperty.Register(nameof(HeaderAlignment), typeof(HorizontalAlignment), typeof(RibbonTitleBar), new FrameworkPropertyMetadata(HorizontalAlignment.Center, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// Defines whether title bar is collapsed
/// </summary>
public bool IsCollapsed
{
get { return (bool)this.GetValue(IsCollapsedProperty); }
set { this.SetValue(IsCollapsedProperty, value); }
}
/// <summary>Identifies the <see cref="IsCollapsed"/> dependency property.</summary>
public static readonly DependencyProperty IsCollapsedProperty =
DependencyProperty.Register(nameof(IsCollapsed), typeof(bool), typeof(RibbonTitleBar), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));
private bool isAtLeastOneRequiredControlPresent;
/// <summary>Identifies the <see cref="HideContextTabs"/> dependency property.</summary>
public static readonly DependencyProperty HideContextTabsProperty =
DependencyProperty.Register(nameof(HideContextTabs), typeof(bool), typeof(RibbonTitleBar), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// Gets or sets whether context tabs are hidden.
/// </summary>
public bool HideContextTabs
{
get { return (bool)this.GetValue(HideContextTabsProperty); }
set { this.SetValue(HideContextTabsProperty, value); }
}
#endregion
#region Initialize
/// <summary>
/// Static constructor
/// </summary>
static RibbonTitleBar()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonTitleBar), new FrameworkPropertyMetadata(typeof(RibbonTitleBar)));
HeaderProperty.OverrideMetadata(typeof(RibbonTitleBar), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure));
}
/// <summary>
/// Creates a new instance.
/// </summary>
public RibbonTitleBar()
{
WindowChrome.SetIsHitTestVisibleInChrome(this, true);
}
#endregion
#region Overrides
/// <inheritdoc />
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
var baseResult = base.HitTestCore(hitTestParameters);
if (baseResult is null)
{
return new PointHitTestResult(this, hitTestParameters.HitPoint);
}
return baseResult;
}
/// <inheritdoc />
protected override void OnMouseRightButtonUp(MouseButtonEventArgs e)
{
base.OnMouseRightButtonUp(e);
if (e.Handled
|| this.IsMouseDirectlyOver == false)
{
return;
}
WindowSteeringHelper.ShowSystemMenu(this, e);
}
/// <inheritdoc />
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
if (e.Handled)
{
return;
}
// Contextual groups shall handle mouse events
if (e.Source is RibbonContextualGroupsContainer
|| e.Source is RibbonContextualTabGroup)
{
return;
}
WindowSteeringHelper.HandleMouseLeftButtonDown(e, true, true);
}
/// <inheritdoc />
protected override DependencyObject GetContainerForItemOverride()
{
return new RibbonContextualTabGroup();
}
/// <inheritdoc />
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is RibbonContextualTabGroup;
}
/// <inheritdoc />
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.quickAccessToolbarHolder = this.GetTemplateChild("PART_QuickAccessToolbarHolder") as FrameworkElement;
this.headerHolder = this.GetTemplateChild("PART_HeaderHolder") as FrameworkElement;
this.itemsContainer = this.GetTemplateChild("PART_ItemsContainer") as Panel;
this.isAtLeastOneRequiredControlPresent = this.quickAccessToolbarHolder != null
|| this.headerHolder != null
|| this.itemsContainer != null;
if (this.quickAccessToolbarHolder != null)
{
WindowChrome.SetIsHitTestVisibleInChrome(this.quickAccessToolbarHolder, true);
}
}
/// <inheritdoc />
protected override Size MeasureOverride(Size constraint)
{
if (this.isAtLeastOneRequiredControlPresent == false)
{
return base.MeasureOverride(constraint);
}
this.lastMeasureConstraint = constraint;
var resultSize = constraint;
if (double.IsPositiveInfinity(resultSize.Width)
|| double.IsPositiveInfinity(resultSize.Height))
{
resultSize = base.MeasureOverride(resultSize);
}
this.Update(resultSize);
this.itemsContainer.Measure(this.itemsRect.Size);
this.headerHolder.Measure(this.headerRect.Size);
this.quickAccessToolbarHolder.Measure(this.quickAccessToolbarRect.Size);
var maxHeight = Math.Max(Math.Max(this.itemsRect.Height, this.headerRect.Height), this.quickAccessToolbarRect.Height);
var width = this.quickAccessToolbarRect.Width + this.headerRect.Width + this.itemsRect.Width;
return new Size(width, maxHeight);
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size arrangeBounds)
{
if (this.isAtLeastOneRequiredControlPresent == false)
{
return base.ArrangeOverride(arrangeBounds);
}
// If the last measure constraint and the arrangeBounds are not equal we have to update again.
// This can happen if the window is set to auto-size it's width.
// As Update also does some things that are related to an arrange pass we have to update again.
// It would be way better if Update wouldn't handle parts of the arrange pass, but that would be very difficult to implement...
if (arrangeBounds.Equals(this.lastMeasureConstraint) == false)
{
this.Update(arrangeBounds);
this.itemsContainer.Measure(this.itemsRect.Size);
this.headerHolder.Measure(this.headerRect.Size);
this.quickAccessToolbarHolder.Measure(this.quickAccessToolbarRect.Size);
}
this.itemsContainer.Arrange(this.itemsRect);
this.headerHolder.Arrange(this.headerRect);
this.quickAccessToolbarHolder.Arrange(this.quickAccessToolbarRect);
this.EnsureCorrectLayoutAfterArrange();
return arrangeBounds;
}
#endregion
#region Private methods
/// <summary>
/// Sometimes the relative position only changes after the arrange phase.
/// To compensate such sitiations we issue a second layout pass by invalidating our measure.
/// This situation can occur if, for example, the icon of a ribbon window has it's visibility changed.
/// </summary>
private void EnsureCorrectLayoutAfterArrange()
{
var currentRelativePosition = this.GetCurrentRelativePosition();
this.RunInDispatcherAsync(() => this.CheckPosition(currentRelativePosition, this.GetCurrentRelativePosition()));
}
private void CheckPosition(Point previousRelativePosition, Point currentRelativePositon)
{
if (previousRelativePosition != currentRelativePositon)
{
this.InvalidateMeasure();
}
}
private Point GetCurrentRelativePosition()
{
var parentUIElement = this.Parent as UIElement;
if (parentUIElement is null)
{
return default;
}
return this.TranslatePoint(default, parentUIElement);
}
// Update items size and positions
private void Update(Size constraint)
{
var visibleGroups = this.Items.OfType<RibbonContextualTabGroup>()
.Where(group => group.InnerVisibility == Visibility.Visible && group.Items.Count > 0)
.ToList();
var canRibbonTabControlScroll = false;
// Defensively try to find out if the RibbonTabControl can scroll
if (visibleGroups.Count > 0)
{
var firstVisibleItem = visibleGroups.First().FirstVisibleItem;
canRibbonTabControlScroll = UIHelper.GetParent<RibbonTabControl>(firstVisibleItem)?.CanScroll == true;
}
if (this.IsCollapsed)
{
// Collapse QuickAccessToolbar
this.quickAccessToolbarRect = new Rect(0, 0, 0, 0);
// Collapse itemRect
this.itemsRect = new Rect(0, 0, 0, 0);
this.headerHolder.Measure(new Size(constraint.Width, constraint.Height));
this.headerRect = new Rect(0, 0, this.headerHolder.DesiredSize.Width, constraint.Height);
}
else if (visibleGroups.Count == 0
|| canRibbonTabControlScroll)
{
// Collapse itemRect
this.itemsRect = new Rect(0, 0, 0, 0);
// Set quick launch toolbar and header position and size
this.quickAccessToolbarHolder.Measure(SizeConstants.Infinite);
if (constraint.Width <= this.quickAccessToolbarHolder.DesiredSize.Width + 50)
{
this.quickAccessToolbarRect = new Rect(0, 0, Math.Max(0, constraint.Width - 50), this.quickAccessToolbarHolder.DesiredSize.Height);
this.quickAccessToolbarHolder.Measure(this.quickAccessToolbarRect.Size);
}
if (constraint.Width > this.quickAccessToolbarHolder.DesiredSize.Width + 50)
{
this.quickAccessToolbarRect = new Rect(0, 0, this.quickAccessToolbarHolder.DesiredSize.Width, this.quickAccessToolbarHolder.DesiredSize.Height);
this.headerHolder.Measure(SizeConstants.Infinite);
var allTextWidth = constraint.Width - this.quickAccessToolbarHolder.DesiredSize.Width;
if (this.HeaderAlignment == HorizontalAlignment.Left)
{
this.headerRect = new Rect(this.quickAccessToolbarHolder.DesiredSize.Width, 0, Math.Min(allTextWidth, this.headerHolder.DesiredSize.Width), constraint.Height);
}
else if (this.HeaderAlignment == HorizontalAlignment.Center)
{
this.headerRect = new Rect(this.quickAccessToolbarHolder.DesiredSize.Width + Math.Max(0, (allTextWidth / 2) - (this.headerHolder.DesiredSize.Width / 2)), 0, Math.Min(allTextWidth, this.headerHolder.DesiredSize.Width), constraint.Height);
}
else if (this.HeaderAlignment == HorizontalAlignment.Right)
{
this.headerRect = new Rect(this.quickAccessToolbarHolder.DesiredSize.Width + Math.Max(0, allTextWidth - this.headerHolder.DesiredSize.Width), 0, Math.Min(allTextWidth, this.headerHolder.DesiredSize.Width), constraint.Height);
}
else if (this.HeaderAlignment == HorizontalAlignment.Stretch)
{
this.headerRect = new Rect(this.quickAccessToolbarHolder.DesiredSize.Width, 0, allTextWidth, constraint.Height);
}
}
else
{
this.headerRect = new Rect(Math.Max(0, constraint.Width - 50), 0, 50, constraint.Height);
}
}
else
{
var pointZero = default(Point);
// get initial StartX value
var startX = visibleGroups.First().FirstVisibleItem.TranslatePoint(pointZero, this).X;
var endX = 0D;
//Get minimum x point (workaround)
foreach (var group in visibleGroups)
{
var currentStartX = group.FirstVisibleItem.TranslatePoint(pointZero, this).X;
if (currentStartX < startX)
{
startX = currentStartX;
}
var lastItem = group.LastVisibleItem;
var currentEndX = lastItem.TranslatePoint(new Point(lastItem.DesiredSize.Width, 0), this).X;
if (currentEndX > endX)
{
endX = currentEndX;
}
}
// Ensure that startX and endX are never negative
startX = Math.Max(0, startX);
endX = Math.Max(0, endX);
// Ensure that startX respect min width of QuickAccessToolBar
startX = Math.Max(startX, this.QuickAccessToolBar?.MinWidth ?? 0);
// Set contextual groups position and size
this.itemsContainer.Measure(SizeConstants.Infinite);
var itemsRectWidth = Math.Min(this.itemsContainer.DesiredSize.Width, Math.Max(0, Math.Min(endX, constraint.Width) - startX));
this.itemsRect = new Rect(startX, 0, itemsRectWidth, constraint.Height);
// Set quick launch toolbar position and size
this.quickAccessToolbarHolder.Measure(SizeConstants.Infinite);
var quickAccessToolbarWidth = this.quickAccessToolbarHolder.DesiredSize.Width;
this.quickAccessToolbarRect = new Rect(0, 0, Math.Min(quickAccessToolbarWidth, startX), this.quickAccessToolbarHolder.DesiredSize.Height);
if (quickAccessToolbarWidth > startX)
{
this.quickAccessToolbarHolder.Measure(this.quickAccessToolbarRect.Size);
this.quickAccessToolbarRect = new Rect(0, 0, this.quickAccessToolbarHolder.DesiredSize.Width, this.quickAccessToolbarHolder.DesiredSize.Height);
quickAccessToolbarWidth = this.quickAccessToolbarHolder.DesiredSize.Width;
}
// Set header
this.headerHolder.Measure(SizeConstants.Infinite);
switch (this.HeaderAlignment)
{
case HorizontalAlignment.Left:
{
if (startX - quickAccessToolbarWidth > 150)
{
var allTextWidth = startX - quickAccessToolbarWidth;
this.headerRect = new Rect(this.quickAccessToolbarRect.Width, 0, Math.Min(allTextWidth, this.headerHolder.DesiredSize.Width), constraint.Height);
}
else
{
var allTextWidth = Math.Max(0, constraint.Width - endX);
this.headerRect = new Rect(Math.Min(endX, constraint.Width), 0, Math.Min(allTextWidth, this.headerHolder.DesiredSize.Width), constraint.Height);
}
}
break;
case HorizontalAlignment.Center:
{
var allTextWidthRight = Math.Max(0, constraint.Width - endX);
var allTextWidthLeft = Math.Max(0, startX - quickAccessToolbarWidth);
var fitsRightButNotLeft = allTextWidthRight >= this.headerHolder.DesiredSize.Width && allTextWidthLeft < this.headerHolder.DesiredSize.Width;
if (((startX - quickAccessToolbarWidth < 150 || fitsRightButNotLeft) && (startX - quickAccessToolbarWidth > 0) && (startX - quickAccessToolbarWidth < constraint.Width - endX)) || (endX < constraint.Width / 2))
{
this.headerRect = new Rect(Math.Min(Math.Max(endX, (constraint.Width / 2) - (this.headerHolder.DesiredSize.Width / 2)), constraint.Width), 0, Math.Min(allTextWidthRight, this.headerHolder.DesiredSize.Width), constraint.Height);
}
else
{
this.headerRect = new Rect(this.quickAccessToolbarHolder.DesiredSize.Width + Math.Max(0, (allTextWidthLeft / 2) - (this.headerHolder.DesiredSize.Width / 2)), 0, Math.Min(allTextWidthLeft, this.headerHolder.DesiredSize.Width), constraint.Height);
}
}
break;
case HorizontalAlignment.Right:
{
if (startX - quickAccessToolbarWidth > 150)
{
var allTextWidth = Math.Max(0, startX - quickAccessToolbarWidth);
this.headerRect = new Rect(this.quickAccessToolbarHolder.DesiredSize.Width + Math.Max(0, allTextWidth - this.headerHolder.DesiredSize.Width), 0, Math.Min(allTextWidth, this.headerHolder.DesiredSize.Width), constraint.Height);
}
else
{
var allTextWidth = Math.Max(0, constraint.Width - endX);
this.headerRect = new Rect(Math.Min(Math.Max(endX, constraint.Width - this.headerHolder.DesiredSize.Width), constraint.Width), 0, Math.Min(allTextWidth, this.headerHolder.DesiredSize.Width), constraint.Height);
}
}
break;
case HorizontalAlignment.Stretch:
{
if (startX - quickAccessToolbarWidth > 150)
{
var allTextWidth = startX - quickAccessToolbarWidth;
this.headerRect = new Rect(this.quickAccessToolbarRect.Width, 0, allTextWidth, constraint.Height);
}
else
{
var allTextWidth = Math.Max(0, constraint.Width - endX);
this.headerRect = new Rect(Math.Min(endX, constraint.Width), 0, allTextWidth, constraint.Height);
}
}
break;
}
}
this.headerRect.Width += 2;
}
#endregion
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() => new Fluent.Automation.Peers.RibbonTitleBarAutomationPeer(this);
}
}

View File

@@ -0,0 +1,598 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Media;
using Fluent.Extensibility;
using Fluent.Internal;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represent panel for group box panel
/// </summary>
[ContentProperty(nameof(Children))]
[StyleTypedProperty(Property = nameof(SeparatorStyle), StyleTargetType = typeof(Separator))]
public class RibbonToolBar : RibbonControl, IRibbonSizeChangedSink
{
#region Fields
// User defined children
// User defined layout definitions
// Actual children
private readonly List<FrameworkElement> actualChildren = new List<FrameworkElement>();
// Designates that rebuilding of visual & logical children is required
private bool rebuildVisualAndLogicalChildren = true;
#endregion
#region Properties
#region Separator Style
/// <summary>
/// Gets or sets style for the separator
/// </summary>
public Style SeparatorStyle
{
get { return (Style)this.GetValue(SeparatorStyleProperty); }
set { this.SetValue(SeparatorStyleProperty, value); }
}
/// <summary>Identifies the <see cref="SeparatorStyle"/> dependency property.</summary>
public static readonly DependencyProperty SeparatorStyleProperty =
DependencyProperty.Register(nameof(SeparatorStyle), typeof(Style),
typeof(RibbonToolBar), new PropertyMetadata(OnSeparatorStyleChanged));
private static void OnSeparatorStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var toolBar = (RibbonToolBar)d;
toolBar.rebuildVisualAndLogicalChildren = true;
toolBar.InvalidateMeasure();
}
#endregion
/// <summary>
/// Gets children
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ObservableCollection<FrameworkElement> Children { get; } = new ObservableCollection<FrameworkElement>();
/// <summary>
/// Gets particular rules for layout in this group box panel
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ObservableCollection<RibbonToolBarLayoutDefinition> LayoutDefinitions { get; } = new ObservableCollection<RibbonToolBarLayoutDefinition>();
#endregion
#region Logical & Visual Tree
/// <inheritdoc />
protected override int VisualChildrenCount
{
get
{
if (this.LayoutDefinitions.Count == 0)
{
return this.Children.Count;
}
if (this.rebuildVisualAndLogicalChildren)
{
//TODO: Exception during theme changing
// UpdateLayout();
this.InvalidateMeasure();
}
return this.actualChildren.Count;
}
}
/// <inheritdoc />
protected override Visual GetVisualChild(int index)
{
if (this.LayoutDefinitions.Count == 0)
{
return this.Children[index];
}
if (this.rebuildVisualAndLogicalChildren)
{
// UpdateLayout();
this.InvalidateMeasure();
}
return this.actualChildren[index];
}
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.Icon != null)
{
yield return this.Icon;
}
foreach (var child in this.Children)
{
yield return child;
}
}
}
#endregion
#region Initialization
/// <summary>
/// Static constructor
/// </summary>
static RibbonToolBar()
{
// Override default style
DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonToolBar), new FrameworkPropertyMetadata(typeof(RibbonToolBar)));
// Disable QAT for this control
CanAddToQuickAccessToolBarProperty.OverrideMetadata(typeof(RibbonToolBar), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));
}
/// <summary>
/// Default constructor
/// </summary>
public RibbonToolBar()
{
this.Children.CollectionChanged += this.OnChildrenCollectionChanged;
this.LayoutDefinitions.CollectionChanged += this.OnLayoutDefinitionsChanged;
}
private void OnLayoutDefinitionsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
this.rebuildVisualAndLogicalChildren = true;
this.InvalidateMeasure();
}
private void OnChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Children have changed, reset layouts
this.rebuildVisualAndLogicalChildren = true;
this.InvalidateMeasure();
}
#endregion
#region Methods
/// <summary>
/// Gets current used layout definition (or null if no present definitions)
/// </summary>
/// <returns>Layout definition or null</returns>
internal RibbonToolBarLayoutDefinition GetCurrentLayoutDefinition()
{
if (this.LayoutDefinitions.Count == 0)
{
return null;
}
if (this.LayoutDefinitions.Count == 1)
{
return this.LayoutDefinitions[0];
}
foreach (var definition in this.LayoutDefinitions)
{
if (RibbonProperties.GetSize(definition) == RibbonProperties.GetSize(this))
{
return definition;
}
}
// TODO: try to find a better definition
return this.LayoutDefinitions[0];
}
#endregion
#region Size Property Changing
/// <inheritdoc />
public void OnSizePropertyChanged(RibbonControlSize previous, RibbonControlSize current)
{
foreach (var frameworkElement in this.actualChildren)
{
RibbonProperties.SetSize(frameworkElement, current);
}
this.rebuildVisualAndLogicalChildren = true;
this.InvalidateMeasure();
}
#endregion
#region Layout Overriding
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize)
{
var layoutDefinition = this.GetCurrentLayoutDefinition();
// Rebuilding actual children (visual & logical)
if (this.rebuildVisualAndLogicalChildren)
{
// Clear previous children
foreach (var child in this.actualChildren)
{
var controlGroup = child as RibbonToolBarControlGroup;
controlGroup?.Items.Clear();
this.RemoveVisualChild(child);
this.RemoveLogicalChild(child);
}
this.actualChildren.Clear();
this.cachedControlGroups.Clear();
}
if (layoutDefinition is null)
{
if (this.rebuildVisualAndLogicalChildren)
{
// If default layout is used add all children
foreach (var child in this.Children)
{
this.actualChildren.Add(child);
this.AddVisualChild(child);
this.AddLogicalChild(child);
}
this.rebuildVisualAndLogicalChildren = false;
}
return this.WrapPanelLayuot(availableSize, true);
}
else
{
var result = this.CustomLayout(layoutDefinition, availableSize, true, this.rebuildVisualAndLogicalChildren);
this.rebuildVisualAndLogicalChildren = false;
return result;
}
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize)
{
var layoutDefinition = this.GetCurrentLayoutDefinition();
if (layoutDefinition is null)
{
return this.WrapPanelLayuot(finalSize, false);
}
return this.CustomLayout(layoutDefinition, finalSize, false, false);
}
#region Wrap Panel Layout
/// <summary>
/// Unified method for wrap panel logic
/// </summary>
/// <param name="availableSize">Available or final size</param>
/// <param name="measure">Pass true if measure required; pass false if arrange required</param>
/// <returns>Final size</returns>
private Size WrapPanelLayuot(Size availableSize, bool measure)
{
var arrange = !measure;
var availableHeight = double.IsPositiveInfinity(availableSize.Height)
? 0
: availableSize.Height;
double currentheight = 0;
double columnWidth = 0;
double resultWidth = 0;
double resultHeight = 0;
foreach (var child in this.Children)
{
// Measuring
if (measure)
{
child.Measure(SizeConstants.Infinite);
}
if (currentheight + child.DesiredSize.Height > availableHeight)
{
// Move to the next column
resultHeight = Math.Max(resultHeight, currentheight);
resultWidth += columnWidth;
currentheight = 0;
columnWidth = 0;
}
// Arranging
if (arrange)
{
child.Arrange(new Rect(resultWidth, currentheight, child.DesiredSize.Width, child.DesiredSize.Height));
}
columnWidth = Math.Max(columnWidth, child.DesiredSize.Width);
currentheight += child.DesiredSize.Height;
}
return new Size(resultWidth + columnWidth, resultHeight);
}
#endregion
#region Control and Group Creation from a Definition
private FrameworkElement GetControl(RibbonToolBarControlDefinition controlDefinition)
{
var name = controlDefinition.Target;
return this.Children.FirstOrDefault(x => x.Name == name);
}
private readonly Dictionary<object, RibbonToolBarControlGroup> cachedControlGroups = new Dictionary<object, RibbonToolBarControlGroup>();
private RibbonToolBarControlGroup GetControlGroup(RibbonToolBarControlGroupDefinition controlGroupDefinition)
{
if (this.cachedControlGroups.TryGetValue(controlGroupDefinition, out var controlGroup))
{
return controlGroup;
}
controlGroup = new RibbonToolBarControlGroup();
// Add items to the group
foreach (var child in controlGroupDefinition.Children)
{
controlGroup.Items.Add(this.GetControl(child));
}
this.cachedControlGroups.Add(controlGroupDefinition, controlGroup);
return controlGroup;
}
#endregion
#region Custom Layout
// Cached separators (clear & set in Measure pass)
private readonly Dictionary<int, Separator> separatorCache = new Dictionary<int, Separator>();
/// <summary>
/// Layout logic for the given layout definition
/// </summary>
/// <param name="layoutDefinition">Current layout definition</param>
/// <param name="availableSize">Available or final size</param>
/// <param name="measure">Pass true if measure required; pass false if arrange required</param>
/// <param name="addchildren">Determines whether we have to add children to the logical and visual tree</param>
/// <returns>Final size</returns>
private Size CustomLayout(RibbonToolBarLayoutDefinition layoutDefinition, Size availableSize, bool measure, bool addchildren)
{
var arrange = !measure;
var availableHeight = double.IsPositiveInfinity(availableSize.Height)
? 0
: availableSize.Height;
// Clear separator cahce
if (addchildren)
{
this.separatorCache.Clear();
}
// Get the first control and measure, its height accepts as row height
var rowHeight = this.GetRowHeight(layoutDefinition);
// Calculate whitespace
var rowCountInColumn = Math.Min(layoutDefinition.RowCount, layoutDefinition.Rows.Count);
var whitespace = (availableHeight - (rowCountInColumn * rowHeight)) / (rowCountInColumn + 1);
double y = 0;
double currentRowBegin = 0;
double currentMaxX = 0;
double maxy = 0;
for (var rowIndex = 0; rowIndex < layoutDefinition.Rows.Count; rowIndex++)
{
var row = layoutDefinition.Rows[rowIndex];
var x = currentRowBegin;
if (rowIndex % rowCountInColumn == 0)
{
// Reset vars at new column
x = currentRowBegin = currentMaxX;
y = 0;
if (rowIndex != 0)
{
#region Add separator
if (this.separatorCache.TryGetValue(rowIndex, out var separator) == false)
{
separator = new Separator
{
Style = this.SeparatorStyle
};
this.separatorCache.Add(rowIndex, separator);
}
if (measure)
{
separator.Height = availableHeight - separator.Margin.Bottom - separator.Margin.Top;
separator.Measure(availableSize);
}
if (arrange)
{
separator.Arrange(new Rect(x, y, separator.DesiredSize.Width, separator.DesiredSize.Height));
}
x += separator.DesiredSize.Width;
if (addchildren)
{
// Add control in the children
this.AddVisualChild(separator);
this.AddLogicalChild(separator);
this.actualChildren.Add(separator);
}
#endregion
}
}
y += whitespace;
// Measure & arrange new row
for (var i = 0; i < row.Children.Count; i++)
{
// Control Definition Case
if (row.Children[i] is RibbonToolBarControlDefinition ribbonToolBarControlDefinition)
{
var control = this.GetControl(ribbonToolBarControlDefinition);
if (control is null)
{
continue;
}
if (addchildren)
{
// Add control in the children
this.AddVisualChild(control);
this.AddLogicalChild(control);
this.actualChildren.Add(control);
}
if (measure)
{
// Apply Control Definition Properties
RibbonProperties.SetSize(control, RibbonProperties.GetSize(ribbonToolBarControlDefinition));
control.Width = ribbonToolBarControlDefinition.Width;
control.Measure(availableSize);
}
if (arrange)
{
control.Arrange(new Rect(x, y, control.DesiredSize.Width, control.DesiredSize.Height));
}
x += control.DesiredSize.Width;
}
// Control Group Definition Case
if (row.Children[i] is RibbonToolBarControlGroupDefinition ribbonToolBarControlGroupDefinition)
{
var control = this.GetControlGroup(ribbonToolBarControlGroupDefinition);
if (addchildren)
{
// Add control in the children
this.AddVisualChild(control);
this.AddLogicalChild(control);
this.actualChildren.Add(control);
}
if (measure)
{
// Apply Control Definition Properties
control.IsFirstInRow = i == 0;
control.IsLastInRow = i == row.Children.Count - 1;
control.Measure(availableSize);
}
if (arrange)
{
control.Arrange(new Rect(x, y, control.DesiredSize.Width, control.DesiredSize.Height));
}
x += control.DesiredSize.Width;
}
}
y += rowHeight;
if (currentMaxX < x)
{
currentMaxX = x;
}
if (maxy < y)
{
maxy = y;
}
}
return new Size(currentMaxX, maxy + whitespace);
}
// Get the first control and measure, its height accepts as row height
private double GetRowHeight(RibbonToolBarLayoutDefinition layoutDefinition)
{
const double defaultRowHeight = 0;
foreach (var row in layoutDefinition.Rows)
{
foreach (var item in row.Children)
{
var controlDefinition = item as RibbonToolBarControlDefinition;
var controlGroupDefinition = item as RibbonToolBarControlGroupDefinition;
FrameworkElement control = null;
if (controlDefinition != null)
{
control = this.GetControl(controlDefinition);
}
else if (controlGroupDefinition != null)
{
control = this.GetControlGroup(controlGroupDefinition);
}
if (control is null)
{
return defaultRowHeight;
}
control.Measure(SizeConstants.Infinite);
return control.DesiredSize.Height;
}
}
return defaultRowHeight;
}
#endregion
#endregion
#region QAT Support
/// <inheritdoc />
public override FrameworkElement CreateQuickAccessItem()
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@@ -0,0 +1,125 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using Fluent.Extensibility;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represent logical definition for a control in toolbar
/// </summary>
public sealed class RibbonToolBarControlDefinition : DependencyObject, INotifyPropertyChanged, IRibbonSizeChangedSink
{
/// <summary>
/// Creates a new instance
/// </summary>
public RibbonToolBarControlDefinition()
{
RibbonProperties.SetSize(this, RibbonControlSize.Small);
}
#region Size
/// <summary>
/// Gets or sets Size for the element.
/// </summary>
public RibbonControlSize Size
{
get { return (RibbonControlSize)this.GetValue(SizeProperty); }
set { this.SetValue(SizeProperty, value); }
}
/// <summary>Identifies the <see cref="Size"/> dependency property.</summary>
public static readonly DependencyProperty SizeProperty = RibbonProperties.SizeProperty.AddOwner(typeof(RibbonToolBarControlDefinition));
#endregion
#region SizeDefinition
/// <summary>
/// Gets or sets SizeDefinition for element.
/// </summary>
public RibbonControlSizeDefinition SizeDefinition
{
get { return (RibbonControlSizeDefinition)this.GetValue(SizeDefinitionProperty); }
set { this.SetValue(SizeDefinitionProperty, value); }
}
/// <summary>Identifies the <see cref="SizeDefinition"/> dependency property.</summary>
public static readonly DependencyProperty SizeDefinitionProperty = RibbonProperties.SizeDefinitionProperty.AddOwner(typeof(RibbonToolBarControlDefinition));
#endregion
#region Target Property
/// <summary>
/// Gets or sets name of the target control
/// </summary>
public string Target
{
get { return (string)this.GetValue(TargetProperty); }
set { this.SetValue(TargetProperty, value); }
}
/// <summary>Identifies the <see cref="Target"/> dependency property.</summary>
public static readonly DependencyProperty TargetProperty =
DependencyProperty.Register(nameof(Target), typeof(string),
typeof(RibbonToolBarControlDefinition), new PropertyMetadata(OnTargetChanged));
private static void OnTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var definition = (RibbonToolBarControlDefinition)d;
definition.OnPropertyChanged(nameof(Target));
}
#endregion
#region Width Property
/// <summary>
/// Gets or sets width of the target control
/// </summary>
public double Width
{
get { return (double)this.GetValue(WidthProperty); }
set { this.SetValue(WidthProperty, value); }
}
/// <summary>Identifies the <see cref="Width"/> dependency property.</summary>
public static readonly DependencyProperty WidthProperty =
DependencyProperty.Register(nameof(Width), typeof(double), typeof(RibbonToolBarControlDefinition), new PropertyMetadata(DoubleBoxes.NaN, OnWidthChanged));
private static void OnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var definition = (RibbonToolBarControlDefinition)d;
definition.OnPropertyChanged(nameof(Width));
}
#endregion
#region Invalidating
/// <inheritdoc />
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region Implementation of IRibbonSizeChangedSink
/// <inheritdoc />
public void OnSizePropertyChanged(RibbonControlSize previous, RibbonControlSize current)
{
// todo: do we really need this? Size is a DependencyProperty.
this.OnPropertyChanged(nameof(this.Size));
}
#endregion
}
}

View File

@@ -0,0 +1,50 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represent logical container for toolbar items
/// </summary>
[ContentProperty(nameof(Items))]
public class RibbonToolBarControlGroup : ItemsControl
{
#region Properties
/// <summary>
/// Gets whether the group is the fisrt control in the row
/// </summary>
public bool IsFirstInRow
{
get { return (bool)this.GetValue(IsFirstInRowProperty); }
set { this.SetValue(IsFirstInRowProperty, value); }
}
/// <summary>Identifies the <see cref="IsFirstInRow"/> dependency property.</summary>
public static readonly DependencyProperty IsFirstInRowProperty =
DependencyProperty.Register(nameof(IsFirstInRow), typeof(bool), typeof(RibbonToolBarControlGroup), new PropertyMetadata(BooleanBoxes.TrueBox));
/// <summary>
/// Gets whether the group is the last control in the row
/// </summary>
public bool IsLastInRow
{
get { return (bool)this.GetValue(IsLastInRowProperty); }
set { this.SetValue(IsLastInRowProperty, value); }
}
/// <summary>Identifies the <see cref="IsLastInRow"/> dependency property.</summary>
public static readonly DependencyProperty IsLastInRowProperty =
DependencyProperty.Register(nameof(IsLastInRow), typeof(bool), typeof(RibbonToolBarControlGroup), new PropertyMetadata(BooleanBoxes.TrueBox));
#endregion
static RibbonToolBarControlGroup()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonToolBarControlGroup), new FrameworkPropertyMetadata(typeof(RibbonToolBarControlGroup)));
}
}
}

View File

@@ -0,0 +1,58 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows;
using System.Windows.Markup;
/// <summary>
/// Represent logical container for toolbar items
/// </summary>
[ContentProperty(nameof(Children))]
public class RibbonToolBarControlGroupDefinition : DependencyObject
{
#region Events
/// <summary>
/// Occures when children has been changed
/// </summary>
public event NotifyCollectionChangedEventHandler ChildrenChanged;
#endregion
#region Fields
// User defined rows
#endregion
#region Children Property
/// <summary>
/// Gets rows
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ObservableCollection<RibbonToolBarControlDefinition> Children { get; } = new ObservableCollection<RibbonToolBarControlDefinition>();
#endregion
#region Initialization
/// <summary>
/// Default constructor
/// </summary>
public RibbonToolBarControlGroupDefinition()
{
this.Children.CollectionChanged += this.OnChildrenCollectionChanged;
}
private void OnChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
this.ChildrenChanged?.Invoke(sender, e);
}
#endregion
}
}

View File

@@ -0,0 +1,80 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Markup;
/// <summary>
/// Represents size definition for group box
/// </summary>
[ContentProperty(nameof(Rows))]
public class RibbonToolBarLayoutDefinition : DependencyObject
{
#region Fields
// User defined rows
#endregion
#region Properties
#region Size
/// <summary>
/// Gets or sets Size for the element.
/// </summary>
public RibbonControlSize Size
{
get { return (RibbonControlSize)this.GetValue(SizeProperty); }
set { this.SetValue(SizeProperty, value); }
}
/// <summary>Identifies the <see cref="Size"/> dependency property.</summary>
public static readonly DependencyProperty SizeProperty = RibbonProperties.SizeProperty.AddOwner(typeof(RibbonToolBarLayoutDefinition));
#endregion
#region SizeDefinition
/// <summary>
/// Gets or sets SizeDefinition for element.
/// </summary>
public RibbonControlSizeDefinition SizeDefinition
{
get { return (RibbonControlSizeDefinition)this.GetValue(SizeDefinitionProperty); }
set { this.SetValue(SizeDefinitionProperty, value); }
}
/// <summary>Identifies the <see cref="SizeDefinition"/> dependency property.</summary>
public static readonly DependencyProperty SizeDefinitionProperty = RibbonProperties.SizeDefinitionProperty.AddOwner(typeof(RibbonToolBarLayoutDefinition));
#endregion
#region Row Count
/// <summary>
/// Gets or sets count of rows in the ribbon toolbar
/// </summary>
public int RowCount
{
get { return (int)this.GetValue(RowCountProperty); }
set { this.SetValue(RowCountProperty, value); }
}
/// <summary>Identifies the <see cref="RowCount"/> dependency property.</summary>
public static readonly DependencyProperty RowCountProperty =
DependencyProperty.Register(nameof(RowCount), typeof(int), typeof(RibbonToolBarLayoutDefinition), new PropertyMetadata(3));
#endregion
/// <summary>
/// Gets rows
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ObservableCollection<RibbonToolBarRow> Rows { get; } = new ObservableCollection<RibbonToolBarRow>();
#endregion
}
}

View File

@@ -0,0 +1,31 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Windows;
using System.Windows.Markup;
/// <summary>
/// Represents size definition for group box
/// </summary>
[ContentProperty(nameof(Children))]
[SuppressMessage("Microsoft.Naming", "CA1702", Justification = "We mean here 'bar row' instead of 'barrow'")]
public class RibbonToolBarRow : DependencyObject
{
#region Properties
/// <summary>
/// Gets rows
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ObservableCollection<DependencyObject> Children { get; } = new ObservableCollection<DependencyObject>();
#endregion
#region Initialization
#endregion
}
}

View File

@@ -0,0 +1,429 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using ControlzEx.Behaviors;
using Fluent.Extensions;
using Fluent.Helpers;
using Fluent.Internal.KnownBoxes;
using Microsoft.Xaml.Behaviors;
using WindowChrome = ControlzEx.Windows.Shell.WindowChrome;
/// <summary>
/// Represents basic window for ribbon
/// </summary>
[TemplatePart(Name = PART_Icon, Type = typeof(UIElement))]
[TemplatePart(Name = PART_ContentPresenter, Type = typeof(UIElement))]
[TemplatePart(Name = PART_RibbonTitleBar, Type = typeof(RibbonTitleBar))]
[TemplatePart(Name = PART_WindowCommands, Type = typeof(WindowCommands))]
public class RibbonWindow : Window, IRibbonWindow
{
// ReSharper disable InconsistentNaming
#pragma warning disable SA1310 // Field names must not contain underscore
private const string PART_Icon = "PART_Icon";
private const string PART_ContentPresenter = "PART_ContentPresenter";
private const string PART_RibbonTitleBar = "PART_RibbonTitleBar";
private const string PART_WindowCommands = "PART_WindowCommands";
#pragma warning restore SA1310 // Field names must not contain underscore
// ReSharper restore InconsistentNaming
private FrameworkElement iconImage;
#region Properties
#region TitelBar
/// <inheritdoc />
public RibbonTitleBar TitleBar
{
get { return (RibbonTitleBar)this.GetValue(TitleBarProperty); }
private set { this.SetValue(TitleBarPropertyKey, value); }
}
// ReSharper disable once InconsistentNaming
private static readonly DependencyPropertyKey TitleBarPropertyKey = DependencyProperty.RegisterReadOnly(nameof(TitleBar), typeof(RibbonTitleBar), typeof(RibbonWindow), new PropertyMetadata());
/// <summary>Identifies the <see cref="TitleBar"/> dependency property.</summary>
public static readonly DependencyProperty TitleBarProperty = TitleBarPropertyKey.DependencyProperty;
#endregion
/// <summary>
/// Gets or sets the height which is used to render the window title.
/// </summary>
public double TitleBarHeight
{
get { return (double)this.GetValue(TitleBarHeightProperty); }
set { this.SetValue(TitleBarHeightProperty, value); }
}
/// <summary>Identifies the <see cref="TitleBarHeight"/> dependency property.</summary>
public static readonly DependencyProperty TitleBarHeightProperty = DependencyProperty.Register(nameof(TitleBarHeight), typeof(double), typeof(RibbonWindow), new PropertyMetadata(DoubleBoxes.Zero));
/// <summary>
/// Gets or sets the <see cref="Brush"/> which is used to render the window title.
/// </summary>
public Brush TitleForeground
{
get { return (Brush)this.GetValue(TitleForegroundProperty); }
set { this.SetValue(TitleForegroundProperty, value); }
}
/// <summary>Identifies the <see cref="TitleForeground"/> dependency property.</summary>
public static readonly DependencyProperty TitleForegroundProperty = DependencyProperty.Register(nameof(TitleForeground), typeof(Brush), typeof(RibbonWindow), new PropertyMetadata());
/// <summary>
/// Gets or sets the <see cref="Brush"/> which is used to render the window title background.
/// </summary>
public Brush TitleBackground
{
get { return (Brush)this.GetValue(TitleBackgroundProperty); }
set { this.SetValue(TitleBackgroundProperty, value); }
}
/// <summary>Identifies the <see cref="TitleBackground"/> dependency property.</summary>
public static readonly DependencyProperty TitleBackgroundProperty = DependencyProperty.Register(nameof(TitleBackground), typeof(Brush), typeof(RibbonWindow), new PropertyMetadata());
/// <summary>Identifies the <see cref="WindowCommands"/> dependency property.</summary>
public static readonly DependencyProperty WindowCommandsProperty = DependencyProperty.Register(nameof(WindowCommands), typeof(WindowCommands), typeof(RibbonWindow), new PropertyMetadata());
/// <summary>
/// Gets or sets the window commands
/// </summary>
public WindowCommands WindowCommands
{
get { return (WindowCommands)this.GetValue(WindowCommandsProperty); }
set { this.SetValue(WindowCommandsProperty, value); }
}
#region Window-Border-Properties
/// <summary>
/// Gets or sets resize border thickness.
/// </summary>
public Thickness ResizeBorderThickness
{
get { return (Thickness)this.GetValue(ResizeBorderThicknessProperty); }
set { this.SetValue(ResizeBorderThicknessProperty, value); }
}
/// <summary>Identifies the <see cref="ResizeBorderThickness"/> dependency property.</summary>
public static readonly DependencyProperty ResizeBorderThicknessProperty = DependencyProperty.Register(nameof(ResizeBorderThickness), typeof(Thickness), typeof(RibbonWindow), new PropertyMetadata(new Thickness(6D)));
/// <summary>Identifies the <see cref="GlowBrush"/> dependency property.</summary>
public static readonly DependencyProperty GlowBrushProperty = DependencyProperty.Register(nameof(GlowBrush), typeof(Brush), typeof(RibbonWindow), new PropertyMetadata(default(Brush)));
/// <summary>
/// Gets or sets a brush which is used as the glow when the window is active.
/// </summary>
public Brush GlowBrush
{
get { return (Brush)this.GetValue(GlowBrushProperty); }
set { this.SetValue(GlowBrushProperty, value); }
}
/// <summary>Identifies the <see cref="NonActiveGlowBrush"/> dependency property.</summary>
public static readonly DependencyProperty NonActiveGlowBrushProperty = DependencyProperty.Register(nameof(NonActiveGlowBrush), typeof(Brush), typeof(RibbonWindow), new PropertyMetadata(default(Brush)));
/// <summary>
/// Gets or sets a brush which is used as the glow when the window is not active.
/// </summary>
public Brush NonActiveGlowBrush
{
get { return (Brush)this.GetValue(NonActiveGlowBrushProperty); }
set { this.SetValue(NonActiveGlowBrushProperty, value); }
}
/// <summary>Identifies the <see cref="NonActiveBorderBrush"/> dependency property.</summary>
public static readonly DependencyProperty NonActiveBorderBrushProperty = DependencyProperty.Register(nameof(NonActiveBorderBrush), typeof(Brush), typeof(RibbonWindow), new PropertyMetadata(default(Brush)));
/// <summary>
/// Gets or sets a brush which is used as the border brush when the window is not active.
/// </summary>
public Brush NonActiveBorderBrush
{
get { return (Brush)this.GetValue(NonActiveBorderBrushProperty); }
set { this.SetValue(NonActiveBorderBrushProperty, value); }
}
#endregion
/// <summary>
/// Gets or sets whether icon is visible.
/// </summary>
public bool IsIconVisible
{
get { return (bool)this.GetValue(IsIconVisibleProperty); }
set { this.SetValue(IsIconVisibleProperty, value); }
}
/// <summary>Identifies the <see cref="IsIconVisible"/> dependency property.</summary>
public static readonly DependencyProperty IsIconVisibleProperty = DependencyProperty.Register(nameof(IsIconVisible), typeof(bool), typeof(RibbonWindow), new FrameworkPropertyMetadata(BooleanBoxes.TrueBox));
/// <summary>
/// Gets or sets the vertical alignment of the icon.
/// </summary>
public VerticalAlignment VerticalIconAlignment
{
get { return (VerticalAlignment)this.GetValue(VerticalIconAlignmentProperty); }
set { this.SetValue(VerticalIconAlignmentProperty, value); }
}
/// <summary>Identifies the <see cref="VerticalIconAlignment"/> dependency property.</summary>
public static readonly DependencyProperty VerticalIconAlignmentProperty = DependencyProperty.Register(nameof(VerticalIconAlignment), typeof(VerticalAlignment), typeof(RibbonWindow), new PropertyMetadata(VerticalAlignment.Top));
// todo check if IsCollapsed and IsAutomaticCollapseEnabled should be reduced to one shared property for RibbonWindow and Ribbon
/// <summary>
/// Gets whether window is collapsed
/// </summary>
public bool IsCollapsed
{
get { return (bool)this.GetValue(IsCollapsedProperty); }
set { this.SetValue(IsCollapsedProperty, value); }
}
/// <summary>Identifies the <see cref="IsCollapsed"/> dependency property.</summary>
public static readonly DependencyProperty IsCollapsedProperty = DependencyProperty.Register(nameof(IsCollapsed), typeof(bool), typeof(RibbonWindow), new PropertyMetadata(BooleanBoxes.FalseBox));
/// <summary>
/// Defines if the Ribbon should automatically set <see cref="IsCollapsed"/> when the width or height of the owner window drop under <see cref="Ribbon.MinimalVisibleWidth"/> or <see cref="Ribbon.MinimalVisibleHeight"/>
/// </summary>
public bool IsAutomaticCollapseEnabled
{
get { return (bool)this.GetValue(IsAutomaticCollapseEnabledProperty); }
set { this.SetValue(IsAutomaticCollapseEnabledProperty, value); }
}
/// <summary>Identifies the <see cref="IsAutomaticCollapseEnabled"/> dependency property.</summary>
public static readonly DependencyProperty IsAutomaticCollapseEnabledProperty = DependencyProperty.Register(nameof(IsAutomaticCollapseEnabled), typeof(bool), typeof(RibbonWindow), new PropertyMetadata(BooleanBoxes.TrueBox));
/// <summary>
/// Defines if the taskbar should be ignored and hidden while the window is maximized.
/// </summary>
public bool IgnoreTaskbarOnMaximize
{
get { return (bool)this.GetValue(IgnoreTaskbarOnMaximizeProperty); }
set { this.SetValue(IgnoreTaskbarOnMaximizeProperty, value); }
}
/// <summary>Identifies the <see cref="IgnoreTaskbarOnMaximize"/> dependency property.</summary>
public static readonly DependencyProperty IgnoreTaskbarOnMaximizeProperty = DependencyProperty.Register(nameof(IgnoreTaskbarOnMaximize), typeof(bool), typeof(RibbonWindow), new PropertyMetadata(default(bool)));
#endregion
#region Constructors
/// <summary>
/// Static constructor
/// </summary>
static RibbonWindow()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(RibbonWindow), new FrameworkPropertyMetadata(typeof(RibbonWindow)));
BorderThicknessProperty.OverrideMetadata(typeof(RibbonWindow), new FrameworkPropertyMetadata(new Thickness(1)));
WindowStyleProperty.OverrideMetadata(typeof(RibbonWindow), new FrameworkPropertyMetadata(WindowStyle.None));
AllowsTransparencyProperty.OverrideMetadata(typeof(RibbonWindow), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));
}
/// <summary>
/// Default constructor
/// </summary>
public RibbonWindow()
{
this.SizeChanged += this.OnSizeChanged;
this.Loaded += this.OnLoaded;
this.ContentRendered += this.OnContentRendered;
// WindowChromeBehavior initialization has to occur in constructor. Otherwise the load event is fired early and performance of the window is degraded.
this.InitializeWindowChromeBehavior();
}
#endregion
#region Behaviors
/// <summary>
/// Initializes the WindowChromeBehavior which is needed to render the custom WindowChrome.
/// </summary>
private void InitializeWindowChromeBehavior()
{
var behavior = new WindowChromeBehavior();
BindingOperations.SetBinding(behavior, WindowChromeBehavior.ResizeBorderThicknessProperty, new Binding { Path = new PropertyPath(ResizeBorderThicknessProperty), Source = this });
BindingOperations.SetBinding(behavior, WindowChromeBehavior.IgnoreTaskbarOnMaximizeProperty, new Binding { Path = new PropertyPath(IgnoreTaskbarOnMaximizeProperty), Source = this });
BindingOperations.SetBinding(behavior, GlowWindowBehavior.GlowBrushProperty, new Binding { Path = new PropertyPath(GlowBrushProperty), Source = this });
Interaction.GetBehaviors(this).Add(behavior);
}
/// <summary>
/// Initializes the GlowWindowBehavior which is needed to render the custom resize windows around the current window.
/// </summary>
private void InitializeGlowWindowBehavior()
{
var behavior = new GlowWindowBehavior();
BindingOperations.SetBinding(behavior, GlowWindowBehavior.ResizeBorderThicknessProperty, new Binding { Path = new PropertyPath(ResizeBorderThicknessProperty), Source = this });
BindingOperations.SetBinding(behavior, GlowWindowBehavior.GlowBrushProperty, new Binding { Path = new PropertyPath(GlowBrushProperty), Source = this });
BindingOperations.SetBinding(behavior, GlowWindowBehavior.NonActiveGlowBrushProperty, new Binding { Path = new PropertyPath(NonActiveGlowBrushProperty), Source = this });
Interaction.GetBehaviors(this).Add(behavior);
}
#endregion
// Size change to collapse ribbon
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
this.MaintainIsCollapsed();
if (this.iconImage != null
&& this.ActualWidth <= 140D + RibbonProperties.GetLastVisibleWidth(this.iconImage).GetZeroIfInfinityOrNaN() + RibbonProperties.GetLastVisibleWidth(this.WindowCommands.ItemsControl).GetZeroIfInfinityOrNaN())
{
this.SetCurrentValue(IsIconVisibleProperty, false);
this.TitleBar.SetCurrentValue(VisibilityProperty, Visibility.Collapsed);
this.WindowCommands.SetCurrentValue(WindowCommands.ItemsPanelVisibilityProperty, Visibility.Collapsed);
}
else if (this.iconImage != null)
{
this.InvalidateProperty(IsIconVisibleProperty);
this.iconImage.SetValue(RibbonProperties.LastVisibleWidthProperty, this.iconImage.ActualWidth);
this.TitleBar.InvalidateProperty(VisibilityProperty);
this.WindowCommands.InvalidateProperty(WindowCommands.ItemsPanelVisibilityProperty);
this.WindowCommands.ItemsControl.SetValue(RibbonProperties.LastVisibleWidthProperty, this.WindowCommands.ItemsControl.ActualWidth);
}
}
private void OnContentRendered(object sender, EventArgs e)
{
this.ContentRendered -= this.OnContentRendered;
this.InitializeGlowWindowBehavior();
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (this.SizeToContent == SizeToContent.Manual)
{
return;
}
this.RunInDispatcherAsync(() =>
{
// Fix for #454 while also keeping #473
var availableSize = new Size(this.TitleBar.ActualWidth, this.TitleBar.ActualHeight);
this.TitleBar.Measure(availableSize);
this.TitleBar.ForceMeasureAndArrange();
}, DispatcherPriority.ApplicationIdle);
}
private void MaintainIsCollapsed()
{
if (this.IsAutomaticCollapseEnabled == false)
{
return;
}
if (this.ActualWidth < Ribbon.MinimalVisibleWidth
|| this.ActualHeight < Ribbon.MinimalVisibleHeight)
{
this.IsCollapsed = true;
}
else
{
this.IsCollapsed = false;
}
}
/// <inheritdoc />
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.TitleBar = this.GetTemplateChild(PART_RibbonTitleBar) as RibbonTitleBar;
if (this.iconImage != null)
{
this.iconImage.MouseDown -= this.HandleIconMouseDown;
}
if (this.WindowCommands is null)
{
this.WindowCommands = new WindowCommands();
}
this.iconImage = this.GetPart<FrameworkElement>(PART_Icon);
if (this.iconImage != null)
{
this.iconImage.MouseDown += this.HandleIconMouseDown;
}
this.GetPart<UIElement>(PART_Icon)?.SetValue(WindowChrome.IsHitTestVisibleInChromeProperty, true);
this.GetPart<UIElement>(PART_WindowCommands)?.SetValue(WindowChrome.IsHitTestVisibleInChromeProperty, true);
}
/// <inheritdoc />
protected override void OnStateChanged(EventArgs e)
{
base.OnStateChanged(e);
// todo: remove fix if we update to ControlzEx 4.0
if (this.WindowState == WindowState.Maximized
&& this.SizeToContent != SizeToContent.Manual)
{
this.SizeToContent = SizeToContent.Manual;
}
this.RunInDispatcherAsync(() => this.TitleBar?.ForceMeasureAndArrange(), DispatcherPriority.Background);
}
private void HandleIconMouseDown(object sender, MouseButtonEventArgs e)
{
switch (e.ChangedButton)
{
case MouseButton.Left:
if (e.ClickCount == 1)
{
e.Handled = true;
WindowSteeringHelper.ShowSystemMenu(this, this.PointToScreen(new Point(0, this.TitleBarHeight)));
}
else if (e.ClickCount == 2)
{
e.Handled = true;
#pragma warning disable 618
ControlzEx.Windows.Shell.SystemCommands.CloseWindow(this);
#pragma warning restore 618
}
break;
case MouseButton.Right:
e.Handled = true;
WindowSteeringHelper.ShowSystemMenu(this, e);
break;
}
}
/// <summary>
/// Gets the template child with the given name.
/// </summary>
/// <typeparam name="T">The interface type inheirted from DependencyObject.</typeparam>
/// <param name="name">The name of the template child.</param>
internal T GetPart<T>(string name)
where T : DependencyObject
{
return this.GetTemplateChild(name) as T;
}
}
}

View File

@@ -0,0 +1,436 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using Fluent.Helpers;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// ScreenTips display the name of the control,
/// the keyboard shortcut for the control, and a brief description
/// of how to use the control. ScreenTips also can provide F1 support,
/// which opens help and takes the user directly to the related
/// help topic for the control whose ScreenTip was
/// displayed when the F1 button was pressed
/// </summary>
public class ScreenTip : ToolTip, ILogicalChildSupport
{
#region Initialization
/// <summary>
/// Static constructor
/// </summary>
static ScreenTip()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ScreenTip), new FrameworkPropertyMetadata(typeof(ScreenTip)));
}
/// <summary>
/// Default constructor
/// </summary>
public ScreenTip()
{
this.Opened += this.OnToolTipOpened;
this.Closed += this.OnToolTipClosed;
this.CustomPopupPlacementCallback = this.CustomPopupPlacementMethod;
this.Placement = PlacementMode.Custom;
this.HelpLabelVisibility = Visibility.Visible;
}
#endregion
#region Popup Custom Placement
// Calculate two variants: below and upper ribbon
private CustomPopupPlacement[] CustomPopupPlacementMethod(Size popupSize, Size targetSize, Point offset)
{
if (this.PlacementTarget is null)
{
#if NETCOREAPP3_0
return Array.Empty<CustomPopupPlacement>();
#else
return new CustomPopupPlacement[] { };
#endif
}
Ribbon ribbon = null;
UIElement topLevelElement = null;
FindControls(this.PlacementTarget, ref ribbon, ref topLevelElement);
// Exclude QAT items
var notQuickAccessItem = !IsQuickAccessItem(this.PlacementTarget);
var notContextMenuChild = !IsContextMenuChild(this.PlacementTarget);
var rightToLeftOffset = this.FlowDirection == FlowDirection.RightToLeft
? -popupSize.Width
: 0;
var decoratorChild = GetDecoratorChild(topLevelElement);
if (notQuickAccessItem
&& this.IsRibbonAligned
&& ribbon != null)
{
var belowY = ribbon.TranslatePoint(new Point(0, ribbon.ActualHeight), this.PlacementTarget).Y;
var aboveY = ribbon.TranslatePoint(new Point(0, 0), this.PlacementTarget).Y - popupSize.Height;
var below = new CustomPopupPlacement(new Point(rightToLeftOffset, belowY + 1), PopupPrimaryAxis.Horizontal);
var above = new CustomPopupPlacement(new Point(rightToLeftOffset, aboveY - 1), PopupPrimaryAxis.Horizontal);
return new[] { below, above };
}
if (notQuickAccessItem
&& this.IsRibbonAligned
&& notContextMenuChild
&& topLevelElement is Window == false
&& decoratorChild != null)
{
// Placed on Popup?
var belowY = decoratorChild.TranslatePoint(new Point(0, ((FrameworkElement)decoratorChild).ActualHeight), this.PlacementTarget).Y;
var aboveY = decoratorChild.TranslatePoint(new Point(0, 0), this.PlacementTarget).Y - popupSize.Height;
var below = new CustomPopupPlacement(new Point(rightToLeftOffset, belowY + 1), PopupPrimaryAxis.Horizontal);
var above = new CustomPopupPlacement(new Point(rightToLeftOffset, aboveY - 1), PopupPrimaryAxis.Horizontal);
return new[] { below, above };
}
return new[]
{
new CustomPopupPlacement(new Point(rightToLeftOffset, this.PlacementTarget.RenderSize.Height + 1), PopupPrimaryAxis.Horizontal),
new CustomPopupPlacement(new Point(rightToLeftOffset, -popupSize.Height - 1), PopupPrimaryAxis.Horizontal)
};
}
private static bool IsContextMenuChild(UIElement element)
{
do
{
var parent = VisualTreeHelper.GetParent(element) as UIElement;
//if (parent is ContextMenuBar) return true;
element = parent;
}
while (element != null);
return false;
}
private static bool IsQuickAccessItem(UIElement element)
{
do
{
var parent = VisualTreeHelper.GetParent(element) as UIElement;
if (parent is QuickAccessToolBar)
{
return true;
}
element = parent;
}
while (element != null);
return false;
}
private static UIElement GetDecoratorChild(UIElement popupRoot)
{
switch (popupRoot)
{
case null:
return null;
case AdornerDecorator decorator:
return decorator.Child;
}
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(popupRoot); i++)
{
var element = GetDecoratorChild(VisualTreeHelper.GetChild(popupRoot, i) as UIElement);
if (element != null)
{
return element;
}
}
return null;
}
private static void FindControls(UIElement obj, ref Ribbon ribbon, ref UIElement topLevelElement)
{
switch (obj)
{
case null:
return;
case Ribbon objRibbon:
ribbon = objRibbon;
break;
}
var parentVisual = VisualTreeHelper.GetParent(obj) as UIElement;
if (parentVisual is null)
{
topLevelElement = obj;
}
else
{
FindControls(parentVisual, ref ribbon, ref topLevelElement);
}
}
#endregion
#region Title Property
/// <summary>
/// Gets or sets title of the screen tip
/// </summary>
[System.ComponentModel.DisplayName("Title")]
[System.ComponentModel.Category("Screen Tip")]
[System.ComponentModel.Description("Title of the screen tip")]
public string Title
{
get { return (string)this.GetValue(TitleProperty); }
set { this.SetValue(TitleProperty, value); }
}
/// <summary>Identifies the <see cref="Title"/> dependency property.</summary>
public static readonly DependencyProperty TitleProperty =
#pragma warning disable WPF0010 // Default value type must match registered type.
DependencyProperty.Register(nameof(Title), typeof(string), typeof(ScreenTip), new PropertyMetadata(StringBoxes.Empty));
#pragma warning restore WPF0010 // Default value type must match registered type.
#endregion
#region Text Property
/// <summary>
/// Gets or sets text of the screen tip
/// </summary>
[System.ComponentModel.DisplayName("Text")]
[System.ComponentModel.Category("Screen Tip")]
[System.ComponentModel.Description("Main text of the screen tip")]
public string Text
{
get { return (string)this.GetValue(TextProperty); }
set { this.SetValue(TextProperty, value); }
}
/// <summary>Identifies the <see cref="Text"/> dependency property.</summary>
public static readonly DependencyProperty TextProperty =
#pragma warning disable WPF0010 // Default value type must match registered type.
DependencyProperty.Register(nameof(Text), typeof(string), typeof(ScreenTip), new PropertyMetadata(StringBoxes.Empty));
#pragma warning restore WPF0010 // Default value type must match registered type.
#endregion
#region DisableReason Property
/// <summary>
/// Gets or sets disable reason of the associated screen tip's control
/// </summary>
[System.ComponentModel.DisplayName("Disable Reason")]
[System.ComponentModel.Category("Screen Tip")]
[System.ComponentModel.Description("Describe here what would cause disable of the control")]
public string DisableReason
{
get { return (string)this.GetValue(DisableReasonProperty); }
set { this.SetValue(DisableReasonProperty, value); }
}
/// <summary>Identifies the <see cref="DisableReason"/> dependency property.</summary>
public static readonly DependencyProperty DisableReasonProperty =
#pragma warning disable WPF0010 // Default value type must match registered type.
DependencyProperty.Register(nameof(DisableReason), typeof(string), typeof(ScreenTip), new PropertyMetadata(StringBoxes.Empty));
#pragma warning restore WPF0010 // Default value type must match registered type.
#endregion
#region HelpTopic Property
/// <summary>
/// Gets or sets help topic of the ScreenTip
/// </summary>
[System.ComponentModel.DisplayName("Help Topic")]
[System.ComponentModel.Category("Screen Tip")]
[System.ComponentModel.Description("Help topic (it will be used to execute help)")]
public object HelpTopic
{
get { return this.GetValue(HelpTopicProperty); }
set { this.SetValue(HelpTopicProperty, value); }
}
/// <summary>Identifies the <see cref="HelpTopic"/> dependency property.</summary>
public static readonly DependencyProperty HelpTopicProperty =
DependencyProperty.Register(nameof(HelpTopic), typeof(object), typeof(ScreenTip), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region Image Property
/// <summary>
/// Gets or sets image of the screen tip
/// </summary>
[System.ComponentModel.DisplayName("Image")]
[System.ComponentModel.Category("Screen Tip")]
[System.ComponentModel.Description("Image of the screen tip")]
public ImageSource Image
{
get { return (ImageSource)this.GetValue(ImageProperty); }
set { this.SetValue(ImageProperty, value); }
}
/// <summary>Identifies the <see cref="Image"/> dependency property.</summary>
public static readonly DependencyProperty ImageProperty =
DependencyProperty.Register(nameof(Image), typeof(ImageSource), typeof(ScreenTip), new PropertyMetadata());
#endregion
#region ShowHelp Property
/// <summary>
/// Shows or hides the Help Label
/// </summary>
[System.ComponentModel.DisplayName("HelpLabelVisibility")]
[System.ComponentModel.Category("Screen Tip")]
[System.ComponentModel.Description("Sets the visibility of the F1 Help Label")]
public Visibility HelpLabelVisibility
{
get { return (Visibility)this.GetValue(HelpLabelVisibilityProperty); }
set { this.SetValue(HelpLabelVisibilityProperty, value); }
}
/// <summary>Identifies the <see cref="HelpLabelVisibility"/> dependency property.</summary>
public static readonly DependencyProperty HelpLabelVisibilityProperty =
DependencyProperty.Register(nameof(HelpLabelVisibility), typeof(Visibility), typeof(ScreenTip), new PropertyMetadata(Visibility.Visible));
#endregion
#region Help Invocation
/// <summary>
/// Occurs when user press F1 on ScreenTip with HelpTopic filled
/// </summary>
public static event EventHandler<ScreenTipHelpEventArgs> HelpPressed;
#endregion
#region IsRibbonAligned
/// <summary>
/// Gets or set whether ScreenTip should positioned below Ribbon
/// </summary>
public bool IsRibbonAligned
{
get { return (bool)this.GetValue(IsRibbonAlignedProperty); }
set { this.SetValue(IsRibbonAlignedProperty, value); }
}
/// <summary>Identifies the <see cref="IsRibbonAligned"/> dependency property.</summary>
public static readonly DependencyProperty IsRibbonAlignedProperty =
DependencyProperty.Register(nameof(IsRibbonAligned), typeof(bool), typeof(ScreenTip),
new PropertyMetadata(BooleanBoxes.TrueBox));
#endregion
#region F1 Help Handling
// Currently focused element
private IInputElement focusedElement;
private void OnToolTipClosed(object sender, RoutedEventArgs e)
{
if (this.focusedElement is null)
{
return;
}
this.focusedElement.PreviewKeyDown -= this.OnFocusedElementPreviewKeyDown;
this.focusedElement = null;
}
private void OnToolTipOpened(object sender, RoutedEventArgs e)
{
if (this.HelpTopic is null)
{
return;
}
this.focusedElement = Keyboard.FocusedElement;
if (this.focusedElement != null)
{
this.focusedElement.PreviewKeyDown += this.OnFocusedElementPreviewKeyDown;
}
}
private void OnFocusedElementPreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.F1)
{
return;
}
e.Handled = true;
HelpPressed?.Invoke(null, new ScreenTipHelpEventArgs(this.HelpTopic));
}
#endregion
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() => new Fluent.Automation.Peers.RibbonScreenTipAutomationPeer(this);
/// <inheritdoc />
void ILogicalChildSupport.AddLogicalChild(object child)
{
this.AddLogicalChild(child);
}
/// <inheritdoc />
void ILogicalChildSupport.RemoveLogicalChild(object child)
{
this.RemoveLogicalChild(child);
}
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.HelpTopic != null)
{
yield return this.HelpTopic;
}
}
}
}
/// <summary>
/// Event args for HelpPressed event handler
/// </summary>
public class ScreenTipHelpEventArgs : EventArgs
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="helpTopic">Help topic</param>
public ScreenTipHelpEventArgs(object helpTopic)
{
this.HelpTopic = helpTopic;
}
/// <summary>
/// Gets help topic associated with screen tip
/// </summary>
public object HelpTopic { get; }
}
}

View File

@@ -0,0 +1,57 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Windows;
using System.Windows.Controls;
using Fluent.Internal;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents separator to use in the TabControl
/// </summary>
public class SeparatorTabItem : TabItem
{
#region Constructors
/// <summary>
/// Static constructor
/// </summary>
static SeparatorTabItem()
{
var type = typeof(SeparatorTabItem);
DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(type));
IsEnabledProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, null, CoerceIsEnabledAndTabStop));
IsTabStopProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, null, CoerceIsEnabledAndTabStop));
IsSelectedProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, OnIsSelectedChanged));
}
private static void OnIsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue == false)
{
return;
}
var separatorTabItem = (SeparatorTabItem)d;
var tabControl = UIHelper.GetParent<TabControl>(separatorTabItem);
if (tabControl is null
|| tabControl.Items.Count <= 1)
{
return;
}
tabControl.SelectedIndex = tabControl.SelectedIndex == tabControl.Items.Count - 1
? tabControl.SelectedIndex - 1
: tabControl.SelectedIndex + 1;
}
private static object CoerceIsEnabledAndTabStop(DependencyObject d, object basevalue)
{
return false;
}
#endregion
}
}

View File

@@ -0,0 +1,566 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Threading;
using Fluent.Converters;
using Fluent.Extensions;
using Fluent.Internal;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents spinner control
/// </summary>
[ContentProperty(nameof(Value))]
[TemplatePart(Name = "PART_TextBox", Type = typeof(System.Windows.Controls.TextBox))]
[TemplatePart(Name = "PART_ButtonUp", Type = typeof(RepeatButton))]
[TemplatePart(Name = "PART_ButtonDown", Type = typeof(RepeatButton))]
public class Spinner : RibbonControl
{
/// <summary>
/// Occurs when value has been changed
/// </summary>
public event RoutedPropertyChangedEventHandler<double> ValueChanged;
// Parts of the control (must be in control template)
private System.Windows.Controls.TextBox textBox;
private RepeatButton buttonUp;
private RepeatButton buttonDown;
#region Properties
#region Value
/// <summary>
/// Gets or sets current value
/// </summary>
public double Value
{
get { return (double)this.GetValue(ValueProperty); }
set { this.SetValue(ValueProperty, value); }
}
/// <summary>
/// Using a DependencyProperty as the backing store for Value.
/// This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty ValueProperty;
private static object CoerceValue(DependencyObject d, object basevalue)
{
var spinner = (Spinner)d;
var value = (double)basevalue;
value = GetLimitedValue(spinner, value);
return value;
}
private static double GetLimitedValue(Spinner spinner, double value)
{
value = Math.Max(spinner.Minimum, value);
value = Math.Min(spinner.Maximum, value);
return value;
}
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var spinner = (Spinner)d;
spinner.ValueToTextBoxText();
spinner.ValueChanged?.Invoke(spinner, new RoutedPropertyChangedEventArgs<double>((double)e.OldValue, (double)e.NewValue));
}
private void ValueToTextBoxText()
{
if (this.textBox is null)
{
return;
}
var newText = (string)this.TextToValueConverter.ConvertBack(this.Value, typeof(string), this.Format, CultureInfo.CurrentCulture);
this.textBox.Text = newText;
this.Text = newText;
}
#endregion
#region Text
/// <summary>
/// Gets current text from the spinner
/// </summary>
public string Text
{
get { return (string)this.GetValue(TextProperty); }
private set { this.SetValue(TextPropertyKey, value); }
}
// ReSharper disable once InconsistentNaming
private static readonly DependencyPropertyKey TextPropertyKey = DependencyProperty.RegisterReadOnly(nameof(Text), typeof(string), typeof(Spinner), new PropertyMetadata());
/// <summary>Identifies the <see cref="Text"/> dependency property.</summary>
public static readonly DependencyProperty TextProperty = TextPropertyKey.DependencyProperty;
#endregion
#region Increment
/// <summary>
/// Gets or sets a value added or subtracted from the value property
/// </summary>
public double Increment
{
get { return (double)this.GetValue(IncrementProperty); }
set { this.SetValue(IncrementProperty, value); }
}
/// <summary>Identifies the <see cref="Increment"/> dependency property.</summary>
public static readonly DependencyProperty IncrementProperty =
DependencyProperty.Register(nameof(Increment), typeof(double), typeof(Spinner), new PropertyMetadata(DoubleBoxes.One));
#endregion
#region Minimum
/// <summary>
/// Gets or sets minimun value
/// </summary>
public double Minimum
{
get { return (double)this.GetValue(MinimumProperty); }
set { this.SetValue(MinimumProperty, value); }
}
/// <summary>
/// Using a DependencyProperty as the backing store for Minimum.
/// This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty MinimumProperty;
private static object CoerceMinimum(DependencyObject d, object basevalue)
{
var spinner = (Spinner)d;
var value = (double)basevalue;
if (spinner.Maximum < value)
{
return spinner.Maximum;
}
return value;
}
private static void OnMinimumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var spinner = (Spinner)d;
var value = (double)CoerceValue(d, spinner.Value);
if (DoubleUtil.AreClose(value, spinner.Value) == false)
{
spinner.Value = value;
}
}
#endregion
#region Maximum
/// <summary>
/// Gets or sets maximum value
/// </summary>
public double Maximum
{
get { return (double)this.GetValue(MaximumProperty); }
set { this.SetValue(MaximumProperty, value); }
}
/// <summary>
/// Using a DependencyProperty as the backing store for Maximum.
/// This enables animation, styling, binding, etc...
/// </summary>
public static readonly DependencyProperty MaximumProperty;
private static object CoerceMaximum(DependencyObject d, object basevalue)
{
var spinner = (Spinner)d;
var value = (double)basevalue;
if (spinner.Minimum > value)
{
return spinner.Minimum;
}
return value;
}
private static void OnMaximumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var spinner = (Spinner)d;
var value = (double)CoerceValue(d, spinner.Value);
if (DoubleUtil.AreClose(value, spinner.Value) == false)
{
spinner.Value = value;
}
}
#endregion
#region Format
/// <summary>
/// Gets or sets string format of value
/// </summary>
public string Format
{
get { return (string)this.GetValue(FormatProperty); }
set { this.SetValue(FormatProperty, value); }
}
/// <summary>Identifies the <see cref="Format"/> dependency property.</summary>
public static readonly DependencyProperty FormatProperty =
DependencyProperty.Register(nameof(Format), typeof(string), typeof(Spinner), new PropertyMetadata("F1", OnFormatChanged));
private static void OnFormatChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var spinner = (Spinner)d;
spinner.ValueToTextBoxText();
}
#endregion
#region Delay
/// <summary>
/// Gets or sets the amount of time, in milliseconds,
/// the Spinner waits while it is pressed before it starts repeating.
/// The value must be non-negative. This is a dependency property.
/// </summary>
public int Delay
{
get { return (int)this.GetValue(DelayProperty); }
set { this.SetValue(DelayProperty, value); }
}
/// <summary>Identifies the <see cref="Delay"/> dependency property.</summary>
public static readonly DependencyProperty DelayProperty =
DependencyProperty.Register(nameof(Delay), typeof(int), typeof(Spinner),
new PropertyMetadata(400));
#endregion
#region Interval
/// <summary>
/// Gets or sets the amount of time, in milliseconds,
/// between repeats once repeating starts. The value must be non-negative.
/// This is a dependency property.
/// </summary>
public int Interval
{
get { return (int)this.GetValue(IntervalProperty); }
set { this.SetValue(IntervalProperty, value); }
}
/// <summary>Identifies the <see cref="Interval"/> dependency property.</summary>
public static readonly DependencyProperty IntervalProperty =
DependencyProperty.Register(nameof(Interval), typeof(int), typeof(Spinner), new PropertyMetadata(80));
#endregion
#region InputWidth
/// <summary>
/// Gets or sets width of the value input part of spinner
/// </summary>
public double InputWidth
{
get { return (double)this.GetValue(InputWidthProperty); }
set { this.SetValue(InputWidthProperty, value); }
}
/// <summary>Identifies the <see cref="InputWidth"/> dependency property.</summary>
public static readonly DependencyProperty InputWidthProperty =
DependencyProperty.Register(nameof(InputWidth), typeof(double), typeof(Spinner), new PropertyMetadata(DoubleBoxes.NaN));
#endregion
#region TextToValueConverter
/// <summary>
/// Gets or sets a converter which is used to convert from text to double and from double to text.
/// </summary>
public IValueConverter TextToValueConverter
{
get { return (IValueConverter)this.GetValue(TextToValueConverterProperty); }
set { this.SetValue(TextToValueConverterProperty, value); }
}
/// <summary>Identifies the <see cref="TextToValueConverter"/> dependency property.</summary>
public static readonly DependencyProperty TextToValueConverterProperty =
#pragma warning disable WPF0016 // Default value is shared reference type.
DependencyProperty.Register(nameof(TextToValueConverter), typeof(IValueConverter), typeof(Spinner), new PropertyMetadata(SpinnerTextToValueConverter.DefaultInstance));
#pragma warning restore WPF0016 // Default value is shared reference type.
#endregion TextToValueConverter
/// <summary>
/// Defines whether all text should be select as soon as this control gets focus.
/// </summary>
public bool SelectAllTextOnFocus
{
get { return (bool)this.GetValue(SelectAllTextOnFocusProperty); }
set { this.SetValue(SelectAllTextOnFocusProperty, value); }
}
/// <summary>Identifies the <see cref="SelectAllTextOnFocus"/> dependency property.</summary>
public static readonly DependencyProperty SelectAllTextOnFocusProperty =
DependencyProperty.Register(nameof(SelectAllTextOnFocus), typeof(bool), typeof(Spinner), new PropertyMetadata(BooleanBoxes.FalseBox));
#endregion
#region Constructors
/// <summary>
/// Static constructor
/// </summary>
static Spinner()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Spinner), new FrameworkPropertyMetadata(typeof(Spinner)));
MaximumProperty = DependencyProperty.Register(nameof(Maximum), typeof(double), typeof(Spinner), new PropertyMetadata(DoubleBoxes.MaxValue, OnMaximumChanged, CoerceMaximum));
MinimumProperty = DependencyProperty.Register(nameof(Minimum), typeof(double), typeof(Spinner), new PropertyMetadata(DoubleBoxes.Zero, OnMinimumChanged, CoerceMinimum));
ValueProperty = DependencyProperty.Register(nameof(Value), typeof(double), typeof(Spinner), new FrameworkPropertyMetadata(DoubleBoxes.Zero, OnValueChanged, CoerceValue) { BindsTwoWayByDefault = true });
KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(Spinner), new FrameworkPropertyMetadata(KeyboardNavigationMode.Once));
}
#endregion
#region Public Methods
/// <summary>
/// Select all text in the Spinner.
/// </summary>
public void SelectAll()
{
this.textBox.SelectAll();
}
#endregion
#region Overrides
/// <summary>
/// When overridden in a derived class, is invoked whenever application code or internal processes call <see cref="M:System.Windows.FrameworkElement.ApplyTemplate"/>.
/// </summary>
public override void OnApplyTemplate()
{
if (this.buttonUp != null)
{
this.buttonUp.Click -= this.OnButtonUpClick;
BindingOperations.ClearAllBindings(this.buttonUp);
}
if (this.buttonDown != null)
{
this.buttonDown.Click -= this.OnButtonDownClick;
BindingOperations.ClearAllBindings(this.buttonDown);
}
if (this.textBox != null)
{
this.textBox.LostKeyboardFocus -= this.OnTextBoxLostKeyboardFocus;
this.textBox.PreviewKeyDown -= this.OnTextBoxPreviewKeyDown;
}
// Get template childs
this.textBox = this.GetTemplateChild("PART_TextBox") as System.Windows.Controls.TextBox;
this.buttonUp = this.GetTemplateChild("PART_ButtonUp") as RepeatButton;
this.buttonDown = this.GetTemplateChild("PART_ButtonDown") as RepeatButton;
// Check template
if (this.IsTemplateValid() == false)
{
Debug.WriteLine("Template for Spinner control is invalid");
return;
}
// Bindings
Bind(this, this.buttonUp, nameof(this.Delay), RepeatButton.DelayProperty, BindingMode.OneWay);
Bind(this, this.buttonDown, nameof(this.Delay), RepeatButton.DelayProperty, BindingMode.OneWay);
Bind(this, this.buttonUp, nameof(this.Interval), RepeatButton.IntervalProperty, BindingMode.OneWay);
Bind(this, this.buttonDown, nameof(this.Interval), RepeatButton.IntervalProperty, BindingMode.OneWay);
// Events subscribing
this.buttonUp.Click += this.OnButtonUpClick;
this.buttonDown.Click += this.OnButtonDownClick;
this.textBox.GotFocus += this.HandleTextBoxGotFocus;
this.textBox.LostKeyboardFocus += this.OnTextBoxLostKeyboardFocus;
this.textBox.PreviewKeyDown += this.OnTextBoxPreviewKeyDown;
this.ValueToTextBoxText();
}
private bool IsTemplateValid()
{
return this.textBox != null
&& this.buttonUp != null
&& this.buttonDown != null;
}
#endregion
#region Event Handling
/// <inheritdoc />
public override KeyTipPressedResult OnKeyTipPressed()
{
if (this.textBox is null)
{
return KeyTipPressedResult.Empty;
}
this.textBox.SelectAll();
this.textBox.Focus();
return new KeyTipPressedResult(true, false);
}
private void HandleTextBoxGotFocus(object sender, RoutedEventArgs e)
{
if (this.SelectAllTextOnFocus)
{
// Async because setting the carret happens after focus.
this.RunInDispatcherAsync(() =>
{
this.textBox.SelectAll();
}, DispatcherPriority.Background);
}
}
/// <summary>
/// Invoked when an unhandled System.Windows.Input.Keyboard.KeyUp attached event reaches
/// an element in its route that is derived from this class. Implement this method to add class handling for this event.
/// </summary>
/// <param name="e">The System.Windows.Input.KeyEventArgs that contains the event data.</param>
protected override void OnKeyUp(KeyEventArgs e)
{
// Avoid Click invocation (from RibbonControl)
if (e.Key == Key.Enter
|| e.Key == Key.Space)
{
return;
}
base.OnKeyUp(e);
}
private void OnButtonUpClick(object sender, RoutedEventArgs e)
{
this.Value = GetLimitedValue(this, this.Value + this.Increment);
}
private void OnButtonDownClick(object sender, RoutedEventArgs e)
{
this.Value = GetLimitedValue(this, this.Value - this.Increment);
}
private void OnTextBoxLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
this.TextBoxTextToValue();
}
private void OnTextBoxPreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
this.TextBoxTextToValue();
}
if (e.Key == Key.Escape)
{
this.ValueToTextBoxText();
}
if (e.Key == Key.Enter
|| e.Key == Key.Escape)
{
// Move Focus
this.textBox.Focusable = false;
this.Focus();
this.textBox.Focusable = true;
e.Handled = true;
}
if (e.Key == Key.Up)
{
this.buttonUp.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent));
}
if (e.Key == Key.Down)
{
this.buttonDown.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent));
}
}
private void TextBoxTextToValue()
{
var converterParam = new Tuple<string, double>(this.Format, this.Value);
var newValue = (double)this.TextToValueConverter.Convert(this.textBox.Text, typeof(double), converterParam, CultureInfo.CurrentCulture);
this.Value = GetLimitedValue(this, newValue);
this.ValueToTextBoxText();
}
#endregion
#region Quick Access Item Creating
/// <summary>
/// Gets control which represents shortcut item.
/// This item MUST be syncronized with the original
/// and send command to original one control.
/// </summary>
/// <returns>Control which represents shortcut item</returns>
public override FrameworkElement CreateQuickAccessItem()
{
var spinner = new Spinner();
this.BindQuickAccessItem(spinner);
return spinner;
}
/// <summary>
/// This method must be overriden to bind properties to use in quick access creating
/// </summary>
/// <param name="element">Toolbar item</param>
protected virtual void BindQuickAccessItem(FrameworkElement element)
{
var spinner = (Spinner)element;
BindQuickAccessItem(this, element);
spinner.Width = this.Width;
spinner.InputWidth = this.InputWidth;
Bind(this, spinner, nameof(this.Value), ValueProperty, BindingMode.TwoWay);
Bind(this, spinner, nameof(this.Increment), IncrementProperty, BindingMode.OneWay);
Bind(this, spinner, nameof(this.Minimum), MinimumProperty, BindingMode.OneWay);
Bind(this, spinner, nameof(this.Maximum), MaximumProperty, BindingMode.OneWay);
Bind(this, spinner, nameof(this.Format), FormatProperty, BindingMode.OneWay);
Bind(this, spinner, nameof(this.Delay), DelayProperty, BindingMode.OneWay);
Bind(this, spinner, nameof(this.Interval), IntervalProperty, BindingMode.OneWay);
BindQuickAccessItem(this, element);
}
#endregion
}
}

View File

@@ -0,0 +1,590 @@
#nullable enable
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using Fluent.Extensibility;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents button control that allows
/// you to add menu and handle clicks
/// </summary>
[TemplatePart(Name = "PART_Button", Type = typeof(ButtonBase))]
public class SplitButton : DropDownButton, IToggleButton, ICommandSource, IKeyTipInformationProvider
{
#region Fields
#pragma warning disable IDE0032
// Inner button
private ToggleButton? button;
#pragma warning restore IDE0032
private SplitButton? quickAccessButton;
#endregion
#region Properties
// ReSharper disable once ConvertToAutoPropertyWithPrivateSetter
internal ToggleButton? Button => this.button;
#region Command
/// <inheritdoc />
[Category("Action")]
[Localizability(LocalizationCategory.NeverLocalize)]
[Bindable(true)]
public ICommand Command
{
get
{
return (ICommand)this.GetValue(CommandProperty);
}
set
{
this.SetValue(CommandProperty, value);
}
}
/// <inheritdoc />
[Bindable(true)]
[Localizability(LocalizationCategory.NeverLocalize)]
[Category("Action")]
public object CommandParameter
{
get
{
return this.GetValue(CommandParameterProperty);
}
set
{
this.SetValue(CommandParameterProperty, value);
}
}
/// <inheritdoc />
[Bindable(true)]
[Category("Action")]
public IInputElement CommandTarget
{
get
{
return (IInputElement)this.GetValue(CommandTargetProperty);
}
set
{
this.SetValue(CommandTargetProperty, value);
}
}
/// <summary>Identifies the <see cref="CommandParameter"/> dependency property.</summary>
public static readonly DependencyProperty CommandParameterProperty = ButtonBase.CommandParameterProperty.AddOwner(typeof(SplitButton), new FrameworkPropertyMetadata());
/// <summary>Identifies the <see cref="Command"/> dependency property.</summary>
public static readonly DependencyProperty CommandProperty = ButtonBase.CommandProperty.AddOwner(typeof(SplitButton), new FrameworkPropertyMetadata());
/// <summary>Identifies the <see cref="CommandTarget"/> dependency property.</summary>
public static readonly DependencyProperty CommandTargetProperty = ButtonBase.CommandTargetProperty.AddOwner(typeof(SplitButton), new FrameworkPropertyMetadata());
#endregion
#region GroupName
/// <inheritdoc />
public string GroupName
{
get { return (string)this.GetValue(GroupNameProperty); }
set { this.SetValue(GroupNameProperty, value); }
}
/// <summary>Identifies the <see cref="GroupName"/> dependency property.</summary>
public static readonly DependencyProperty GroupNameProperty = DependencyProperty.Register(nameof(GroupName), typeof(string), typeof(SplitButton));
#endregion
#region IsChecked
/// <inheritdoc />
public bool? IsChecked
{
get { return (bool?)this.GetValue(IsCheckedProperty); }
set { this.SetValue(IsCheckedProperty, value); }
}
/// <summary>Identifies the <see cref="IsChecked"/> dependency property.</summary>
public static readonly DependencyProperty IsCheckedProperty = System.Windows.Controls.Primitives.ToggleButton.IsCheckedProperty.AddOwner(typeof(SplitButton), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, OnIsCheckedChanged, CoerceIsChecked));
private static void OnIsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var button = (SplitButton)d;
if (button.IsCheckable)
{
var nullable = (bool?)e.NewValue;
if (nullable is null)
{
button.RaiseEvent(new RoutedEventArgs(IndeterminateEvent, button));
}
else if (nullable.Value)
{
button.RaiseEvent(new RoutedEventArgs(CheckedEvent, button));
}
else
{
button.RaiseEvent(new RoutedEventArgs(UncheckedEvent, button));
}
}
}
private static object CoerceIsChecked(DependencyObject d, object basevalue)
{
var button = (SplitButton)d;
if (button.IsCheckable == false)
{
return BooleanBoxes.FalseBox;
}
return basevalue;
}
#endregion
#region IsCheckable
/// <summary>
/// Gets or sets a value indicating whether SplitButton can be checked
/// </summary>
public bool IsCheckable
{
get { return (bool)this.GetValue(IsCheckableProperty); }
set { this.SetValue(IsCheckableProperty, value); }
}
/// <summary>Identifies the <see cref="IsCheckable"/> dependency property.</summary>
public static readonly DependencyProperty IsCheckableProperty =
DependencyProperty.Register(nameof(IsCheckable), typeof(bool), typeof(SplitButton), new PropertyMetadata(BooleanBoxes.FalseBox));
#endregion
#region DropDownToolTip
/// <summary>
/// Gets or sets tooltip of dropdown part of split button
/// </summary>
public object DropDownToolTip
{
get { return this.GetValue(DropDownToolTipProperty); }
set { this.SetValue(DropDownToolTipProperty, value); }
}
/// <summary>Identifies the <see cref="DropDownToolTip"/> dependency property.</summary>
public static readonly DependencyProperty DropDownToolTipProperty =
DependencyProperty.Register(nameof(DropDownToolTip), typeof(object), typeof(SplitButton), new PropertyMetadata());
#endregion
#region IsButtonEnabled
/// <summary>
/// Gets or sets a value indicating whether the button part of split button is enabled.
/// If you want to disable the button part and the DropDown please use <see cref="UIElement.IsEnabled"/>.
/// </summary>
public bool IsButtonEnabled
{
get { return (bool)this.GetValue(IsButtonEnabledProperty); }
set { this.SetValue(IsButtonEnabledProperty, value); }
}
/// <summary>Identifies the <see cref="IsButtonEnabled"/> dependency property.</summary>
public static readonly DependencyProperty IsButtonEnabledProperty =
DependencyProperty.Register(nameof(IsButtonEnabled), typeof(bool), typeof(SplitButton), new PropertyMetadata(BooleanBoxes.TrueBox));
#endregion
#region IsDefinitive
/// <summary>
/// Gets or sets whether ribbon control click must close backstage
/// </summary>
public bool IsDefinitive
{
get { return (bool)this.GetValue(IsDefinitiveProperty); }
set { this.SetValue(IsDefinitiveProperty, value); }
}
/// <summary>Identifies the <see cref="IsDefinitive"/> dependency property.</summary>
public static readonly DependencyProperty IsDefinitiveProperty =
DependencyProperty.Register(nameof(IsDefinitive), typeof(bool), typeof(SplitButton), new PropertyMetadata(BooleanBoxes.TrueBox));
#endregion
#region KeyTipPostfix
/// <summary>Identifies the <see cref="PrimaryActionKeyTipPostfix"/> dependency property.</summary>
public static readonly DependencyProperty PrimaryActionKeyTipPostfixProperty = DependencyProperty.Register(nameof(PrimaryActionKeyTipPostfix), typeof(string), typeof(SplitButton), new PropertyMetadata("A"));
/// <summary>
/// Gets or sets the postfix for the primary keytip action.
/// </summary>
public string PrimaryActionKeyTipPostfix
{
get { return (string)this.GetValue(PrimaryActionKeyTipPostfixProperty); }
set { this.SetValue(PrimaryActionKeyTipPostfixProperty, value); }
}
/// <summary>Identifies the <see cref="SecondaryActionKeyTipPostfix"/> dependency property.</summary>
public static readonly DependencyProperty SecondaryActionKeyTipPostfixProperty = DependencyProperty.Register(nameof(SecondaryActionKeyTipPostfix), typeof(string), typeof(SplitButton), new PropertyMetadata("B"));
/// <summary>
/// Gets or sets the postfix for the secondary keytip action.
/// </summary>
public string SecondaryActionKeyTipPostfix
{
get { return (string)this.GetValue(SecondaryActionKeyTipPostfixProperty); }
set { this.SetValue(SecondaryActionKeyTipPostfixProperty, value); }
}
#endregion KeyTipPostfix
/// <summary>Identifies the <see cref="SecondaryKeyTip"/> dependency property.</summary>
public static readonly DependencyProperty SecondaryKeyTipProperty = DependencyProperty.Register(nameof(SecondaryKeyTip), typeof(string), typeof(SplitButton), new PropertyMetadata(StringBoxes.Empty));
/// <summary>
/// Gets or sets the keytip for the secondary action.
/// </summary>
public string SecondaryKeyTip
{
get { return (string)this.GetValue(SecondaryKeyTipProperty); }
set { this.SetValue(SecondaryKeyTipProperty, value); }
}
#endregion
#region Events
/// <summary>
/// Occurs when user clicks
/// </summary>
public static readonly RoutedEvent ClickEvent = ButtonBase.ClickEvent.AddOwner(typeof(SplitButton));
/// <summary>
/// Occurs when user clicks
/// </summary>
public event RoutedEventHandler Click
{
add
{
this.AddHandler(ClickEvent, value);
}
remove
{
this.RemoveHandler(ClickEvent, value);
}
}
/// <summary>
/// Occurs when button is checked
/// </summary>
public static readonly RoutedEvent CheckedEvent = System.Windows.Controls.Primitives.ToggleButton.CheckedEvent.AddOwner(typeof(SplitButton));
/// <summary>
/// Occurs when button is checked
/// </summary>
public event RoutedEventHandler Checked
{
add
{
this.AddHandler(CheckedEvent, value);
}
remove
{
this.RemoveHandler(CheckedEvent, value);
}
}
/// <summary>
/// Occurs when button is unchecked
/// </summary>
public static readonly RoutedEvent UncheckedEvent = System.Windows.Controls.Primitives.ToggleButton.UncheckedEvent.AddOwner(typeof(SplitButton));
/// <summary>
/// Occurs when button is unchecked
/// </summary>
public event RoutedEventHandler Unchecked
{
add
{
this.AddHandler(UncheckedEvent, value);
}
remove
{
this.RemoveHandler(UncheckedEvent, value);
}
}
/// <summary>
/// Occurs when button is unchecked
/// </summary>
public static readonly RoutedEvent IndeterminateEvent = System.Windows.Controls.Primitives.ToggleButton.IndeterminateEvent.AddOwner(typeof(SplitButton));
/// <summary>
/// Occurs when button is unchecked
/// </summary>
public event RoutedEventHandler Indeterminate
{
add
{
this.AddHandler(IndeterminateEvent, value);
}
remove
{
this.RemoveHandler(IndeterminateEvent, value);
}
}
#endregion
#region Constructors
static SplitButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SplitButton), new FrameworkPropertyMetadata(typeof(SplitButton)));
FocusVisualStyleProperty.OverrideMetadata(typeof(SplitButton), new FrameworkPropertyMetadata());
}
/// <summary>
/// Default constructor
/// </summary>
public SplitButton()
{
ContextMenuService.Coerce(this);
this.Click += this.OnClick;
this.Loaded += this.OnLoaded;
this.Unloaded += this.OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
this.SubscribeEvents();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
this.UnSubscribeEvents();
}
private void SubscribeEvents()
{
// Always unsubscribe events to ensure we don't subscribe twice
this.UnSubscribeEvents();
if (this.button != null)
{
this.button.Click += this.OnButtonClick;
}
}
private void UnSubscribeEvents()
{
if (this.button != null)
{
this.button.Click -= this.OnButtonClick;
}
}
private void OnClick(object sender, RoutedEventArgs e)
{
if (ReferenceEquals(e.OriginalSource, this) == false
&& ReferenceEquals(e.OriginalSource, this.quickAccessButton) == false)
{
e.Handled = true;
}
}
#endregion
#region Overrides
/// <inheritdoc />
public override void OnApplyTemplate()
{
this.UnSubscribeEvents();
this.button = this.GetTemplateChild("PART_Button") as ToggleButton;
base.OnApplyTemplate();
this.SubscribeEvents();
}
/// <inheritdoc />
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
{
if (!PopupService.IsMousePhysicallyOver(this.button))
{
base.OnPreviewMouseLeftButtonDown(e);
}
else
{
this.IsDropDownOpen = false;
}
}
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() => new Fluent.Automation.Peers.RibbonSplitButtonAutomationPeer(this);
#region Overrides of DropDownButton
/// <inheritdoc />
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Key.Enter)
{
this.button?.InvokeClick();
}
}
#endregion
internal void AutomationButtonClick()
{
this.button?.InvokeClick();
}
private void OnButtonClick(object sender, RoutedEventArgs e)
{
e.Handled = true;
this.RaiseEvent(new RoutedEventArgs(ClickEvent, this));
}
#endregion
#region Quick Access Item Creating
/// <inheritdoc />
public override FrameworkElement CreateQuickAccessItem()
{
var buttonForQAT = new SplitButton
{
CanAddButtonToQuickAccessToolBar = false
};
buttonForQAT.Click += (sender, e) => this.RaiseEvent(e);
buttonForQAT.DropDownOpened += this.OnQuickAccessOpened;
RibbonProperties.SetSize(buttonForQAT, RibbonControlSize.Small);
this.BindQuickAccessItem(buttonForQAT);
this.BindQuickAccessItemDropDownEvents(buttonForQAT);
this.quickAccessButton = buttonForQAT;
return buttonForQAT;
}
/// <inheritdoc />
protected override void BindQuickAccessItem(FrameworkElement element)
{
RibbonControl.Bind(this, element, nameof(this.DisplayMemberPath), DisplayMemberPathProperty, BindingMode.OneWay);
RibbonControl.Bind(this, element, nameof(this.GroupStyleSelector), GroupStyleSelectorProperty, BindingMode.OneWay);
RibbonControl.Bind(this, element, nameof(this.ItemContainerStyle), ItemContainerStyleProperty, BindingMode.OneWay);
RibbonControl.Bind(this, element, nameof(this.ItemsPanel), ItemsPanelProperty, BindingMode.OneWay);
RibbonControl.Bind(this, element, nameof(this.ItemStringFormat), ItemStringFormatProperty, BindingMode.OneWay);
RibbonControl.Bind(this, element, nameof(this.ItemTemplate), ItemTemplateProperty, BindingMode.OneWay);
RibbonControl.Bind(this, element, nameof(this.MaxDropDownHeight), MaxDropDownHeightProperty, BindingMode.OneWay);
RibbonControl.Bind(this, element, nameof(this.IsChecked), IsCheckedProperty, BindingMode.TwoWay);
RibbonControl.Bind(this, element, nameof(this.DropDownToolTip), DropDownToolTipProperty, BindingMode.TwoWay);
RibbonControl.Bind(this, element, nameof(this.IsCheckable), IsCheckableProperty, BindingMode.Default);
RibbonControl.Bind(this, element, nameof(this.IsButtonEnabled), IsButtonEnabledProperty, BindingMode.Default);
RibbonControl.Bind(this, element, nameof(this.ContextMenu), ContextMenuProperty, BindingMode.Default);
RibbonControl.Bind(this, element, nameof(this.ResizeMode), ResizeModeProperty, BindingMode.Default);
RibbonControl.Bind(this, element, nameof(this.MaxDropDownHeight), MaxDropDownHeightProperty, BindingMode.Default);
RibbonControl.Bind(this, element, nameof(this.HasTriangle), HasTriangleProperty, BindingMode.Default);
RibbonControl.BindQuickAccessItem(this, element);
}
/// <summary>
/// Gets or sets whether button can be added to quick access toolbar
/// </summary>
public bool CanAddButtonToQuickAccessToolBar
{
get { return (bool)this.GetValue(CanAddButtonToQuickAccessToolBarProperty); }
set { this.SetValue(CanAddButtonToQuickAccessToolBarProperty, value); }
}
/// <summary>Identifies the <see cref="CanAddButtonToQuickAccessToolBar"/> dependency property.</summary>
public static readonly DependencyProperty CanAddButtonToQuickAccessToolBarProperty = DependencyProperty.Register(nameof(CanAddButtonToQuickAccessToolBar), typeof(bool), typeof(SplitButton), new PropertyMetadata(BooleanBoxes.TrueBox, RibbonControl.OnCanAddToQuickAccessToolBarChanged));
#region Implementation of IKeyTipInformationProvider
/// <inheritdoc />
public IEnumerable<KeyTipInformation> GetKeyTipInformations(bool hide)
{
if (string.IsNullOrEmpty(this.KeyTip) == false
&& this.button is null == false)
{
if (string.IsNullOrEmpty(this.SecondaryKeyTip))
{
yield return new KeyTipInformation(this.KeyTip + this.PrimaryActionKeyTipPostfix, this.button, hide)
{
VisualTarget = this
};
}
else
{
yield return new KeyTipInformation(this.KeyTip, this.button, hide)
{
VisualTarget = this
};
}
}
if (string.IsNullOrEmpty(this.SecondaryKeyTip) == false)
{
yield return new KeyTipInformation(this.SecondaryKeyTip, this, hide);
}
else if (string.IsNullOrEmpty(this.KeyTip) == false)
{
yield return new KeyTipInformation(this.KeyTip + this.SecondaryActionKeyTipPostfix, this, hide);
}
}
#endregion
#endregion
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.button != null)
{
yield return this.button;
}
}
}
}
}

View File

@@ -0,0 +1,95 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Windows;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents the container for the <see cref="StartScreenTabControl"/>.
/// </summary>
public class StartScreen : Backstage
{
/// <summary>
/// Indicates whether the <see cref="StartScreen"/> has aleaady been shown or not.
/// </summary>
public bool Shown
{
get => (bool)this.GetValue(ShownProperty);
set => this.SetValue(ShownProperty, value ? BooleanBoxes.TrueBox : BooleanBoxes.FalseBox);
}
/// <summary>Identifies the <see cref="Shown"/> dependency property.</summary>
public static readonly DependencyProperty ShownProperty =
DependencyProperty.Register(nameof(Shown), typeof(bool), typeof(StartScreen), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null));
static StartScreen()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(StartScreen), new FrameworkPropertyMetadata(typeof(StartScreen)));
VisibilityProperty.OverrideMetadata(typeof(StartScreen), new PropertyMetadata(OnVisibilityChanged));
}
private static void OnVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((StartScreen)d).UpdateIsTitleBarCollapsed();
}
// This is required for scenarios like in #445 where the start screen is shown, but never hidden through Hide() but made hidden by changing just it's visibility.
private void UpdateIsTitleBarCollapsed()
{
if (this.IsOpen == false)
{
return;
}
var parentRibbon = GetParentRibbon(this);
parentRibbon?.TitleBar?.SetCurrentValue(RibbonTitleBar.IsCollapsedProperty, this.Visibility == Visibility.Visible
? BooleanBoxes.TrueBox
: BooleanBoxes.FalseBox);
}
/// <summary>
/// Shows the <see cref="StartScreen"/>.
/// </summary>
/// <returns>
/// <c>true</c> if the <see cref="StartScreen"/> was made visible.
/// <c>false</c> if the <see cref="StartScreen"/> was previously shown and was not made visible during this call.
/// </returns>
protected override bool Show()
{
if (this.Shown)
{
return false;
}
this.Shown = base.Show();
if (this.Shown)
{
var parentRibbon = GetParentRibbon(this);
parentRibbon?.TitleBar?.SetCurrentValue(RibbonTitleBar.IsCollapsedProperty, BooleanBoxes.TrueBox);
}
return this.Shown;
}
/// <summary>
/// Hides the <see cref="StartScreen" />.
/// </summary>
protected override void Hide()
{
var wasShown = this.Shown;
base.Hide();
if (wasShown)
{
var parentRibbon = GetParentRibbon(this);
parentRibbon?.TitleBar?.ClearValue(RibbonTitleBar.IsCollapsedProperty);
}
}
}
}

View File

@@ -0,0 +1,86 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Collections;
using System.Windows;
using Fluent.Helpers;
/// <summary>
/// Control for representing the left and right side of the start screen.
/// </summary>
/// <remarks>
/// To control some aspects of the left handed side of this control please use properties prefixed with "ItemsPanel*".
/// </remarks>
public class StartScreenTabControl : BackstageTabControl
{
/// <summary>
/// Left side panel content of the startscreen.
/// </summary>
public object LeftContent
{
get { return this.GetValue(LeftContentProperty); }
set { this.SetValue(LeftContentProperty, value); }
}
/// <summary>Identifies the <see cref="LeftContent"/> dependency property.</summary>
public static readonly DependencyProperty LeftContentProperty = DependencyProperty.Register(nameof(LeftContent), typeof(object), typeof(StartScreenTabControl), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
/// <summary>
/// Defines the margin for <see cref="LeftContent"/>
/// </summary>
public Thickness LeftContentMargin
{
get { return (Thickness)this.GetValue(LeftContentMarginProperty); }
set { this.SetValue(LeftContentMarginProperty, value); }
}
/// <summary>Identifies the <see cref="LeftContentMargin"/> dependency property.</summary>
public static readonly DependencyProperty LeftContentMarginProperty =
DependencyProperty.Register(nameof(LeftContentMargin), typeof(Thickness), typeof(StartScreenTabControl), new PropertyMetadata(default(Thickness)));
/// <summary>
/// Right side panel content of the startscreen.
/// </summary>
public object RightContent
{
get { return this.GetValue(RightContentProperty); }
set { this.SetValue(RightContentProperty, value); }
}
/// <summary>Identifies the <see cref="RightContent"/> dependency property.</summary>
public static readonly DependencyProperty RightContentProperty = DependencyProperty.Register(nameof(RightContent), typeof(object), typeof(StartScreenTabControl), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
/// <summary>
/// Static constructor.
/// </summary>
static StartScreenTabControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(StartScreenTabControl), new FrameworkPropertyMetadata(typeof(StartScreenTabControl)));
ItemsPanelMinWidthProperty.OverrideMetadata(typeof(StartScreenTabControl), new PropertyMetadata(342d));
}
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.LeftContent != null)
{
yield return this.LeftContent;
}
if (this.RightContent != null)
{
yield return this.RightContent;
}
}
}
}
}

View File

@@ -0,0 +1,304 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Threading;
using Fluent.Extensions;
using Fluent.Localization;
/// <summary>
/// Represents ribbon status bar
/// </summary>
public class StatusBar : System.Windows.Controls.Primitives.StatusBar
{
#region Fields
// Context menu
private readonly ContextMenu contextMenu = new ContextMenu();
private bool waitingForItemContainerGenerator;
#endregion
#region Properties
private object currentItem;
#endregion
#region Constructors
/// <summary>
/// Static constructor
/// </summary>
static StatusBar()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(StatusBar), new FrameworkPropertyMetadata(typeof(StatusBar)));
}
/// <summary>
/// Default constructor
/// </summary>
public StatusBar()
{
this.RecreateMenu();
this.ContextMenu = this.contextMenu;
this.Loaded += this.OnLoaded;
this.Unloaded += this.OnUnloaded;
this.ItemContainerGenerator.StatusChanged += this.HandleItemContainerGeneratorStatusChanged;
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
}
#endregion
#region Overrides
/// <inheritdoc />
protected override DependencyObject GetContainerForItemOverride()
{
var item = this.currentItem;
this.currentItem = null;
if (this.UsesItemContainerTemplate
&& item != null)
{
var dataTemplate = this.ItemContainerTemplateSelector.SelectTemplate(item, this);
if (dataTemplate != null)
{
var dataTemplateContent = (object)dataTemplate.LoadContent();
if (dataTemplateContent is StatusBarItem
|| dataTemplateContent is Separator)
{
return (DependencyObject)dataTemplateContent;
}
throw new InvalidOperationException("Invalid ItemContainer");
}
}
return new StatusBarItem();
}
/// <inheritdoc />
protected override bool IsItemItsOwnContainerOverride(object item)
{
var isItemItsOwnContainerOverride = item is StatusBarItem || item is Separator;
if (isItemItsOwnContainerOverride == false)
{
this.currentItem = item;
}
return isItemItsOwnContainerOverride;
}
private void HandleItemContainerGeneratorStatusChanged(object sender, EventArgs e)
{
if (this.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
{
return;
}
this.RunInDispatcherAsync(this.RecreateMenu, DispatcherPriority.Loaded);
}
/// <inheritdoc />
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (this.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated
|| this.waitingForItemContainerGenerator)
{
this.waitingForItemContainerGenerator = true;
return;
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
{
foreach (var newItem in e.NewItems)
{
var container = this.ItemContainerGenerator.ContainerFromItem(newItem);
var containerIndex = this.ItemContainerGenerator.IndexFromContainer(container);
if (container is StatusBarItem item)
{
item.Checked += this.OnItemChecked;
item.Unchecked += this.OnItemUnchecked;
this.contextMenu.Items.Insert(containerIndex, new StatusBarMenuItem(item));
}
else
{
this.contextMenu.Items.Insert(containerIndex, new Separator());
}
}
break;
}
case NotifyCollectionChangedAction.Move:
{
for (var i = 0; i < e.NewItems.Count; i++)
{
var menuItem = this.contextMenu.Items[e.OldStartingIndex + 1];
this.contextMenu.Items.Remove(e.OldStartingIndex + 1);
this.contextMenu.Items.Insert(e.NewStartingIndex + i + 1, menuItem);
}
break;
}
case NotifyCollectionChangedAction.Remove:
{
for (var i = 0; i < e.OldItems.Count; i++)
{
if (this.contextMenu.Items[e.OldStartingIndex + 1] is StatusBarMenuItem menuItem)
{
menuItem.StatusBarItem.Checked -= this.OnItemChecked;
menuItem.StatusBarItem.Unchecked -= this.OnItemUnchecked;
}
this.contextMenu.Items.RemoveAt(e.OldStartingIndex + 1);
}
break;
}
case NotifyCollectionChangedAction.Replace:
{
for (var i = 0; i < e.OldItems.Count; i++)
{
if (this.contextMenu.Items[e.OldStartingIndex + 1] is StatusBarMenuItem menuItem)
{
menuItem.StatusBarItem.Checked -= this.OnItemChecked;
menuItem.StatusBarItem.Unchecked -= this.OnItemUnchecked;
}
this.contextMenu.Items.RemoveAt(e.OldStartingIndex + 1);
}
for (var i = 0; i < e.NewItems.Count; i++)
{
if (this.ItemContainerGenerator.ContainerFromItem(e.NewItems[i]) is StatusBarItem item)
{
item.Checked += this.OnItemChecked;
item.Unchecked += this.OnItemUnchecked;
this.contextMenu.Items.Insert(e.NewStartingIndex + i + 1, new StatusBarMenuItem(item));
}
else
{
this.contextMenu.Items.Insert(e.NewStartingIndex + i + 1, new Separator());
}
}
break;
}
case NotifyCollectionChangedAction.Reset:
{
this.RecreateMenu();
break;
}
}
}
private void OnItemUnchecked(object sender, RoutedEventArgs e)
{
this.UpdateSeparartorsVisibility();
}
private void OnItemChecked(object sender, RoutedEventArgs e)
{
this.UpdateSeparartorsVisibility();
}
#endregion
#region Private Methods
// Creates menu
private void RecreateMenu()
{
this.contextMenu.Items.Clear();
// Adding header separator
this.contextMenu.Items.Add(new GroupSeparatorMenuItem());
RibbonControl.Bind(RibbonLocalization.Current.Localization, this.contextMenu.Items[0] as FrameworkElement, nameof(RibbonLocalizationBase.CustomizeStatusBar), HeaderedItemsControl.HeaderProperty, BindingMode.OneWay);
for (var i = 0; i < this.Items.Count; i++)
{
if (this.ItemContainerGenerator.ContainerFromItem(this.Items[i]) is StatusBarItem item)
{
// Prevent double event handler
item.Checked -= this.OnItemChecked;
item.Unchecked -= this.OnItemUnchecked;
item.Checked += this.OnItemChecked;
item.Unchecked += this.OnItemUnchecked;
this.contextMenu.Items.Add(new StatusBarMenuItem(item));
}
else
{
this.contextMenu.Items.Add(new Separator());
}
}
this.UpdateSeparartorsVisibility();
this.waitingForItemContainerGenerator = false;
}
// Updates separators visibility, to not duplicate
private void UpdateSeparartorsVisibility()
{
var isPrevSeparator = false;
var isFirstVsible = true;
foreach (var item in this.Items)
{
var containerFromItem = this.ItemContainerGenerator.ContainerFromItem(item);
if (containerFromItem is Separator separator)
{
if (isPrevSeparator || isFirstVsible)
{
separator.Visibility = Visibility.Collapsed;
}
else
{
separator.Visibility = Visibility.Visible;
}
isPrevSeparator = true;
isFirstVsible = false;
}
else
{
if ((containerFromItem as StatusBarItem)?.Visibility == Visibility.Visible)
{
isPrevSeparator = false;
isFirstVsible = false;
}
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,165 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Windows;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents ribbon status bar item
/// </summary>
public class StatusBarItem : System.Windows.Controls.Primitives.StatusBarItem
{
#region Properties
#region Title
/// <summary>
/// Gets or sets ribbon status bar item
/// </summary>
public string Title
{
get { return (string)this.GetValue(TitleProperty); }
set { this.SetValue(TitleProperty, value); }
}
/// <summary>Identifies the <see cref="Title"/> dependency property.</summary>
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register(nameof(Title), typeof(string), typeof(StatusBarItem), new PropertyMetadata());
#endregion
#region Value
/// <summary>
/// Gets or sets ribbon status bar value
/// </summary>
public string Value
{
get { return (string)this.GetValue(ValueProperty); }
set { this.SetValue(ValueProperty, value); }
}
/// <summary>Identifies the <see cref="Value"/> dependency property.</summary>
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(string), typeof(StatusBarItem),
new PropertyMetadata(OnValueChanged));
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var item = (StatusBarItem)d;
item.CoerceValue(ContentProperty);
}
#endregion
#region isChecked
/// <summary>
/// Gets or sets whether status bar item is checked in menu
/// </summary>
public bool IsChecked
{
get { return (bool)this.GetValue(IsCheckedProperty); }
set { this.SetValue(IsCheckedProperty, value); }
}
/// <summary>Identifies the <see cref="IsChecked"/> dependency property.</summary>
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register(nameof(IsChecked), typeof(bool), typeof(StatusBarItem), new PropertyMetadata(BooleanBoxes.TrueBox, OnIsCheckedChanged));
// Handles IsChecked changed
private static void OnIsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var item = (StatusBarItem)d;
item.CoerceValue(VisibilityProperty);
if ((bool)e.NewValue)
{
item.RaiseChecked();
}
else
{
item.RaiseUnchecked();
}
}
#endregion
#endregion
#region Events
/// <summary>
/// Occurs when status bar item checks
/// </summary>
public event RoutedEventHandler Checked;
/// <summary>
/// Occurs when status bar item unchecks
/// </summary>
public event RoutedEventHandler Unchecked;
// Raises checked event
#pragma warning disable WPF0005 // Name of PropertyChangedCallback should match registered name.
private void RaiseChecked()
{
this.Checked?.Invoke(this, new RoutedEventArgs());
}
// Raises unchecked event
private void RaiseUnchecked()
{
this.Unchecked?.Invoke(this, new RoutedEventArgs());
}
#pragma warning restore WPF0005 // Name of PropertyChangedCallback should match registered name.
#endregion
#region Constructors
/// <summary>
/// Static constructor
/// </summary>
static StatusBarItem()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(StatusBarItem), new FrameworkPropertyMetadata(typeof(StatusBarItem)));
VisibilityProperty.AddOwner(typeof(StatusBarItem), new FrameworkPropertyMetadata(null, CoerceVisibility));
ContentProperty.AddOwner(typeof(StatusBarItem), new FrameworkPropertyMetadata(OnContentChanged, CoerceContent));
}
// Content changing handler
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var item = (StatusBarItem)d;
item.CoerceValue(ValueProperty);
}
// Coerce content
private static object CoerceContent(DependencyObject d, object basevalue)
{
var item = (StatusBarItem)d;
// if content is null returns value
if (basevalue is null
&& item.Value != null)
{
return item.Value;
}
return basevalue;
}
// Coerce visibility
private static object CoerceVisibility(DependencyObject d, object basevalue)
{
// If unchecked when not visible in status bar
if (((StatusBarItem)d).IsChecked == false)
{
return Visibility.Collapsed;
}
return basevalue;
}
#endregion
}
}

View File

@@ -0,0 +1,53 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Windows;
/// <summary>
/// Represents menu item in ribbon status bar menu
/// </summary>
public class StatusBarMenuItem : MenuItem
{
#region Properties
/// <summary>
/// Gets or sets Ribbon Status Bar menu item
/// </summary>
public StatusBarItem StatusBarItem
{
get { return (StatusBarItem)this.GetValue(StatusBarItemProperty); }
set { this.SetValue(StatusBarItemProperty, value); }
}
/// <summary>Identifies the <see cref="StatusBarItem"/> dependency property.</summary>
public static readonly DependencyProperty StatusBarItemProperty =
DependencyProperty.Register(nameof(StatusBarItem), typeof(StatusBarItem), typeof(StatusBarMenuItem), new PropertyMetadata());
#endregion
#region Constructors
/// <summary>
/// Static constructor
/// </summary>
static StatusBarMenuItem()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(StatusBarMenuItem), new FrameworkPropertyMetadata(typeof(StatusBarMenuItem)));
}
/// <summary>
/// Default constructor
/// </summary>
/// <param name="item">Ribbon Status Bar menu item</param>
public StatusBarMenuItem(StatusBarItem item)
{
this.StatusBarItem = item;
}
internal StatusBarMenuItem()
{
}
#endregion
}
}

View File

@@ -0,0 +1,174 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using Fluent.Internal;
/// <summary>
/// Represents panel for status bar
/// </summary>
public class StatusBarPanel : Panel
{
#region Attributes
private readonly List<UIElement> leftChildren = new List<UIElement>();
private readonly List<UIElement> rightChildren = new List<UIElement>();
private readonly List<UIElement> otherChildren = new List<UIElement>();
private int lastRightIndex;
private int lastLeftIndex;
#endregion
#region Overrides
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize)
{
// Sort children
this.leftChildren.Clear();
this.rightChildren.Clear();
this.otherChildren.Clear();
for (var i = 0; i < this.InternalChildren.Count; i++)
{
if (this.InternalChildren[i] is FrameworkElement child)
{
if (child.HorizontalAlignment == HorizontalAlignment.Left)
{
this.leftChildren.Add(child);
}
else if (child.HorizontalAlignment == HorizontalAlignment.Right)
{
this.rightChildren.Add(child);
}
else
{
this.otherChildren.Add(child);
}
}
}
this.lastRightIndex = this.rightChildren.Count;
this.lastLeftIndex = this.leftChildren.Count;
// Measure children
var zero = new Size(0, 0);
double width = 0;
double height = 0;
var canAdd = true;
// Right children
for (var i = 0; i < this.rightChildren.Count; i++)
{
if (canAdd)
{
this.rightChildren[i].Measure(SizeConstants.Infinite);
height = Math.Max(this.rightChildren[i].DesiredSize.Height, height);
if (width + this.rightChildren[i].DesiredSize.Width <= availableSize.Width)
{
width += this.rightChildren[i].DesiredSize.Width;
}
else
{
canAdd = false;
this.rightChildren[i].Measure(zero);
this.lastRightIndex = i;
this.lastLeftIndex = 0;
}
}
else
{
this.rightChildren[i].Measure(zero);
}
}
// Left children
for (var i = 0; i < this.leftChildren.Count; i++)
{
if (canAdd)
{
this.leftChildren[i].Measure(SizeConstants.Infinite);
height = Math.Max(this.leftChildren[i].DesiredSize.Height, height);
if (width + this.leftChildren[i].DesiredSize.Width <= availableSize.Width)
{
width += this.leftChildren[i].DesiredSize.Width;
}
else
{
canAdd = false;
this.leftChildren[i].Measure(zero);
this.lastLeftIndex = i;
}
}
else
{
this.leftChildren[i].Measure(zero);
}
}
// Collapse other children
foreach (var otherChild in this.otherChildren)
{
otherChild.Measure(zero);
}
return new Size(width, height);
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize)
{
var zero = new Rect(0, 0, 0, 0);
// Right shift
double rightShift = 0;
// Arrange right
for (var i = this.rightChildren.Count - 1; i >= 0; i--)
{
if (this.lastRightIndex > i)
{
rightShift += this.rightChildren[i].DesiredSize.Width;
this.rightChildren[i].Arrange(new Rect(finalSize.Width - rightShift, 0, this.rightChildren[i].DesiredSize.Width, finalSize.Height));
}
else
{
this.rightChildren[i].Arrange(zero);
}
}
// Left shift
double leftShift = 0;
// Arrange left
for (var i = 0; i < this.leftChildren.Count; i++)
{
if (i < this.lastLeftIndex)
{
this.leftChildren[i].Arrange(new Rect(leftShift, 0, this.leftChildren[i].DesiredSize.Width, finalSize.Height));
leftShift += this.leftChildren[i].DesiredSize.Width;
}
else
{
this.leftChildren[i].Arrange(zero);
}
}
// Arrange other
foreach (var otherChild in this.otherChildren)
{
otherChild.Arrange(zero);
}
return finalSize;
}
#endregion
}
}

View File

@@ -0,0 +1,295 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Collections;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using Fluent.Helpers;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents custom Fluent UI TextBox
/// </summary>
[TemplatePart(Name = "PART_ContentHost", Type = typeof(UIElement))]
public class TextBox : System.Windows.Controls.TextBox, IQuickAccessItemProvider, IRibbonControl
{
private UIElement contentHost;
#region Properties (Dependency)
#region InputWidth
/// <summary>
/// Gets or sets width of the value input part of textbox
/// </summary>
public double InputWidth
{
get { return (double)this.GetValue(InputWidthProperty); }
set { this.SetValue(InputWidthProperty, value); }
}
/// <summary>Identifies the <see cref="InputWidth"/> dependency property.</summary>
public static readonly DependencyProperty InputWidthProperty =
DependencyProperty.Register(nameof(InputWidth), typeof(double), typeof(TextBox), new PropertyMetadata(DoubleBoxes.NaN));
#endregion
#endregion
#region Constructors
/// <summary>
/// Static constructor
/// </summary>
static TextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBox), new FrameworkPropertyMetadata(typeof(TextBox)));
}
#endregion
#region Overrides
/// <inheritdoc />
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.contentHost = this.Template.FindName("PART_ContentHost", this) as UIElement;
}
/// <inheritdoc />
// Handling context menu manually to fix #653
protected override void OnContextMenuOpening(ContextMenuEventArgs e)
{
this.InvalidateProperty(ContextMenuProperty);
if (this.contentHost?.IsMouseOver == true
|| this.contentHost?.IsKeyboardFocusWithin == true)
{
base.OnContextMenuOpening(e);
}
else
{
var coerced = ContextMenuService.CoerceContextMenu(this, this.ContextMenu);
if (coerced != null)
{
this.SetCurrentValue(ContextMenuProperty, coerced);
}
base.OnContextMenuOpening(e);
}
}
/// <inheritdoc />
protected override void OnContextMenuClosing(ContextMenuEventArgs e)
{
this.InvalidateProperty(ContextMenuProperty);
base.OnContextMenuClosing(e);
}
/// <inheritdoc />
protected override void OnKeyUp(KeyEventArgs e)
{
// Avoid Click invocation (from RibbonControl)
if (e.Key == Key.Enter
|| e.Key == Key.Space)
{
return;
}
base.OnKeyUp(e);
}
#endregion
#region Quick Access Item Creating
/// <inheritdoc />
public virtual FrameworkElement CreateQuickAccessItem()
{
var textBoxForQAT = new TextBox();
this.BindQuickAccessItem(textBoxForQAT);
return textBoxForQAT;
}
/// <inheritdoc />
public bool CanAddToQuickAccessToolBar
{
get { return (bool)this.GetValue(CanAddToQuickAccessToolBarProperty); }
set { this.SetValue(CanAddToQuickAccessToolBarProperty, value); }
}
/// <summary>Identifies the <see cref="CanAddToQuickAccessToolBar"/> dependency property.</summary>
public static readonly DependencyProperty CanAddToQuickAccessToolBarProperty = RibbonControl.CanAddToQuickAccessToolBarProperty.AddOwner(typeof(TextBox), new PropertyMetadata(BooleanBoxes.TrueBox, RibbonControl.OnCanAddToQuickAccessToolBarChanged));
/// <summary>
/// This method must be overridden to bind properties to use in quick access creating
/// </summary>
/// <param name="element">Toolbar item</param>
protected virtual void BindQuickAccessItem(FrameworkElement element)
{
RibbonControl.BindQuickAccessItem(this, element);
var textBoxQAT = (TextBox)element;
textBoxQAT.Width = this.Width;
this.ForwardBindingsForQAT(this, textBoxQAT);
RibbonControl.BindQuickAccessItem(this, element);
}
// ReSharper disable once SuggestBaseTypeForParameter
private void ForwardBindingsForQAT(TextBox source, TextBox target)
{
RibbonControl.Bind(source, target, nameof(this.Text), TextProperty, BindingMode.TwoWay, UpdateSourceTrigger.PropertyChanged);
RibbonControl.Bind(source, target, nameof(this.IsReadOnly), IsReadOnlyProperty, BindingMode.OneWay);
RibbonControl.Bind(source, target, nameof(this.CharacterCasing), CharacterCasingProperty, BindingMode.TwoWay);
RibbonControl.Bind(source, target, nameof(this.MaxLength), MaxLengthProperty, BindingMode.TwoWay);
RibbonControl.Bind(source, target, nameof(this.TextAlignment), TextAlignmentProperty, BindingMode.TwoWay);
RibbonControl.Bind(source, target, nameof(this.TextDecorations), TextDecorationsProperty, BindingMode.TwoWay);
RibbonControl.Bind(source, target, nameof(this.IsUndoEnabled), IsUndoEnabledProperty, BindingMode.TwoWay);
RibbonControl.Bind(source, target, nameof(this.UndoLimit), UndoLimitProperty, BindingMode.TwoWay);
RibbonControl.Bind(source, target, nameof(this.AutoWordSelection), AutoWordSelectionProperty, BindingMode.TwoWay);
RibbonControl.Bind(source, target, nameof(this.SelectionBrush), SelectionBrushProperty, BindingMode.TwoWay);
RibbonControl.Bind(source, target, nameof(this.SelectionOpacity), SelectionOpacityProperty, BindingMode.TwoWay);
RibbonControl.Bind(source, target, nameof(this.CaretBrush), CaretBrushProperty, BindingMode.TwoWay);
RibbonControl.Bind(source, target, nameof(this.InputWidth), InputWidthProperty, BindingMode.TwoWay);
}
#endregion
#region Implementation of Ribbon interfaces
/// <inheritdoc />
public KeyTipPressedResult OnKeyTipPressed()
{
this.SelectAll();
this.Focus();
return new KeyTipPressedResult(true, false);
}
/// <inheritdoc />
public void OnKeyTipBack()
{
}
#region Size
/// <inheritdoc />
public RibbonControlSize Size
{
get { return (RibbonControlSize)this.GetValue(SizeProperty); }
set { this.SetValue(SizeProperty, value); }
}
/// <summary>Identifies the <see cref="Size"/> dependency property.</summary>
public static readonly DependencyProperty SizeProperty = RibbonProperties.SizeProperty.AddOwner(typeof(TextBox));
#endregion
#region SizeDefinition
/// <inheritdoc />
public RibbonControlSizeDefinition SizeDefinition
{
get { return (RibbonControlSizeDefinition)this.GetValue(SizeDefinitionProperty); }
set { this.SetValue(SizeDefinitionProperty, value); }
}
/// <summary>Identifies the <see cref="SizeDefinition"/> dependency property.</summary>
public static readonly DependencyProperty SizeDefinitionProperty = RibbonProperties.SizeDefinitionProperty.AddOwner(typeof(TextBox));
#endregion
#region KeyTip
/// <inheritdoc />
public string KeyTip
{
get { return (string)this.GetValue(KeyTipProperty); }
set { this.SetValue(KeyTipProperty, value); }
}
/// <inheritdoc cref="Fluent.KeyTip.KeysProperty"/>
public static readonly DependencyProperty KeyTipProperty = Fluent.KeyTip.KeysProperty.AddOwner(typeof(TextBox));
#endregion
#region Header
/// <inheritdoc />
public object Header
{
get { return this.GetValue(HeaderProperty); }
set { this.SetValue(HeaderProperty, value); }
}
/// <summary>Identifies the <see cref="Header"/> dependency property.</summary>
public static readonly DependencyProperty HeaderProperty = RibbonControl.HeaderProperty.AddOwner(typeof(TextBox), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region Icon
/// <inheritdoc />
public object Icon
{
get { return this.GetValue(IconProperty); }
set { this.SetValue(IconProperty, value); }
}
/// <summary>Identifies the <see cref="Icon"/> dependency property.</summary>
public static readonly DependencyProperty IconProperty = RibbonControl.IconProperty.AddOwner(typeof(TextBox), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#endregion
/// <inheritdoc />
void ILogicalChildSupport.AddLogicalChild(object child)
{
this.AddLogicalChild(child);
}
/// <inheritdoc />
void ILogicalChildSupport.RemoveLogicalChild(object child)
{
this.RemoveLogicalChild(child);
}
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.Icon != null)
{
yield return this.Icon;
}
if (this.Header != null)
{
yield return this.Header;
}
}
}
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() => new Fluent.Automation.Peers.RibbonTextBoxAutomationPeer(this);
}
}

View File

@@ -0,0 +1,291 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Collections;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Data;
using System.Windows.Markup;
using Fluent.Helpers;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents toggle button
/// </summary>
[ContentProperty(nameof(Header))]
public class ToggleButton : System.Windows.Controls.Primitives.ToggleButton, IToggleButton, IRibbonControl, IQuickAccessItemProvider, ILargeIconProvider
{
#region Properties
#region Size
/// <inheritdoc />
public RibbonControlSize Size
{
get { return (RibbonControlSize)this.GetValue(SizeProperty); }
set { this.SetValue(SizeProperty, value); }
}
/// <summary>Identifies the <see cref="Size"/> dependency property.</summary>
public static readonly DependencyProperty SizeProperty = RibbonProperties.SizeProperty.AddOwner(typeof(ToggleButton));
#endregion
#region SizeDefinition
/// <inheritdoc />
public RibbonControlSizeDefinition SizeDefinition
{
get { return (RibbonControlSizeDefinition)this.GetValue(SizeDefinitionProperty); }
set { this.SetValue(SizeDefinitionProperty, value); }
}
/// <summary>Identifies the <see cref="SizeDefinition"/> dependency property.</summary>
public static readonly DependencyProperty SizeDefinitionProperty = RibbonProperties.SizeDefinitionProperty.AddOwner(typeof(ToggleButton));
#endregion
#region KeyTip
/// <inheritdoc />
public string KeyTip
{
get { return (string)this.GetValue(KeyTipProperty); }
set { this.SetValue(KeyTipProperty, value); }
}
/// <inheritdoc cref="Fluent.KeyTip.KeysProperty"/>
public static readonly DependencyProperty KeyTipProperty = Fluent.KeyTip.KeysProperty.AddOwner(typeof(ToggleButton));
#endregion
#region GroupName
/// <inheritdoc />
public string GroupName
{
get { return (string)this.GetValue(GroupNameProperty); }
set { this.SetValue(GroupNameProperty, value); }
}
/// <summary>Identifies the <see cref="GroupName"/> dependency property.</summary>
public static readonly DependencyProperty GroupNameProperty =
DependencyProperty.Register(nameof(GroupName), typeof(string), typeof(ToggleButton), new PropertyMetadata(ToggleButtonHelper.OnGroupNameChanged));
#endregion
#region Header
/// <inheritdoc />
public object Header
{
get { return this.GetValue(HeaderProperty); }
set { this.SetValue(HeaderProperty, value); }
}
/// <summary>Identifies the <see cref="Header"/> dependency property.</summary>
public static readonly DependencyProperty HeaderProperty = RibbonControl.HeaderProperty.AddOwner(typeof(ToggleButton), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region Icon
/// <inheritdoc />
public object Icon
{
get { return this.GetValue(IconProperty); }
set { this.SetValue(IconProperty, value); }
}
/// <summary>Identifies the <see cref="Icon"/> dependency property.</summary>
public static readonly DependencyProperty IconProperty = RibbonControl.IconProperty.AddOwner(typeof(ToggleButton), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region LargeIcon
/// <inheritdoc />
public object LargeIcon
{
get { return this.GetValue(LargeIconProperty); }
set { this.SetValue(LargeIconProperty, value); }
}
/// <summary>Identifies the <see cref="LargeIcon"/> dependency property.</summary>
public static readonly DependencyProperty LargeIconProperty = LargeIconProviderProperties.LargeIconProperty.AddOwner(typeof(ToggleButton), new PropertyMetadata(LogicalChildSupportHelper.OnLogicalChildPropertyChanged));
#endregion
#region IsDefinitive
/// <summary>
/// Gets or sets whether ribbon control click must close backstage
/// </summary>
public bool IsDefinitive
{
get { return (bool)this.GetValue(IsDefinitiveProperty); }
set { this.SetValue(IsDefinitiveProperty, value); }
}
/// <summary>Identifies the <see cref="IsDefinitive"/> dependency property.</summary>
public static readonly DependencyProperty IsDefinitiveProperty =
DependencyProperty.Register(nameof(IsDefinitive), typeof(bool), typeof(ToggleButton), new PropertyMetadata(BooleanBoxes.TrueBox));
#endregion
#endregion
#region Constructors
/// <summary>
/// Initializes static members of the <see cref="ToggleButton"/> class.
/// </summary>
static ToggleButton()
{
var type = typeof(ToggleButton);
DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(type));
ContextMenuService.Attach(type);
ToolTipService.Attach(type);
}
/// <summary>
/// Initializes a new instance of the <see cref="ToggleButton"/> class.
/// </summary>
public ToggleButton()
{
ContextMenuService.Coerce(this);
}
#endregion
#region Overrides
/// <inheritdoc />
protected override void OnClick()
{
// Close popup on click
if (this.IsDefinitive)
{
PopupService.RaiseDismissPopupEvent(this, DismissPopupMode.Always);
}
// fix for #481
// We can't overwrite OnToggle because it's "internal protected"...
if (string.IsNullOrEmpty(this.GroupName) == false)
{
// Only forward click if button is not checked to prevent wrong bound values
if (this.IsChecked == false)
{
base.OnClick();
}
}
else
{
base.OnClick();
}
}
/// <inheritdoc />
protected override void OnChecked(RoutedEventArgs e)
{
ToggleButtonHelper.UpdateButtonGroup(this);
base.OnChecked(e);
}
#endregion
/// <summary>
/// Used to call OnClick (which is protected)
/// </summary>
public void InvokeClick()
{
this.OnClick();
}
#region Quick Access Item Creating
/// <inheritdoc />
public virtual FrameworkElement CreateQuickAccessItem()
{
var button = new ToggleButton();
RibbonControl.Bind(this, button, nameof(this.IsChecked), IsCheckedProperty, BindingMode.TwoWay);
button.Click += (sender, e) => this.RaiseEvent(e);
RibbonControl.BindQuickAccessItem(this, button);
return button;
}
/// <inheritdoc />
public bool CanAddToQuickAccessToolBar
{
get { return (bool)this.GetValue(CanAddToQuickAccessToolBarProperty); }
set { this.SetValue(CanAddToQuickAccessToolBarProperty, value); }
}
/// <summary>Identifies the <see cref="CanAddToQuickAccessToolBar"/> dependency property.</summary>
public static readonly DependencyProperty CanAddToQuickAccessToolBarProperty = RibbonControl.CanAddToQuickAccessToolBarProperty.AddOwner(typeof(ToggleButton), new PropertyMetadata(BooleanBoxes.TrueBox, RibbonControl.OnCanAddToQuickAccessToolBarChanged));
#endregion
#region Implementation of IKeyTipedControl
/// <inheritdoc />
public KeyTipPressedResult OnKeyTipPressed()
{
this.OnClick();
return KeyTipPressedResult.Empty;
}
/// <inheritdoc />
public void OnKeyTipBack()
{
}
#endregion
/// <inheritdoc />
void ILogicalChildSupport.AddLogicalChild(object child)
{
this.AddLogicalChild(child);
}
/// <inheritdoc />
void ILogicalChildSupport.RemoveLogicalChild(object child)
{
this.RemoveLogicalChild(child);
}
/// <inheritdoc />
protected override IEnumerator LogicalChildren
{
get
{
var baseEnumerator = base.LogicalChildren;
while (baseEnumerator?.MoveNext() == true)
{
yield return baseEnumerator.Current;
}
if (this.Icon != null)
{
yield return this.Icon;
}
if (this.LargeIcon != null)
{
yield return this.LargeIcon;
}
if (this.Header != null)
{
yield return this.Header;
}
}
}
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() => new Fluent.Automation.Peers.RibbonToggleButtonAutomationPeer(this);
}
}

View File

@@ -0,0 +1,218 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Markup;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Represents specific label to use in particular ribbon controls
/// </summary>
[DefaultProperty(nameof(Text))]
[ContentProperty(nameof(Text))]
[TemplatePart(Name = "PART_TextRun", Type = typeof(AccessText))]
[TemplatePart(Name = "PART_TextRun2", Type = typeof(AccessText))]
public class TwoLineLabel : Control
{
#region Fields
/// <summary>
/// Run with text
/// </summary>
private AccessText textRun;
private AccessText textRun2;
#endregion
#region Properties
/// <summary>
/// Gets or sets whether label must have two lines
/// </summary>
public bool HasTwoLines
{
get { return (bool)this.GetValue(HasTwoLinesProperty); }
set { this.SetValue(HasTwoLinesProperty, value); }
}
/// <summary>Identifies the <see cref="HasTwoLines"/> dependency property.</summary>
public static readonly DependencyProperty HasTwoLinesProperty =
DependencyProperty.Register(nameof(HasTwoLines), typeof(bool), typeof(TwoLineLabel), new PropertyMetadata(BooleanBoxes.TrueBox, OnHasTwoLinesChanged));
/// <summary>
/// Handles HasTwoLines property changes
/// </summary>
/// <param name="d">Object</param>
/// <param name="e">The event data</param>
private static void OnHasTwoLinesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TwoLineLabel)d).UpdateTextRun();
}
/// <summary>
/// Gets or sets whether label has glyph
/// </summary>
public bool HasGlyph
{
get { return (bool)this.GetValue(HasGlyphProperty); }
set { this.SetValue(HasGlyphProperty, value); }
}
/// <summary>Identifies the <see cref="HasGlyph"/> dependency property.</summary>
public static readonly DependencyProperty HasGlyphProperty =
DependencyProperty.Register(nameof(HasGlyph), typeof(bool), typeof(TwoLineLabel), new PropertyMetadata(BooleanBoxes.FalseBox, OnHasGlyphChanged));
/// <summary>
/// Handles HasGlyph property changes
/// </summary>
/// <param name="d">Object</param>
/// <param name="e">The event data</param>
private static void OnHasGlyphChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TwoLineLabel)d).UpdateTextRun();
}
/// <summary>
/// Gets or sets the text
/// </summary>
public string Text
{
get { return (string)this.GetValue(TextProperty); }
set { this.SetValue(TextProperty, value); }
}
/// <summary>Identifies the <see cref="Text"/> dependency property.</summary>
public static readonly DependencyProperty TextProperty =
#pragma warning disable WPF0010 // Default value type must match registered type.
DependencyProperty.Register(nameof(Text), typeof(string), typeof(TwoLineLabel), new PropertyMetadata(StringBoxes.Empty, OnTextChanged));
#pragma warning restore WPF0010 // Default value type must match registered type.
#endregion
#region Initialize
/// <summary>
/// Static constructor
/// </summary>
static TwoLineLabel()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TwoLineLabel), new FrameworkPropertyMetadata(typeof(TwoLineLabel)));
FocusableProperty.OverrideMetadata(typeof(TwoLineLabel), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));
}
#endregion
#region Overrides
/// <inheritdoc />
public override void OnApplyTemplate()
{
this.textRun = this.GetTemplateChild("PART_TextRun") as AccessText;
this.textRun2 = this.GetTemplateChild("PART_TextRun2") as AccessText;
this.UpdateTextRun();
}
/// <inheritdoc />
protected override AutomationPeer OnCreateAutomationPeer() => new Fluent.Automation.Peers.TwoLineLabelAutomationPeer(this);
#endregion
#region Event handling
/// <summary>
/// Handles text property changes
/// </summary>
/// <param name="d">Object</param>
/// <param name="e">The event data</param>
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var label = (TwoLineLabel)d;
label.UpdateTextRun();
}
#endregion
#region Private methods
/// <summary>
/// Updates text runs and adds newline if HasTwoLines == true
/// </summary>
private void UpdateTextRun()
{
if (this.textRun is null
|| this.textRun2 is null)
{
return;
}
var text = this.Text?.Trim();
if (this.HasTwoLines == false
|| string.IsNullOrEmpty(text))
{
this.textRun.Text = text;
this.textRun2.Text = string.Empty;
return;
}
// 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 = 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 = 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 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) + " ";
}
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,283 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Windows;
using System.Windows.Controls;
using Fluent.Internal;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// UniformGrid is used to arrange children in a grid with all equal cell sizes.
/// </summary>
public class UniformGridWithItemSize : Panel
{
private int rows;
private int columns;
private int nonCollapsedCount;
private int usedColumns;
/// <summary>
/// Gets or sets panel orientation
/// </summary>
public Orientation Orientation
{
get { return (Orientation)this.GetValue(OrientationProperty); }
set { this.SetValue(OrientationProperty, value); }
}
/// <summary>Identifies the <see cref="Orientation"/> dependency property.</summary>
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register(
nameof(Orientation),
typeof(Orientation),
typeof(UniformGridWithItemSize),
new FrameworkPropertyMetadata(Orientation.Horizontal,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsParentMeasure));
/// <summary>
/// Specifies the number of maximum columns in the grid
/// </summary>
public int MinColumns
{
get => this.Orientation == Orientation.Horizontal ? (int)this.GetValue(MinColumnsProperty) : 1;
set => this.SetValue(MinColumnsProperty, value);
}
/// <summary>Identifies the <see cref="MinColumns"/> dependency property.</summary>
public static readonly DependencyProperty MinColumnsProperty =
DependencyProperty.Register(
nameof(MinColumns),
typeof(int),
typeof(UniformGridWithItemSize),
new FrameworkPropertyMetadata(IntBoxes.Zero,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsParentMeasure),
ValidateMinColumns);
private static bool ValidateMinColumns(object o)
{
return (int)o >= 0;
}
/// <summary>
/// Specifies the number of maximum columns in the grid
/// </summary>
public int MaxColumns
{
get => this.Orientation == Orientation.Horizontal ? (int)this.GetValue(MaxColumnsProperty) : 1;
set => this.SetValue(MaxColumnsProperty, value);
}
/// <summary>Identifies the <see cref="MaxColumns"/> dependency property.</summary>
public static readonly DependencyProperty MaxColumnsProperty =
DependencyProperty.Register(
nameof(MaxColumns),
typeof(int),
typeof(UniformGridWithItemSize),
new FrameworkPropertyMetadata(IntBoxes.Zero,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsParentMeasure),
ValidateMaxColumns);
private static bool ValidateMaxColumns(object o)
{
return (int)o >= 0;
}
/// <summary>Identifies the <see cref="ItemWidth"/> dependency property.</summary>
public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register(
nameof(ItemWidth), typeof(double), typeof(UniformGridWithItemSize), new FrameworkPropertyMetadata(DoubleBoxes.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsParentMeasure));
/// <summary>
/// Specifies the item width.
/// </summary>
public double ItemWidth
{
get => (double)this.GetValue(ItemWidthProperty);
set => this.SetValue(ItemWidthProperty, value);
}
/// <summary>Identifies the <see cref="ItemHeight"/> dependency property.</summary>
public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register(
nameof(ItemHeight), typeof(double), typeof(UniformGridWithItemSize), new FrameworkPropertyMetadata(DoubleBoxes.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsParentMeasure));
/// <summary>
/// Specifies the item height.
/// </summary>
public double ItemHeight
{
get => (double)this.GetValue(ItemHeightProperty);
set => this.SetValue(ItemHeightProperty, value);
}
/// <summary>
/// Compute the desired size of this UniformGrid by measuring all of the
/// children with a constraint equal to a cell's portion of the given
/// constraint (e.g. for a 2 x 4 grid, the child constraint would be
/// constraint.Width*0.5 x constraint.Height*0.25). The maximum child
/// width and maximum child height are tracked, and then the desired size
/// is computed by multiplying these maximums by the row and column count
/// (e.g. for a 2 x 4 grid, the desired size for the UniformGrid would be
/// maxChildDesiredWidth*2 x maxChildDesiredHeight*4).
/// </summary>
/// <param name="constraint">Constraint</param>
/// <returns>Desired size</returns>
protected override Size MeasureOverride(Size constraint)
{
this.UpdateComputedValues(this.MaxColumns);
var useDefinedItemWidth = double.IsNaN(this.ItemWidth) == false && DoubleUtil.AreClose(this.ItemWidth, 0) == false;
var maxItemWidth = useDefinedItemWidth
? this.ItemWidth
: constraint.Width / this.columns;
var useDefinedItemHeight = double.IsNaN(this.ItemHeight) == false && DoubleUtil.AreClose(this.ItemHeight, 0) == false;
var maxItemHeight = useDefinedItemHeight
? this.ItemHeight
: constraint.Height / this.rows;
var childConstraint = new Size(maxItemWidth, maxItemHeight);
var maxChildDesiredWidth = 0.0;
var maxChildDesiredHeight = 0.0;
// Measure each child, keeping track of maximum desired width and height.
for (int i = 0, count = this.InternalChildren.Count; i < count; ++i)
{
var child = this.InternalChildren[i];
// Measure the child.
child.Measure(childConstraint);
var childDesiredSize = child.DesiredSize;
if (useDefinedItemWidth)
{
maxChildDesiredWidth = this.ItemWidth;
}
else
{
if (maxChildDesiredWidth < childDesiredSize.Width)
{
maxChildDesiredWidth = childDesiredSize.Width;
}
}
if (useDefinedItemHeight)
{
maxChildDesiredHeight = this.ItemHeight;
}
else
{
if (maxChildDesiredHeight < childDesiredSize.Height)
{
maxChildDesiredHeight = childDesiredSize.Height;
}
}
}
if (double.IsNaN(constraint.Width) == false
&& double.IsPositiveInfinity(constraint.Width) == false)
{
var precomputedColumns = this.MaxColumns != 0
? Math.Min(this.MaxColumns, (int)(constraint.Width / maxChildDesiredWidth))
: (int)(constraint.Width / maxChildDesiredWidth);
this.UpdateComputedValues(precomputedColumns);
}
return new Size(maxChildDesiredWidth * this.usedColumns, maxChildDesiredHeight * this.rows);
}
/// <summary>
/// Arrange the children of this UniformGrid by distributing space evenly
/// among all of the children, making each child the size equal to a cell's
/// portion of the given arrangeSize (e.g. for a 2 x 4 grid, the child size
/// would be arrangeSize*0.5 x arrangeSize*0.25)
/// </summary>
/// <param name="arrangeSize">Arrange size</param>
protected override Size ArrangeOverride(Size arrangeSize)
{
var childBounds = new Rect(0, 0, arrangeSize.Width / this.usedColumns, arrangeSize.Height / this.rows);
var xStep = childBounds.Width;
var xBound = arrangeSize.Width;
// Arrange and Position each child to the same cell size
foreach (UIElement child in this.InternalChildren)
{
child.Arrange(childBounds);
// only advance to the next grid cell if the child was not collapsed
if (child.Visibility != Visibility.Collapsed)
{
childBounds.X += xStep;
if (childBounds.X >= xBound)
{
childBounds.Y += childBounds.Height;
childBounds.X = 0;
}
}
}
return arrangeSize;
}
/// <summary>
/// If either Rows or Columns are set to 0, then dynamically compute these
/// values based on the actual number of non-collapsed children.
///
/// In the case when both Rows and Columns are set to 0, then make Rows
/// and Columns be equal, thus laying out in a square grid.
/// </summary>
private void UpdateComputedValues(int precomputedColumns = 0)
{
this.columns = precomputedColumns;
this.rows = 0;
this.nonCollapsedCount = 0;
// First compute the actual # of non-collapsed children to be laid out
for (int i = 0, count = this.InternalChildren.Count; i < count; ++i)
{
var child = this.InternalChildren[i];
if (child.Visibility != Visibility.Collapsed)
{
this.nonCollapsedCount++;
}
}
// to ensure that we have at leat one row & column, make sure
// that nonCollapsedCount is at least 1
if (this.nonCollapsedCount == 0)
{
this.nonCollapsedCount = 1;
}
if (this.columns == 0)
{
// columns are unset -- lay out in a square
this.columns = (int)Math.Sqrt(this.nonCollapsedCount);
if (this.columns * this.columns < this.nonCollapsedCount)
{
this.columns++;
}
}
if (this.MinColumns != 0
&& this.columns < this.MinColumns)
{
this.columns = this.MinColumns;
}
if (this.MaxColumns != 0
&& this.columns > this.MaxColumns)
{
this.columns = this.MaxColumns;
}
this.columns = Math.Max(1, this.columns);
// -1 because we ensured that nonCollapsedCount is at least 1
this.rows = (this.nonCollapsedCount + (this.columns - 1)) / this.columns;
this.rows = Math.Max(1, this.rows);
this.usedColumns = Math.Min(this.columns, this.nonCollapsedCount);
}
}
}

View File

@@ -0,0 +1,49 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Fluent.Helpers;
using Fluent.Internal.KnownBoxes;
/// <summary>
/// Helper control which enables easy embedding of window steering functions.
/// </summary>
public class WindowSteeringHelperControl : Border
{
/// <summary>
/// Static constructor
/// </summary>
static WindowSteeringHelperControl()
{
BackgroundProperty.OverrideMetadata(typeof(WindowSteeringHelperControl), new FrameworkPropertyMetadata(Brushes.Transparent));
IsHitTestVisibleProperty.OverrideMetadata(typeof(WindowSteeringHelperControl), new FrameworkPropertyMetadata(BooleanBoxes.TrueBox));
HorizontalAlignmentProperty.OverrideMetadata(typeof(WindowSteeringHelperControl), new FrameworkPropertyMetadata(HorizontalAlignment.Stretch));
VerticalAlignmentProperty.OverrideMetadata(typeof(WindowSteeringHelperControl), new FrameworkPropertyMetadata(VerticalAlignment.Stretch));
}
/// <inheritdoc />
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
if (this.IsEnabled)
{
WindowSteeringHelper.HandleMouseLeftButtonDown(e, true, true);
}
}
/// <inheritdoc />
protected override void OnMouseRightButtonUp(MouseButtonEventArgs e)
{
base.OnMouseRightButtonUp(e);
if (this.IsEnabled)
{
WindowSteeringHelper.ShowSystemMenu(this, e);
}
}
}
}

View File

@@ -0,0 +1,34 @@
namespace Fluent.Converters
{
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
/// <summary>
/// Extracts right content presenter of application menu converter
/// </summary>
public class ApplicationMenuRightScrollViewerExtractorConverter : IValueConverter
{
#region Implementation of IValueConverter
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ApplicationMenu menu)
{
return menu.Template.FindName("PART_ScrollViewer", menu) as UIElement;
}
return value;
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
#endregion
}
}

View File

@@ -0,0 +1,43 @@
namespace Fluent.Converters
{
using System;
using System.Windows.Data;
using System.Windows.Media;
/// <summary>
/// Converts <see cref="Color"/> to a <see cref="SolidColorBrush"/> and back.
/// </summary>
[ValueConversion(typeof(Color), typeof(SolidColorBrush))]
public class ColorToSolidColorBrushValueConverter : IValueConverter
{
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
switch (value)
{
case null:
return null;
case Color _:
return new SolidColorBrush((Color)value);
}
throw new InvalidOperationException($"Unsupported type [{value.GetType().Name}], ColorToSolidColorBrushValueConverter.Convert()");
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
switch (value)
{
case null:
return null;
case SolidColorBrush brush:
return brush.Color;
}
throw new InvalidOperationException($"Unsupported type [{value.GetType().Name}], ColorToSolidColorBrushValueConverter.Convert()");
}
}
}

View File

@@ -0,0 +1,37 @@
namespace Fluent.Converters
{
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
/// <summary>
/// Checks equality of value and the converter parameter.
/// Returns <see cref="Visibility.Visible"/> if they are equal.
/// Returns <see cref="Visibility.Collapsed"/> if they are NOT equal.
/// </summary>
public class EqualsToVisibilityConverter : IValueConverter
{
#region Implementation of IValueConverter
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == parameter
|| (value != null && value.Equals(parameter)))
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
#endregion
}
}

View File

@@ -0,0 +1,162 @@
// ReSharper disable once CheckNamespace
namespace Fluent
{
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using ControlzEx.Standard;
using Fluent.Converters;
/// <summary>
/// Icon converter provides window or application default icon if user-defined is not present.
/// </summary>
[ValueConversion(sourceType: typeof(string), targetType: typeof(Image))]
[ValueConversion(sourceType: typeof(Uri), targetType: typeof(Image))]
[ValueConversion(sourceType: typeof(System.Drawing.Icon), targetType: typeof(Image))]
[ValueConversion(sourceType: typeof(ImageSource), targetType: typeof(Image))]
[ValueConversion(sourceType: typeof(string), targetType: typeof(ImageSource))]
[ValueConversion(sourceType: typeof(Uri), targetType: typeof(ImageSource))]
[ValueConversion(sourceType: typeof(System.Drawing.Icon), targetType: typeof(ImageSource))]
[ValueConversion(sourceType: typeof(ImageSource), targetType: typeof(ImageSource))]
public sealed class IconConverter : ObjectToImageConverter
{
/// <summary>
/// Creates a new instance.
/// </summary>
/// <param name="iconBinding">The binding to which the converter should be applied to.</param>
public IconConverter(Binding iconBinding)
: base(iconBinding, new Size(SystemParameters.SmallIconWidth, SystemParameters.SmallIconHeight))
{
}
/// <summary>
/// Creates a new instance.
/// </summary>
/// <param name="iconBinding">The binding to which the converter should be applied to.</param>
/// <param name="targetVisualBinding">The target visual on which the image/icon should be shown.</param>
public IconConverter(Binding iconBinding, Binding targetVisualBinding)
: base(iconBinding, new Size(SystemParameters.SmallIconWidth, SystemParameters.SmallIconHeight), targetVisualBinding)
{
}
/// <summary>
/// Creates a new instance.
/// </summary>
/// <param name="iconBinding">The binding to which the converter should be applied to.</param>
/// <param name="desiredSize">The desired size for the image.</param>
/// <param name="targetVisualBinding">The target visual on which the image/icon should be shown.</param>
public IconConverter(Binding iconBinding, object desiredSize, Binding targetVisualBinding)
: base(iconBinding, desiredSize, targetVisualBinding)
{
}
/// <inheritdoc />
protected override object GetValueToConvert(object value, Size desiredSize, Visual targetVisual)
{
if (value is null)
{
var defaultIcon = GetDefaultIcon(targetVisual, desiredSize);
if (defaultIcon != null)
{
return defaultIcon;
}
}
return base.GetValueToConvert(value, desiredSize, targetVisual);
}
private static ImageSource GetDefaultIcon(DependencyObject targetVisual, Size desiredSize)
{
IntPtr windowHandle;
if (targetVisual != null)
{
var window = Window.GetWindow(targetVisual);
if (window != null
&& (windowHandle = new WindowInteropHelper(window).Handle) != IntPtr.Zero)
{
return GetDefaultIcon(windowHandle, desiredSize);
}
}
if (Application.Current != null
&& Application.Current.CheckAccess()
&& Application.Current.MainWindow != null
&& Application.Current.MainWindow.CheckAccess()
&& (windowHandle = new WindowInteropHelper(Application.Current.MainWindow).Handle) != IntPtr.Zero)
{
return GetDefaultIcon(windowHandle, desiredSize);
}
using (var p = Process.GetCurrentProcess())
{
if (p.MainWindowHandle != IntPtr.Zero)
{
return GetDefaultIcon(p.MainWindowHandle, desiredSize);
}
}
return null;
}
private static ImageSource GetDefaultIcon(IntPtr hwnd, Size desiredSize)
{
#pragma warning disable CS0219 // Variable is assigned but its value is never used
// Retrieve the small icon for the window.
const int ICON_SMALL = 0;
// Retrieve the large icon for the window.
const int ICON_BIG = 1;
// Retrieves the small icon provided by the application. If the application does not provide one, the system uses the system-generated icon for that window.
const int ICON_SMALL2 = 2;
// Shares the image handle if the image is loaded multiple times. If LR_SHARED is not set, a second call to LoadImage for the same resource will load the image again and return a different handle.
const int LR_SHARED = 0x00008000;
const int IDI_APPLICATION = 0x7f00;
#pragma warning restore CS0219 // Variable is assigned but its value is never used
try
{
#pragma warning disable 618
var iconPtr = IntPtr.Zero;
if (hwnd != IntPtr.Zero)
{
iconPtr = NativeMethods.SendMessage(hwnd, WM.GETICON, new IntPtr(ICON_SMALL2), IntPtr.Zero);
if (iconPtr == IntPtr.Zero)
{
iconPtr = NativeMethods.GetClassLong(hwnd, GCLP.HICONSM);
}
}
if (iconPtr == IntPtr.Zero)
{
iconPtr = NativeMethods.LoadImage(IntPtr.Zero, new IntPtr(IDI_APPLICATION), 1, (int)desiredSize.Width, (int)desiredSize.Height, LR_SHARED);
}
if (iconPtr != IntPtr.Zero)
{
var bitmapFrame = BitmapFrame.Create(Imaging.CreateBitmapSourceFromHIcon(iconPtr, Int32Rect.Empty, BitmapSizeOptions.FromWidthAndHeight((int)desiredSize.Width, (int)desiredSize.Height)));
return bitmapFrame;
}
#pragma warning restore 618
}
catch
{
// ignored
}
return null;
}
}
}

View File

@@ -0,0 +1,52 @@
namespace Fluent.Converters
{
using System;
using System.Globalization;
using System.Windows.Data;
/// <summary>
/// Used to invert numbers
/// </summary>
public class InvertNumericConverter : IValueConverter
{
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
{
if (value is float numericValue)
{
return numericValue * -1;
}
}
{
if (value is double numericValue)
{
return numericValue * -1;
}
}
{
if (value is int numericValue)
{
return numericValue * -1;
}
}
{
if (value is long numericValue)
{
return numericValue * -1;
}
}
return value;
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return this.Convert(value, targetType, parameter, culture);
}
}
}

View File

@@ -0,0 +1,31 @@
namespace Fluent.Converters
{
using System;
using System.Globalization;
using System.Windows.Data;
/// <summary>
/// Converts <c>null</c> to <c>true</c> and not <c>null</c> to <c>false</c>.
/// </summary>
public sealed class IsNullConverter : IValueConverter
{
private static IsNullConverter instance;
/// <summary>
/// A singleton instance for <see cref="IsNullConverter" />.
/// </summary>
public static IsNullConverter Instance => instance ?? (instance = new IsNullConverter());
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is null;
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}
}

View File

@@ -0,0 +1,577 @@
namespace Fluent.Converters
{
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Interop;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
#if NET452 // for DpiScale
using ControlzEx.Standard;
#endif
using Fluent.Internal;
#pragma warning disable WPF0072
/// <summary>
/// Converts <see cref="string"/>, <see cref="Uri"/>, <see cref="System.Drawing.Icon"/> or <see cref="ImageSource"/> to <see cref="System.Windows.Controls.Image"/> or <see cref="ImageSource"/> (dependent upon target type).
/// When converting you can pass a desired size as the converter parameter.
/// The returned <see cref="ImageSource"/> will be the closest <see cref="BitmapFrame"/> found in the provided image.
/// </summary>
/// <remarks>
/// - This converter is also a <see cref="MarkupExtension"/> to be able to extract the <see cref="DpiScale"/> from the target control.
/// - This converter is also a <see cref="IMultiValueConverter"/>. The order for parameters/values is:
/// 0 = value to convert
/// 1 = visual target or desired size
/// 2 = desired size
/// Index checks are applied during value extraction, so providing null or just the value to convert are considered valid.
/// </remarks>
[MarkupExtensionReturnType(typeof(ImageSource))]
[ValueConversion(sourceType: typeof(string), targetType: typeof(Image))]
[ValueConversion(sourceType: typeof(Uri), targetType: typeof(Image))]
[ValueConversion(sourceType: typeof(System.Drawing.Icon), targetType: typeof(Image))]
[ValueConversion(sourceType: typeof(ImageSource), targetType: typeof(Image))]
[ValueConversion(sourceType: typeof(string), targetType: typeof(ImageSource))]
[ValueConversion(sourceType: typeof(Uri), targetType: typeof(ImageSource))]
[ValueConversion(sourceType: typeof(System.Drawing.Icon), targetType: typeof(ImageSource))]
[ValueConversion(sourceType: typeof(ImageSource), targetType: typeof(ImageSource))]
public class ObjectToImageConverter : MarkupExtension, IValueConverter, IMultiValueConverter
{
private static readonly ImageSource imageNotFoundImageSource = (ImageSource)CreateImageNotFoundImageSource().GetAsFrozen();
private static readonly SizeConverter sizeConverter = new SizeConverter();
/// <summary>
/// Creates a new instance.
/// </summary>
public ObjectToImageConverter()
{
}
/// <summary>
/// Creates a new instance.
/// </summary>
/// <param name="input">The object or binding to which the converter should be applied to.</param>
public ObjectToImageConverter(object input)
: this(input, Size.Empty, null)
{
}
/// <summary>
/// Creates a new instance.
/// </summary>
/// <param name="input">The object or binding to which the converter should be applied to.</param>
/// <param name="desiredSize">The desired size for the image.</param>
public ObjectToImageConverter(object input, Size desiredSize)
: this(input, desiredSize, null)
{
}
/// <summary>
/// Creates a new instance.
/// </summary>
/// <param name="input">The object or binding to which the converter should be applied to.</param>
/// <param name="desiredSize">The desired size for the image.</param>
public ObjectToImageConverter(object input, object desiredSize)
: this(input, desiredSize, null)
{
}
/// <summary>
/// Creates a new instance.
/// </summary>
/// <param name="input">The object or binding to which the converter should be applied to.</param>
/// <param name="desiredSize">The desired size for the image.</param>
/// <param name="targetVisualBinding">The target visual on which the image/icon should be shown.</param>
public ObjectToImageConverter(object input, object desiredSize, Binding targetVisualBinding)
{
if (desiredSize is Size desiredSizeValue
&& (desiredSizeValue.IsEmpty
|| DoubleUtil.AreClose(desiredSizeValue.Width, 0)
|| DoubleUtil.AreClose(desiredSizeValue.Height, 0)))
{
throw new ArgumentException("DesiredSize must not be empty and width/height must be greater than 0.", nameof(desiredSize));
}
this.IconBinding = input as Binding ?? new Binding { Source = input };
this.DesiredSizeBinding = desiredSize as Binding ?? new Binding { Source = desiredSize };
this.TargetVisualBinding = targetVisualBinding;
}
/// <summary>
/// The target visual on which the image/icon should be shown.
/// </summary>
[ConstructorArgument("targetVisualBinding")]
public Binding TargetVisualBinding { get; set; }
/// <summary>
/// The binding to which the converter should be applied to.
/// </summary>
[ConstructorArgument("iconBinding")]
public Binding IconBinding { get; set; }
/// <summary>
/// The binding for the desired size for the image.
/// </summary>
[ConstructorArgument("desiredSize")]
public Binding DesiredSizeBinding { get; set; }
#region Implementation of IValueConverter
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var desiredSize = Size.Empty;
if (parameter is double
|| parameter is int
|| parameter is string)
{
var size = System.Convert.ToDouble(parameter);
desiredSize = new Size(size, size);
}
else if (parameter is Size size)
{
desiredSize = size;
}
return this.Convert(value, null, desiredSize, targetType);
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
/// <summary>
/// Returns the value to convert.
/// </summary>
protected virtual object GetValueToConvert(object value, Size desiredSize, Visual targetVisual)
{
return value;
}
#endregion
#region Implementation of IMultiValueConverter
/// <inheritdoc />
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var desiredSize = Size.Empty;
// TargetVisual can be at index 1 or index 2, depending on the number of values passed
var targetVisual = values.Length == 2
? values[1] as Visual
: null;
if (targetVisual is null)
{
targetVisual = values.Length == 3
? values[2] as Visual
: null;
}
if (values.Length >= 2
&& values[1] is Size desiredSizeFromIndex1)
{
desiredSize = desiredSizeFromIndex1;
}
else if (values.Length >= 2
&& (values[1] is Visual) == false)
{
var possibleDesiredSizeValue = values[1];
object convertedValue;
if (sizeConverter.CanConvertFrom(possibleDesiredSizeValue.GetType())
&& (convertedValue = sizeConverter.ConvertFrom(possibleDesiredSizeValue)) != null)
{
desiredSize = (Size)convertedValue;
}
else
{
desiredSize = Size.Empty;
}
}
//if (desiredSize.IsEmpty
// && targetVisual != null
// && targetVisual is FrameworkElement targetFrameworkElement
// && DoubleHelper.IsFinite(targetFrameworkElement.Width)
// && DoubleHelper.IsFinite(targetFrameworkElement.Height))
//{
// desiredSize = new Size(targetFrameworkElement.Width, targetFrameworkElement.Height);
//}
return this.Convert(values[0], targetVisual, desiredSize, targetType);
}
/// <inheritdoc />
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion Implementation of IMultiValueConverter
#region Implementation of MarkupExtension
/// <inheritdoc />
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this.CreateMultiBinding(serviceProvider);
}
#endregion Implementation of MarkupExtension
private object CreateMultiBinding(IServiceProvider serviceProvider)
{
var multiBinding = new MultiBinding
{
Converter = this
};
multiBinding.Bindings.Add(this.IconBinding);
if (this.DesiredSizeBinding != null)
{
multiBinding.Bindings.Add(this.DesiredSizeBinding);
}
if (this.TargetVisualBinding != null)
{
multiBinding.Bindings.Add(this.TargetVisualBinding);
}
return multiBinding.ProvideValue(serviceProvider);
}
private object Convert(object value, Visual targetVisual, Size desiredSize, Type targetType)
{
var imageSource = CreateFrozenImageSource(this.GetValueToConvert(value, desiredSize, targetVisual), targetVisual, desiredSize);
if (imageSource is null)
{
//if (value is FrameworkElement element)
//{
// element.VerticalAlignment = VerticalAlignment.Center;
// element.HorizontalAlignment = HorizontalAlignment.Center;
//}
return value;
}
if (typeof(ImageSource).IsAssignableFrom(targetType))
{
return imageSource;
}
var image = new Image
{
Source = imageSource,
Stretch = Stretch.Uniform,
};
if (desiredSize.IsEmpty == false)
{
image.Width = desiredSize.Width;
image.Height = desiredSize.Height;
}
return image;
}
/// <summary>
/// Extracts an <see cref="ImageSource"/> from <paramref name="value"/> which closest matches the <paramref name="desiredSize"/>.
/// </summary>
/// <param name="value">Value from which the <see cref="ImageSource"/> should be extracted. It can be of type <see cref="ImageSource"/></param>
/// <param name="desiredSize">The desired size to extract from <paramref name="value"/> .</param>
/// <returns>An frozen <see cref="ImageSource"/> which closest matches <paramref name="desiredSize"/></returns>
public static ImageSource CreateFrozenImageSource(object value, Size desiredSize)
{
// We have to use a frozen instance. Otherwise we run into trouble if the same instance is used in multiple locations.
// In case of BitmapImage it even gets worse when using the same Uri...
return GetAsFrozenIfPossible(CreateImageSource(value, desiredSize));
}
/// <summary>
/// Extracts an <see cref="ImageSource"/> from <paramref name="value"/> which closest matches the <paramref name="desiredSize"/>.
/// </summary>
/// <param name="value">Value from which the <see cref="ImageSource"/> should be extracted. It can be of type <see cref="ImageSource"/></param>
/// <param name="targetVisual">The target on which the <see cref="ImageSource"/> will be used.</param>
/// <param name="desiredSize">The desired size to extract from <paramref name="value"/> .</param>
/// <returns>An frozen <see cref="ImageSource"/> which closest matches <paramref name="desiredSize"/></returns>
public static ImageSource CreateFrozenImageSource(object value, Visual targetVisual, Size desiredSize)
{
// We have to use a frozen instance. Otherwise we run into trouble if the same instance is used in multiple locations.
// In case of BitmapImage it even gets worse when using the same Uri...
return GetAsFrozenIfPossible(CreateImageSource(value, targetVisual, desiredSize));
}
/// <summary>
/// Extracts an <see cref="ImageSource"/> from <paramref name="value"/> which closest matches the <paramref name="desiredSize"/>.
/// </summary>
/// <param name="value">Value from which the <see cref="ImageSource"/> should be extracted. It can be of type <see cref="ImageSource"/></param>
/// <param name="desiredSize">The desired size to extract from <paramref name="value"/> .</param>
/// <returns>An <see cref="ImageSource"/> which closest matches <paramref name="desiredSize"/></returns>
public static ImageSource CreateImageSource(object value, Size desiredSize)
{
return CreateImageSource(value, null, desiredSize);
}
/// <summary>
/// Extracts an <see cref="ImageSource"/> from <paramref name="value"/> which closest matches the <paramref name="desiredSize"/>.
/// </summary>
/// <param name="value">Value from which the <see cref="ImageSource"/> should be extracted. It can be of type <see cref="ImageSource"/></param>
/// /// <param name="targetVisual">The target on which the <see cref="ImageSource"/> will be used.</param>
/// <param name="desiredSize">The desired size to extract from <paramref name="value"/> .</param>
/// <returns>An <see cref="ImageSource"/> which closest matches <paramref name="desiredSize"/></returns>
public static ImageSource CreateImageSource(object value, Visual targetVisual, Size desiredSize)
{
if (value is null)
{
return null;
}
if (desiredSize == default
|| DoubleUtil.AreClose(desiredSize.Width, 0)
|| DoubleUtil.AreClose(desiredSize.Height, 0))
{
desiredSize = Size.Empty;
}
if (value is ImageSource imageSource)
{
return ExtractImageSource(imageSource, targetVisual, desiredSize);
}
if (value is string imagePath)
{
return CreateImageSource(imagePath, targetVisual, desiredSize);
}
if (value is Uri imageUri)
{
return CreateImageSource(imageUri, targetVisual, desiredSize);
}
if (value is System.Drawing.Icon icon)
{
return ExtractImageSource(icon, targetVisual, desiredSize);
}
// !!! Danger zone ahead !!!
// !!! Please do not copy that code somewhere and blame me for failures !!!
// Hack to get the value from resource expressions
{
if (targetVisual is null == false // to get values for resource expressions we need a DependencyObject
&& value is Expression expression)
{
var type = expression.GetType();
var method = type.GetMethod("GetValue", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (method != null)
{
var valueFromExpression = method.Invoke(expression, new object[]
{
targetVisual,
// to get values from resource expressions we need a DependencyProperty, so just pass a random one
RibbonProperties.SizeProperty
});
return CreateImageSource(valueFromExpression, targetVisual, desiredSize);
}
}
if (value.GetType().InheritsFrom("DeferredReference"))
{
var type = value.GetType();
var method = type.GetMethod("GetValue", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (method != null)
{
var valueFromDeferredReference = method.Invoke(value, new object[]
{
null
});
return CreateImageSource(valueFromDeferredReference, targetVisual, desiredSize);
}
}
}
return null;
}
private static ImageSource CreateImageSource(string imagePath, Visual targetVisual, Size desiredSize)
{
// Allow things like "Images\Green.png"
if (imagePath.StartsWith("pack:", StringComparison.OrdinalIgnoreCase) == false)
{
// If that file does not exist, try to find it using resource notation
if (File.Exists(imagePath) == false)
{
var slash = string.Empty;
if (imagePath.StartsWith("/", StringComparison.OrdinalIgnoreCase) == false)
{
slash = "/";
}
imagePath = "pack://application:,,," + slash + imagePath;
}
}
var imageUri = new Uri(imagePath, UriKind.RelativeOrAbsolute);
return CreateImageSource(imageUri, targetVisual, desiredSize);
}
private static ImageSource CreateImageSource(Uri imageUri, Visual targetVisual, Size desiredSize)
{
try
{
var decoder = BitmapDecoder.Create(imageUri, BitmapCreateOptions.None, BitmapCacheOption.Default);
return ExtractImageSource(decoder, targetVisual, desiredSize);
}
catch (IOException exception) when (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{
Trace.WriteLine(exception);
return imageNotFoundImageSource;
}
}
private static ImageSource ExtractImageSource(System.Drawing.Icon icon, Visual targetVisual, Size desiredSize)
{
var imageSource = Imaging.CreateBitmapSourceFromHIcon(icon.Handle,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
return ExtractImageSource(imageSource, targetVisual, desiredSize);
}
private static ImageSource ExtractImageSource(ImageSource imageSource, Visual targetVisual, Size desiredSize)
{
if (desiredSize.IsEmpty)
{
return imageSource;
}
var bitmapFrame = imageSource as BitmapFrame;
// We may have some other type of ImageSource
// that doesn't have a notion of frames or decoder
if (bitmapFrame?.Decoder is null)
{
return imageSource;
}
return ExtractImageSource(bitmapFrame.Decoder, targetVisual, desiredSize);
}
private static ImageSource ExtractImageSource(BitmapDecoder decoder, Visual targetVisual, Size desiredSize)
{
var scaledDesiredSize = GetScaledDesiredSize(desiredSize, targetVisual);
var framesOrderedByWidth = decoder.Frames
.OrderBy(f => f.Width)
.ThenBy(f => f.Height)
.ToList();
// if there is no matching frame, get the largest frame
return framesOrderedByWidth
.FirstOrDefault(f => f.Width >= scaledDesiredSize.Width
&& f.Height >= scaledDesiredSize.Height)
?? framesOrderedByWidth.Last();
}
/// <summary>
/// Get the scaled desired size.
/// </summary>
protected static Size GetScaledDesiredSize(Size desiredSize, Visual targetVisual)
{
return GetScaledDesiredSize(desiredSize, GetDpiScale(targetVisual));
}
/// <summary>
/// Get the scaled desired size.
/// </summary>
private static Size GetScaledDesiredSize(Size desiredSize, DpiScale dpiScale)
{
if (desiredSize.IsEmpty)
{
return desiredSize;
}
return new Size(desiredSize.Width * dpiScale.DpiScaleX, desiredSize.Height * dpiScale.DpiScaleY);
}
private static DpiScale GetDpiScale(Visual targetVisual)
{
#if !NET452 // VisualTreeHelper.GetDpi is not supported on .NET 4.5
if (targetVisual != null)
{
return VisualTreeHelper.GetDpi(targetVisual);
}
#endif
if (Application.Current?.CheckAccess() == true
&& Application.Current.MainWindow?.CheckAccess() == true)
{
var presentationSource = PresentationSource.FromVisual(Application.Current.MainWindow);
if (presentationSource?.CompositionTarget != null)
{
// dpi.M11 = dpiX, dpi.M22 = dpiY
return new DpiScale(presentationSource.CompositionTarget.TransformToDevice.M11, presentationSource.CompositionTarget.TransformToDevice.M22);
}
}
return new DpiScale(1, 1);
}
private static ImageSource CreateImageNotFoundImageSource()
{
var drawingGroup = new DrawingGroup
{
ClipGeometry = Geometry.Parse("M0,0 V426,667 H426,667 V0 H0 Z")
};
var geometryDrawing = new GeometryDrawing(Brushes.Red, new Pen(), Geometry.Parse("F1 M426.667,426.667z M0,0z M213.333,0C95.514,0 0,95.514 0,213.333 0,331.152 95.514,426.666 213.333,426.666 331.152,426.666 426.666,331.152 426.666,213.333 426.666,95.514 331.153,0 213.333,0z M330.995,276.689L276.693,330.995 213.333,267.639 149.973,330.999 95.671,276.689 159.027,213.333 95.671,149.973 149.973,95.671 213.333,159.027 276.693,95.671 330.995,149.973 267.639,213.333 330.995,276.689z"));
using (drawingGroup.Append())
{
drawingGroup.Children.Add(geometryDrawing);
}
var image = new DrawingImage
{
Drawing = drawingGroup
};
return image;
}
private static ImageSource GetAsFrozenIfPossible(ImageSource imageSource)
{
if (imageSource is null)
{
return null;
}
if (imageSource.CanFreeze)
{
return (ImageSource)imageSource.GetAsFrozen();
}
return imageSource;
}
}
}

View File

@@ -0,0 +1,24 @@
namespace Fluent.Converters
{
using System;
using System.ComponentModel;
using System.Globalization;
/// <summary>
/// Class which enables conversion from <see cref="string"/> to <see cref="RibbonControlSizeDefinition"/>
/// </summary>
public class SizeDefinitionConverter : TypeConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType.IsAssignableFrom(typeof(string));
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return new RibbonControlSizeDefinition(value as string);
}
}
}

View File

@@ -0,0 +1,73 @@
namespace Fluent.Converters
{
using System;
using System.Globalization;
using System.Text;
using System.Windows.Data;
/// <summary>
/// Converter class which converts from <see cref="string"/> to <see cref="double"/> and back.
/// </summary>
public class SpinnerTextToValueConverter : IValueConverter
{
/// <summary>
/// Gets a default instance of <see cref="SpinnerTextToValueConverter"/>.
/// </summary>
public static readonly SpinnerTextToValueConverter DefaultInstance = new SpinnerTextToValueConverter();
/// <inheritdoc />
public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var converterParam = (Tuple<string, double>)parameter;
var format = converterParam.Item1;
var previousValue = converterParam.Item2;
return this.TextToDouble((string)value, format, previousValue, culture);
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return this.DoubleToText((double)value, (string)parameter, culture);
}
/// <summary>
/// Converts the given <paramref name="text"/> to a <see cref="double"/>.
/// </summary>
/// <returns>The <see cref="double"/> value converted from <paramref name="text"/> or <paramref name="previousValue"/> if the conversion fails.</returns>
public virtual double TextToDouble(string text, string format, double previousValue, CultureInfo culture)
{
// Remove all except digits, signs and commas
var stringBuilder = new StringBuilder();
foreach (var symbol in text)
{
if (char.IsDigit(symbol)
|| symbol == ','
|| symbol == '.'
|| (symbol == '-' && stringBuilder.Length == 0))
{
stringBuilder.Append(symbol);
}
}
text = stringBuilder.ToString();
if (double.TryParse(text, NumberStyles.Any, culture, out var doubleValue) == false)
{
doubleValue = previousValue;
}
return doubleValue;
}
/// <summary>
/// Converts <paramref name="value"/> to a formatted text using <paramref name="format"/>.
/// </summary>
/// <returns><paramref name="value"/> converted to a <see cref="string"/>.</returns>
public virtual string DoubleToText(double value, string format, CultureInfo culture)
{
return value.ToString(format, culture);
}
}
}

Some files were not shown because too many files have changed in this diff Show More