Skip to content

Commit

Permalink
Fixes #244 by also using the visual tree, for specific controls, when…
Browse files Browse the repository at this point in the history
… looking for key tips and by using some kind of DoEvents when switching ribbon tabs through key tips
  • Loading branch information
batzen committed Jan 15, 2016
1 parent 9a1b2bd commit 7b74fde
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 29 deletions.
2 changes: 2 additions & 0 deletions Fluent.Ribbon.Showcase/Fluent.Ribbon.Showcase.NET 4.0.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
<Compile Include="RibbonWindowWithoutVisibileRibbon.xaml.cs">
<DependentUpon>RibbonWindowWithoutVisibileRibbon.xaml</DependentUpon>
</Compile>
<Compile Include="TemplateSelectors\DynamicTemplateSelector.cs" />
<Compile Include="Helpers\TemplateCollection.cs" />
<Compile Include="TestContent.xaml.cs">
<DependentUpon>TestContent.xaml</DependentUpon>
</Compile>
Expand Down
2 changes: 2 additions & 0 deletions Fluent.Ribbon.Showcase/Fluent.Ribbon.Showcase.NET 4.5.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@
<Compile Include="RibbonWindowWithoutVisibileRibbon.xaml.cs">
<DependentUpon>RibbonWindowWithoutVisibileRibbon.xaml</DependentUpon>
</Compile>
<Compile Include="TemplateSelectors\DynamicTemplateSelector.cs" />
<Compile Include="Helpers\TemplateCollection.cs" />
<Compile Include="TestContent.xaml.cs">
<DependentUpon>TestContent.xaml</DependentUpon>
</Compile>
Expand Down
10 changes: 10 additions & 0 deletions Fluent.Ribbon.Showcase/Helpers/TemplateCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace FluentTest.Helpers
{
using System.Collections.Generic;
using System.Windows;

/// <summary> Holds a collection of <see cref="DataTemplate"/> items for application as a control's DataTemplate. </summary>
public class TemplateCollection : List<DataTemplate>
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
namespace FluentTest.TemplateSelectors
{
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Helpers;

/// <summary>
/// Provides a means to specify DataTemplates to be selected from within WPF code
/// </summary>
public class DynamicTemplateSelector : DataTemplateSelector
{
/// <summary> Generic attached property specifying <see cref="DataTemplate"/>s used by the <see cref="DynamicTemplateSelector"/></summary>
/// <remarks>
/// This attached property will allow you to set the templates you wish to be available whenever
/// a control's TemplateSelector is set to an instance of <see cref="DynamicTemplateSelector"/>
/// </remarks>
public static readonly DependencyProperty TemplatesCollectionProperty =
DependencyProperty.RegisterAttached("TemplatesCollection", typeof(TemplateCollection),
typeof(DynamicTemplateSelector),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.OverridesInheritanceBehavior));

/// <summary> Gets the value of the <paramref name="target"/>'s attached <see cref="TemplatesCollectionProperty"/> </summary>
/// <param name="target">The <see cref="UIElement"/> who's attached template's property you wish to retrieve</param>
/// <returns>The templates used by the givem <paramref name="target"/> when using the <see cref="DynamicTemplateSelector"/></returns>
[AttachedPropertyBrowsableForType(typeof(ContentControl))]
public static TemplateCollection GetTemplatesCollection(DependencyObject target)
{
return (TemplateCollection)target.GetValue(TemplatesCollectionProperty);
}

/// <summary> Sets the value of the <paramref name="target"/>'s attached <see cref="TemplatesCollectionProperty"/> </summary>
/// <param name="target">The element to set the property on</param>
/// <param name="value">The collection of <see cref="DataTemplate"/>s to apply to this element</param>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public static void SetTemplatesCollection(DependencyObject target, TemplateCollection value)
{
target.SetValue(TemplatesCollectionProperty, value);
}

/// <summary>
/// Overriden base method to allow the selection of the correct DataTemplate
/// </summary>
/// <param name="item">The item for which the template should be retrieved</param>
/// <param name="container">The object containing the current item</param>
/// <returns>The <see cref="DataTemplate"/> to use when rendering the <paramref name="item"/></returns>
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
//This should ensure that the item we are getting is in fact capable of holding our property
//before we attempt to retrieve it.
if (container != null)
{
var templates = GetTemplatesCollection(container);

if (templates != null
&& templates.Count != 0)
{
foreach (var template in templates)
{
//In this case, we are checking whether the type of the item
//is the same as the type supported by our DataTemplate
if (template.DataType is Type
&& ((Type)template.DataType).IsInstanceOfType(item))
{
//And if it is, then we return that DataTemplate
return template;
}
}
}
}

//If all else fails, then we go back to using the default DataTemplate
return new DataTemplate();
}
}
}
20 changes: 20 additions & 0 deletions Fluent.Ribbon.Showcase/TestContent.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:Fluent="urn:fluent-ribbon"
xmlns:TemplateSelectors="clr-namespace:FluentTest.TemplateSelectors"
xmlns:Helpers="clr-namespace:FluentTest.Helpers"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:viewModels="clr-namespace:FluentTest.ViewModels"
mc:Ignorable="d"
Expand Down Expand Up @@ -2347,6 +2349,24 @@
IsChecked="False" />
</Fluent:RibbonGroupBox>

<Fluent:RibbonGroupBox Header="KeyTips on templated items">
<Fluent:RibbonGroupBox.Resources>
<TemplateSelectors:DynamicTemplateSelector x:Key="DynamicTemplateSelector" />
<System:Int32 x:Key="TemplateTestContent">0</System:Int32>
</Fluent:RibbonGroupBox.Resources>
<ContentPresenter ContentTemplateSelector="{StaticResource DynamicTemplateSelector}"
Content="{StaticResource TemplateTestContent}">
<TemplateSelectors:DynamicTemplateSelector.TemplatesCollection>
<Helpers:TemplateCollection>
<DataTemplate DataType="{x:Type System:Int32}">
<Fluent:Button Header="Template test"
KeyTip="B" />
</DataTemplate>
</Helpers:TemplateCollection>
</TemplateSelectors:DynamicTemplateSelector.TemplatesCollection>
</ContentPresenter>
</Fluent:RibbonGroupBox>

<!--It is enough to set attached property Fluent:KeyTip.Keys and
the ribbon will move and show the keytips automatically.
It is possible to set keytips to menu and/or submenu items.
Expand Down
54 changes: 36 additions & 18 deletions Fluent.Ribbon/Adorners/KeyTipAdorner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
namespace Fluent
{
using System.Diagnostics;
using System.Windows.Controls;
using Fluent.Internal;

/// <summary>
/// Represents adorner for KeyTips.
Expand Down Expand Up @@ -124,17 +126,10 @@ private void FindKeyTips(UIElement element, bool hide)
{
this.Log("FindKeyTips");

var children = LogicalTreeHelper.GetChildren(element);
foreach (var item in children)
{
var child = item as UIElement;

if (child == null
|| child.Visibility != Visibility.Visible)
{
continue;
}
var children = GetVisibleChildren(element);

foreach (var child in children)
{
var groupBox = child as RibbonGroupBox;

var keys = KeyTip.GetKeys(child);
Expand Down Expand Up @@ -193,6 +188,29 @@ private void FindKeyTips(UIElement element, bool hide)
}
}

private static UIElement[] GetVisibleChildren(UIElement element)
{
var logicalChildren = LogicalTreeHelper.GetChildren(element)
.OfType<UIElement>();

var children = logicalChildren;

// Always using the visual tree is very expensive, so we only search through it when you 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<UIElement>();
}

return children
.Where(x => x.Visibility == Visibility.Visible)
.Distinct()
.ToArray();
}

#endregion

#region Attach & Detach
Expand All @@ -211,7 +229,7 @@ public void Attach()

this.Log("Attach begin {0}", this.Visibility);

if (!this.oneOfAssociatedElements.IsLoaded)
if (this.oneOfAssociatedElements.IsLoaded == false)
{
// Delay attaching
this.Log("Delaying attach");
Expand Down Expand Up @@ -588,8 +606,10 @@ public bool Forward(string keys, bool click)
}

// Forward to the next element
private void Forward(UIElement element) {
this.Forward(element, true); }
private void Forward(UIElement element)
{
this.Forward(element, true);
}

// Forward to the next element
private void Forward(UIElement element, bool click)
Expand All @@ -608,9 +628,7 @@ private void Forward(UIElement element, bool click)
}
}

var children = LogicalTreeHelper.GetChildren(element)
.OfType<UIElement>()
.ToArray();
var children = GetVisibleChildren(element);

if (children.Length == 0)
{
Expand All @@ -623,7 +641,7 @@ private void Forward(UIElement element, bool click)
: new KeyTipAdorner(element, element, this);

// Stop if no further KeyTips can be displayed.
if (!this.childAdorner.keyTips.Any())
if (this.childAdorner.keyTips.Any() == false)
{
this.Terminate();
return;
Expand Down Expand Up @@ -818,7 +836,7 @@ private void UpdateKeyTipPositions()
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
groupBox.GetLayoutRoot().TranslatePoint(new Point(0, height + 1), this.AdornedElement).Y
};
}
}
Expand Down
9 changes: 6 additions & 3 deletions Fluent.Ribbon/Controls/RibbonTabItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -515,9 +515,9 @@ public RibbonTabItem()

// 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("DataContext")
{
Source = this
});
{
Source = this
});

ContextMenuService.Coerce(this);

Expand Down Expand Up @@ -732,6 +732,9 @@ public void OnKeyTipPressed()
}

this.IsSelected = true;

// This way keytips for delay loaded elements work correctly. Partially fixes #244.
Application.Current.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, new Action(delegate { }));
}

/// <summary>
Expand Down
34 changes: 26 additions & 8 deletions Fluent.Ribbon/Internal/UIHelper.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
namespace Fluent.Internal
{
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;

/// <summary>
/// Class with helper functions for UI related stuff
/// </summary>
internal class UIHelper
internal static class UIHelper
{
/// <summary>
/// Tries to find immediate visual child of type <typeparamref name="T"/> which matches <paramref name="predicate"/>
Expand All @@ -19,9 +20,9 @@ internal class UIHelper
public static T FindImmediateVisualChild<T>(DependencyObject parent, Predicate<T> predicate)
where T : DependencyObject
{
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
foreach (var child in GetVisualChildren(parent))
{
var obj = VisualTreeHelper.GetChild(parent, i) as T;
var obj = child as T;

if (obj != null
&& predicate(obj))
Expand All @@ -37,24 +38,41 @@ public static T FindImmediateVisualChild<T>(DependencyObject parent, Predicate<T
/// Gets the first visual child of type TChildItem by walking down the visual tree.
/// </summary>
/// <typeparam name="TChildItem">The type of visual child to find.</typeparam>
/// <param name="obj">The parent element whose visual tree shall be walked down.</param>
/// <param name="parent">The parent element whose visual tree shall be walked down.</param>
/// <returns>The first element of type TChildItem found in the visual tree is returned. If none is found, null is returned.</returns>
public static TChildItem FindVisualChild<TChildItem>(DependencyObject obj) where TChildItem : DependencyObject
public static TChildItem FindVisualChild<TChildItem>(DependencyObject parent) where TChildItem : DependencyObject
{
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
foreach (var child in GetVisualChildren(parent))
{
var child = VisualTreeHelper.GetChild(obj, i);
var item = child as TChildItem;

if (item != null)
{
return item;

}

var childOfChild = FindVisualChild<TChildItem>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
return null;
}

public static IEnumerable<DependencyObject> GetVisualChildren(DependencyObject parent)
{
var visualChildrenCount = VisualTreeHelper.GetChildrenCount(parent);

for (var i = 0; i < visualChildrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);

if (child != null)
{
yield return child;
}
}
}
}
}

0 comments on commit 7b74fde

Please sign in to comment.