Skip to content

Instantly share code, notes, and snippets.

@michel-pi
Last active May 29, 2019 18:10
Show Gist options
  • Save michel-pi/551f4f9b334614af693ffc732e55f1f3 to your computer and use it in GitHub Desktop.
Save michel-pi/551f4f9b334614af693ffc732e55f1f3 to your computer and use it in GitHub Desktop.
Provides extension methods for WPF windows such as Invoke, enabling drag move, control sliders through the mouse wheel and enumerating controls.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
namespace System.Windows.Extensions
{
/// <summary>
/// Provides extension methods for WPF windows.
/// </summary>
public static class WindowExtensions
{
/// <summary>
/// Determines whether the calling thread is the thread associated with the given <see cref="T:System.Windows.Window" />.
/// </summary>
/// <param name="window">The <see cref="T:System.Windows.Window" /> to be checked.</param>
/// <returns><see langword="true" /> if the calling thread is the thread associated with this <see cref="T:System.Windows.Window" />; otherwise, <see langword="false" />.</returns>
public static bool CheckAccess(this Window window)
{
if (window == null) throw new ArgumentNullException(nameof(window));
return window.Dispatcher?.CheckAccess() != false;
}
/// <summary>
/// Disables the ability to move a window by clicking into it.
/// </summary>
/// <param name="window">A <see cref="T:System.Windows.Window" />.</param>
public static void DisableDragMove(this Window window)
{
if (window == null) throw new ArgumentNullException(nameof(window));
window.Invoke(() => window.MouseLeftButtonDown -= Window_MouseLeftButtonDown);
}
/// <summary>
/// Removes any customization of the visual style applied when a control captures keyboard focus from the given window.
/// </summary>
/// <param name="window">A <see cref="T:System.Windows.Window" />.</param>
public static void DisableFocusVisualStyle(this Window window)
{
if (window == null) throw new ArgumentNullException(nameof(window));
window.Invoke(() =>
{
foreach (var control in window.FindChildren<Control>())
{
control.FocusVisualStyle = null;
}
});
}
/// <summary>
/// Disables the ability to change a sliders value by using the mouse wheel.
/// </summary>
/// <param name="window">A <see cref="T:System.Windows.Window" />.</param>
public static void DisablesMouseWheelSupportForSliders(this Window window)
{
if (window == null) throw new ArgumentNullException(nameof(window));
window.Invoke(() =>
{
foreach (var slider in window.FindChildren<Slider>())
{
slider.PreviewMouseWheel -= Slider_PreviewMouseWheel;
}
});
}
/// <summary>
/// Enables the ability to move a window by clicking into it.
/// </summary>
/// <param name="window">A <see cref="T:System.Windows.Window" />.</param>
public static void EnableDragMove(this Window window)
{
if (window == null) throw new ArgumentNullException(nameof(window));
window.Invoke(() => window.MouseLeftButtonDown += Window_MouseLeftButtonDown);
}
/// <summary>
/// Enables the ability to change a sliders value by using the mouse wheel.
/// </summary>
/// <param name="window">A <see cref="T:System.Windows.Window" />.</param>
public static void EnableMouseWheelSupportForSliders(this Window window)
{
if (window == null) throw new ArgumentNullException(nameof(window));
window.Invoke(() =>
{
foreach (var slider in window.FindChildren<Slider>())
{
slider.PreviewMouseWheel += Slider_PreviewMouseWheel;
}
});
}
/// <summary>
/// Executes the specified <see cref="T:System.Action" /> synchronously on the thread the given <see cref="T:System.Windows.Window" /> is associated with.
/// </summary>
/// <param name="window">A <see cref="T:System.Windows.Window" />.</param>
/// <param name="callback">A delegate to invoke through the window thread.</param>
public static void Invoke(this Window window, Action callback)
{
if (window == null) throw new ArgumentNullException(nameof(window));
if (callback == null) throw new ArgumentNullException(nameof(callback));
if (CheckAccess(window))
{
callback();
}
else
{
window.Dispatcher.Invoke(callback);
}
}
/// <summary>
/// Executes the specified <see cref="T:System.Func`1" /> synchronously on the thread the given <see cref="T:System.Windows.Window" /> is associated with.
/// </summary>
/// <typeparam name="TResult">The return value type of the specified delegate.</typeparam>
/// <param name="window">A <see cref="T:System.Windows.Window" />.</param>
/// <param name="callback">A delegate to invoke through the window thread.</param>
/// <returns>The result of the given callback.</returns>
public static TResult Invoke<TResult>(this Window window, Func<TResult> callback)
{
if (window == null) throw new ArgumentNullException(nameof(window));
if (callback == null) throw new ArgumentNullException(nameof(callback));
if (CheckAccess(window))
{
return callback();
}
else
{
return window.Dispatcher.Invoke<TResult>(callback);
}
}
/// <summary>
/// Executes the specified <see cref="T:System.Action" /> asynchronously on the thread the given <see cref="T:System.Windows.Window" /> is associated with.
/// </summary>
/// <param name="window">A <see cref="T:System.Windows.Window" />.</param>
/// <param name="callback">A delegate to invoke through the window thread.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> created by this method.</returns>
public static Task InvokeAsync(this Window window, Action callback)
{
if (window == null) throw new ArgumentNullException(nameof(window));
if (callback == null) throw new ArgumentNullException(nameof(callback));
var operation = window.Dispatcher.InvokeAsync(callback);
return operation.Task;
}
/// <summary>
/// Executes the specified <see cref="T:System.Func`1" /> asynchronously on the thread the given <see cref="T:System.Windows.Window" /> is associated with.
/// </summary>
/// <typeparam name="TResult">The return value type of the specified delegate.</typeparam>
/// <param name="window">A <see cref="T:System.Windows.Window" />.</param>
/// <param name="callback">A delegate to invoke through the window thread.</param>
/// <returns>The <see cref="T:System.Threading.Tasks.Task" /> created by this method.</returns>
public static Task<TResult> InvokeAsync<TResult>(this Window window, Func<TResult> callback)
{
if (window == null) throw new ArgumentNullException(nameof(window));
if (callback == null) throw new ArgumentNullException(nameof(callback));
var operation = window.Dispatcher.InvokeAsync<TResult>(callback);
return operation.Task;
}
private static void Slider_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (sender == null) return;
if (e == null || e.Delta == 0) return;
if (sender is Slider slider)
{
if (e.Delta > 0)
{
slider.Value += slider.SmallChange;
}
else
{
slider.Value -= slider.SmallChange;
}
}
}
private static void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender == null) return;
if (sender is Window window)
{
window.DragMove();
}
else if (sender is DependencyObject dependencyObject)
{
window = dependencyObject.TryFindParent<Window>();
if (window == null)
{
return;
}
else
{
window.DragMove();
}
}
else
{
return;
}
}
}
#pragma warning disable IDE0019 // Use pattern matching
/// <summary>
/// Helper methods for UI-related tasks.
/// This class was obtained from Philip Sumi (a fellow WPF Disciples blog)
/// http://www.hardcodet.net/uploads/2009/06/UIHelper.cs
/// </summary>
public static class TreeHelper
{
/// <summary>
/// Finds a parent of a given item on the visual tree.
/// </summary>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="child">A direct or indirect child of the
/// queried item.</param>
/// <returns>The first parent item that matches the submitted
/// type parameter. If not matching item can be found, a null
/// reference is being returned.</returns>
public static T TryFindParent<T>(this DependencyObject child) where T : DependencyObject
{
//get parent item
DependencyObject parentObject = GetParentObject(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
return parent ?? TryFindParent<T>(parentObject);
}
/// <summary>
/// Finds all Ancestors of a given item on the visual tree.
/// </summary>
/// <param name="child">A node in a visual tree</param>
/// <returns>All ancestors in visual tree of <paramref name="child"/> element</returns>
public static IEnumerable<DependencyObject> GetAncestors(this DependencyObject child)
{
var parent = VisualTreeHelper.GetParent(child);
while (parent != null)
{
yield return parent;
parent = VisualTreeHelper.GetParent(parent);
}
}
/// <summary>
/// Finds a Child of a given item in the visual tree.
/// </summary>
/// <param name="parent">A direct parent of the queried item.</param>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="childName">x:Name or Name of child. </param>
/// <returns>The first parent item that matches the submitted type parameter.
/// If not matching item can be found,
/// a null parent is being returned.</returns>
public static T FindChild<T>(this DependencyObject parent, string childName = null)
where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null) return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
T childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkInputElement = child as IFrameworkInputElement;
// If the child's name is set for search
if (frameworkInputElement != null && frameworkInputElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
else
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
/// <summary>
/// This method is an alternative to WPF's
/// <see cref="VisualTreeHelper.GetParent"/> method, which also
/// supports content elements. Keep in mind that for content element,
/// this method falls back to the logical tree of the element!
/// </summary>
/// <param name="child">The item to be processed.</param>
/// <returns>The submitted item's parent, if available. Otherwise
/// null.</returns>
public static DependencyObject GetParentObject(this DependencyObject child)
{
if (child == null) return null;
// handle content elements separately
var contentElement = child as ContentElement;
if (contentElement != null)
{
DependencyObject parent = ContentOperations.GetParent(contentElement);
if (parent != null) return parent;
var fce = contentElement as FrameworkContentElement;
return fce?.Parent;
}
var childParent = VisualTreeHelper.GetParent(child);
if (childParent != null)
{
return childParent;
}
// also try searching for parent in framework elements (such as DockPanel, etc)
var frameworkElement = child as FrameworkElement;
if (frameworkElement != null)
{
DependencyObject parent = frameworkElement.Parent;
if (parent != null) return parent;
}
return null;
}
/// <summary>
/// Analyzes both visual and logical tree in order to find all elements of a given
/// type that are descendants of the <paramref name="source"/> item.
/// </summary>
/// <typeparam name="T">The type of the queried items.</typeparam>
/// <param name="source">The root element that marks the source of the search. If the
/// source is already of the requested type, it will not be included in the result.</param>
/// <param name="forceUsingTheVisualTreeHelper">Sometimes it's better to search in the VisualTree (e.g. in tests)</param>
/// <returns>All descendants of <paramref name="source"/> that match the requested type.</returns>
public static IEnumerable<T> FindChildren<T>(this DependencyObject source, bool forceUsingTheVisualTreeHelper = false) where T : DependencyObject
{
if (source != null)
{
var childs = GetChildObjects(source, forceUsingTheVisualTreeHelper);
foreach (DependencyObject child in childs)
{
//analyze if children match the requested type
if (child != null && child is T)
{
yield return (T)child;
}
//recurse tree
foreach (T descendant in FindChildren<T>(child, forceUsingTheVisualTreeHelper))
{
yield return descendant;
}
}
}
}
/// <summary>
/// This method is an alternative to WPF's
/// <see cref="VisualTreeHelper.GetChild"/> method, which also
/// supports content elements. Keep in mind that for content elements,
/// this method falls back to the logical tree of the element.
/// </summary>
/// <param name="parent">The item to be processed.</param>
/// <param name="forceUsingTheVisualTreeHelper">Sometimes it's better to search in the VisualTree (e.g. in tests)</param>
/// <returns>The submitted item's child elements, if available.</returns>
public static IEnumerable<DependencyObject> GetChildObjects(this DependencyObject parent, bool forceUsingTheVisualTreeHelper = false)
{
if (parent == null) yield break;
if (!forceUsingTheVisualTreeHelper && (parent is ContentElement || parent is FrameworkElement))
{
//use the logical tree for content / framework elements
foreach (object obj in LogicalTreeHelper.GetChildren(parent))
{
var depObj = obj as DependencyObject;
if (depObj != null) yield return (DependencyObject)obj;
}
}
else if (parent is Visual || parent is Visual3D)
{
//use the visual tree per default
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
yield return VisualTreeHelper.GetChild(parent, i);
}
}
}
/// <summary>
/// Tries to locate a given item within the visual tree,
/// starting with the dependency object at a given position.
/// </summary>
/// <typeparam name="T">The type of the element to be found
/// on the visual tree of the element at the given location.</typeparam>
/// <param name="reference">The main element which is used to perform
/// hit testing.</param>
/// <param name="point">The position to be evaluated on the origin.</param>
public static T TryFindFromPoint<T>(UIElement reference, Point point)
where T : DependencyObject
{
if (reference.InputHitTest(point) is DependencyObject element)
{
if (element is T result) return result;
return TryFindParent<T>(element);
}
else
{
return null;
}
}
}
#pragma warning restore IDE0019 // Use pattern matching
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment