How do you enable WPF to respond to horizontal scrolling using the mouse tilt wheel? For example, I have a Microsoft Explorer mini mouse and have tried horizontally scrolling content contained within a ScrollViewer with
HorizontalScrollBarVisibility="Visible"
but the content will not scroll horizontally. Vertical scrolling, however, works reliably as usual.
If such input is not directly supported by WPF at this time, is there a way to do this using interop with unmanaged code?
Thanks!
I just made a class that adds the PreviewMouseHorizontalWheel and MouseHorizontalWheel attached events to all UIElements.
These events include as parameter a MouseHorizontalWheelEventArgs HorizontalDelta.
Update 3
The tilt value was reversed according to WPF standards, where up is positive and down is negative, so made left positive and right negative.
Update 2
If the AutoEnableMouseHorizontalWheelSupport is set to true (as it is by default) there's no special requirement to use those events.
Only if it is set to false then you will need to call either MouseHorizontalWheelEnabler.EnableMouseHorizontalWheel(X)
where X is the top level element (Window, Popup or ContextMenu) or MouseHorizontalWheelEnabler.EnableMouseHorizontalWheelForParentOf(X) with the Element to enable support for. You can read the provided docs for more info on those methods.
Note that all this does nothing on XP, since WM_MOUSE-H-WHEEL was added on Vista.
MouseHorizontalWheelEnabler.cs
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interop;
using JetBrains.Annotations;
namespace WpfExtensions
{
public static class MouseHorizontalWheelEnabler
{
/// <summary>
/// When true it will try to enable Horizontal Wheel support on parent windows/popups/context menus automatically
/// so the programmer does not need to call it.
/// Defaults to true.
/// </summary>
public static bool AutoEnableMouseHorizontalWheelSupport = true;
private static readonly HashSet<IntPtr> _HookedWindows = new HashSet<IntPtr>();
/// <summary>
/// Enable Horizontal Wheel support for all the controls inside the window.
/// This method does not need to be called if AutoEnableMouseHorizontalWheelSupport is true.
/// This does not include popups or context menus.
/// If it was already enabled it will do nothing.
/// </summary>
/// <param name="window">Window to enable support for.</param>
public static void EnableMouseHorizontalWheelSupport([NotNull] Window window) {
if (window == null) {
throw new ArgumentNullException(nameof(window));
}
if (window.IsLoaded) {
// handle should be available at this level
IntPtr handle = new WindowInteropHelper(window).Handle;
EnableMouseHorizontalWheelSupport(handle);
}
else {
window.Loaded += (sender, args) => {
IntPtr handle = new WindowInteropHelper(window).Handle;
EnableMouseHorizontalWheelSupport(handle);
};
}
}
/// <summary>
/// Enable Horizontal Wheel support for all the controls inside the popup.
/// This method does not need to be called if AutoEnableMouseHorizontalWheelSupport is true.
/// This does not include sub-popups or context menus.
/// If it was already enabled it will do nothing.
/// </summary>
/// <param name="popup">Popup to enable support for.</param>
public static void EnableMouseHorizontalWheelSupport([NotNull] Popup popup) {
if (popup == null) {
throw new ArgumentNullException(nameof(popup));
}
if (popup.IsOpen) {
// handle should be available at this level
// ReSharper disable once PossibleInvalidOperationException
EnableMouseHorizontalWheelSupport(GetObjectParentHandle(popup.Child).Value);
}
// also hook for IsOpened since a new window is created each time
popup.Opened += (sender, args) => {
// ReSharper disable once PossibleInvalidOperationException
EnableMouseHorizontalWheelSupport(GetObjectParentHandle(popup.Child).Value);
};
}
/// <summary>
/// Enable Horizontal Wheel support for all the controls inside the context menu.
/// This method does not need to be called if AutoEnableMouseHorizontalWheelSupport is true.
/// This does not include popups or sub-context menus.
/// If it was already enabled it will do nothing.
/// </summary>
/// <param name="contextMenu">Context menu to enable support for.</param>
public static void EnableMouseHorizontalWheelSupport([NotNull] ContextMenu contextMenu) {
if (contextMenu == null) {
throw new ArgumentNullException(nameof(contextMenu));
}
if (contextMenu.IsOpen) {
// handle should be available at this level
// ReSharper disable once PossibleInvalidOperationException
EnableMouseHorizontalWheelSupport(GetObjectParentHandle(contextMenu).Value);
}
// also hook for IsOpened since a new window is created each time
contextMenu.Opened += (sender, args) => {
// ReSharper disable once PossibleInvalidOperationException
EnableMouseHorizontalWheelSupport(GetObjectParentHandle(contextMenu).Value);
};
}
private static IntPtr? GetObjectParentHandle([NotNull] DependencyObject depObj) {
if (depObj == null) {
throw new ArgumentNullException(nameof(depObj));
}
var presentationSource = PresentationSource.FromDependencyObject(depObj) as HwndSource;
return presentationSource?.Handle;
}
/// <summary>
/// Enable Horizontal Wheel support for all the controls inside the HWND.
/// This method does not need to be called if AutoEnableMouseHorizontalWheelSupport is true.
/// This does not include popups or sub-context menus.
/// If it was already enabled it will do nothing.
/// </summary>
/// <param name="handle">HWND handle to enable support for.</param>
/// <returns>True if it was enabled or already enabled, false if it couldn't be enabled.</returns>
public static bool EnableMouseHorizontalWheelSupport(IntPtr handle) {
if (_HookedWindows.Contains(handle)) {
return true;
}
_HookedWindows.Add(handle);
HwndSource source = HwndSource.FromHwnd(handle);
if (source == null) {
return false;
}
source.AddHook(WndProcHook);
return true;
}
/// <summary>
/// Disable Horizontal Wheel support for all the controls inside the HWND.
/// This method does not need to be called in most cases.
/// This does not include popups or sub-context menus.
/// If it was already disabled it will do nothing.
/// </summary>
/// <param name="handle">HWND handle to disable support for.</param>
/// <returns>True if it was disabled or already disabled, false if it couldn't be disabled.</returns>
public static bool DisableMouseHorizontalWheelSupport(IntPtr handle) {
if (!_HookedWindows.Contains(handle)) {
return true;
}
HwndSource source = HwndSource.FromHwnd(handle);
if (source == null) {
return false;
}
source.RemoveHook(WndProcHook);
_HookedWindows.Remove(handle);
return true;
}
/// <summary>
/// Disable Horizontal Wheel support for all the controls inside the window.
/// This method does not need to be called in most cases.
/// This does not include popups or sub-context menus.
/// If it was already disabled it will do nothing.
/// </summary>
/// <param name="window">Window to disable support for.</param>
/// <returns>True if it was disabled or already disabled, false if it couldn't be disabled.</returns>
public static bool DisableMouseHorizontalWheelSupport([NotNull] Window window) {
if (window == null) {
throw new ArgumentNullException(nameof(window));
}
IntPtr handle = new WindowInteropHelper(window).Handle;
return DisableMouseHorizontalWheelSupport(handle);
}
/// <summary>
/// Disable Horizontal Wheel support for all the controls inside the popup.
/// This method does not need to be called in most cases.
/// This does not include popups or sub-context menus.
/// If it was already disabled it will do nothing.
/// </summary>
/// <param name="popup">Popup to disable support for.</param>
/// <returns>True if it was disabled or already disabled, false if it couldn't be disabled.</returns>
public static bool DisableMouseHorizontalWheelSupport([NotNull] Popup popup) {
if (popup == null) {
throw new ArgumentNullException(nameof(popup));
}
IntPtr? handle = GetObjectParentHandle(popup.Child);
if (handle == null) {
return false;
}
return DisableMouseHorizontalWheelSupport(handle.Value);
}
/// <summary>
/// Disable Horizontal Wheel support for all the controls inside the context menu.
/// This method does not need to be called in most cases.
/// This does not include popups or sub-context menus.
/// If it was already disabled it will do nothing.
/// </summary>
/// <param name="contextMenu">Context menu to disable support for.</param>
/// <returns>True if it was disabled or already disabled, false if it couldn't be disabled.</returns>
public static bool DisableMouseHorizontalWheelSupport([NotNull] ContextMenu contextMenu) {
if (contextMenu == null) {
throw new ArgumentNullException(nameof(contextMenu));
}
IntPtr? handle = GetObjectParentHandle(contextMenu);
if (handle == null) {
return false;
}
return DisableMouseHorizontalWheelSupport(handle.Value);
}
/// <summary>
/// Enable Horizontal Wheel support for all that control and all controls hosted by the same window/popup/context menu.
/// This method does not need to be called if AutoEnableMouseHorizontalWheelSupport is true.
/// If it was already enabled it will do nothing.
/// </summary>
/// <param name="uiElement">UI Element to enable support for.</param>
public static void EnableMouseHorizontalWheelSupportForParentOf(UIElement uiElement) {
// try to add it right now
if (uiElement is Window) {
EnableMouseHorizontalWheelSupport((Window)uiElement);
}
else if (uiElement is Popup) {
EnableMouseHorizontalWheelSupport((Popup)uiElement);
}
else if (uiElement is ContextMenu) {
EnableMouseHorizontalWheelSupport((ContextMenu)uiElement);
}
else {
IntPtr? parentHandle = GetObjectParentHandle(uiElement);
if (parentHandle != null) {
EnableMouseHorizontalWheelSupport(parentHandle.Value);
}
// and in the rare case the parent window ever changes...
PresentationSource.AddSourceChangedHandler(uiElement, PresenationSourceChangedHandler);
}
}
private static void PresenationSourceChangedHandler(object sender, SourceChangedEventArgs sourceChangedEventArgs) {
var src = sourceChangedEventArgs.NewSource as HwndSource;
if (src != null) {
EnableMouseHorizontalWheelSupport(src.Handle);
}
}
private static void HandleMouseHorizontalWheel(IntPtr wParam) {
int tilt = -Win32.HiWord(wParam);
if (tilt == 0) {
return;
}
IInputElement element = Mouse.DirectlyOver;
if (element == null) {
return;
}
if (!(element is UIElement)) {
element = VisualTreeHelpers.FindAncestor<UIElement>(element as DependencyObject);
}
if (element == null) {
return;
}
var ev = new MouseHorizontalWheelEventArgs(Mouse.PrimaryDevice, Environment.TickCount, tilt) {
RoutedEvent = PreviewMouseHorizontalWheelEvent
//Source = handledWindow
};
// first raise preview
element.RaiseEvent(ev);
if (ev.Handled) {
return;
}
// then bubble it
ev.RoutedEvent = MouseHorizontalWheelEvent;
element.RaiseEvent(ev);
}
private static IntPtr WndProcHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
// transform horizontal mouse wheel messages
switch (msg) {
case Win32.WM_MOUSEHWHEEL:
HandleMouseHorizontalWheel(wParam);
break;
}
return IntPtr.Zero;
}
private static class Win32
{
// ReSharper disable InconsistentNaming
public const int WM_MOUSEHWHEEL = 0x020E;
// ReSharper restore InconsistentNaming
public static int GetIntUnchecked(IntPtr value) {
return IntPtr.Size == 8 ? unchecked((int)value.ToInt64()) : value.ToInt32();
}
public static int HiWord(IntPtr ptr) {
return unchecked((short)((uint)GetIntUnchecked(ptr) >> 16));
}
}
#region MouseWheelHorizontal Event
public static readonly RoutedEvent MouseHorizontalWheelEvent =
EventManager.RegisterRoutedEvent("MouseHorizontalWheel", RoutingStrategy.Bubble, typeof(RoutedEventHandler),
typeof(MouseHorizontalWheelEnabler));
public static void AddMouseHorizontalWheelHandler(DependencyObject d, RoutedEventHandler handler) {
var uie = d as UIElement;
if (uie != null) {
uie.AddHandler(MouseHorizontalWheelEvent, handler);
if (AutoEnableMouseHorizontalWheelSupport) {
EnableMouseHorizontalWheelSupportForParentOf(uie);
}
}
}
public static void RemoveMouseHorizontalWheelHandler(DependencyObject d, RoutedEventHandler handler) {
var uie = d as UIElement;
uie?.RemoveHandler(MouseHorizontalWheelEvent, handler);
}
#endregion
#region PreviewMouseWheelHorizontal Event
public static readonly RoutedEvent PreviewMouseHorizontalWheelEvent =
EventManager.RegisterRoutedEvent("PreviewMouseHorizontalWheel", RoutingStrategy.Tunnel, typeof(RoutedEventHandler),
typeof(MouseHorizontalWheelEnabler));
public static void AddPreviewMouseHorizontalWheelHandler(DependencyObject d, RoutedEventHandler handler) {
var uie = d as UIElement;
if (uie != null) {
uie.AddHandler(PreviewMouseHorizontalWheelEvent, handler);
if (AutoEnableMouseHorizontalWheelSupport) {
EnableMouseHorizontalWheelSupportForParentOf(uie);
}
}
}
public static void RemovePreviewMouseHorizontalWheelHandler(DependencyObject d, RoutedEventHandler handler) {
var uie = d as UIElement;
uie?.RemoveHandler(PreviewMouseHorizontalWheelEvent, handler);
}
#endregion
}
}
MouseHorizontalWheelEventArgs.cs
using System.Windows.Input;
namespace WpfExtensions
{
public class MouseHorizontalWheelEventArgs : MouseEventArgs
{
public int HorizontalDelta { get; }
public MouseHorizontalWheelEventArgs(MouseDevice mouse, int timestamp, int horizontalDelta)
: base(mouse, timestamp) {
HorizontalDelta = horizontalDelta;
}
}
}
As for VisualTreeHelpers.FindAncestor, it is defined as follows:
/// <summary>
/// Returns the first ancestor of specified type
/// </summary>
public static T FindAncestor<T>(DependencyObject current) where T : DependencyObject {
current = GetVisualOrLogicalParent(current);
while (current != null) {
if (current is T) {
return (T)current;
}
current = GetVisualOrLogicalParent(current);
}
return null;
}
private static DependencyObject GetVisualOrLogicalParent(DependencyObject obj) {
if (obj is Visual || obj is Visual3D) {
return VisualTreeHelper.GetParent(obj);
}
return LogicalTreeHelper.GetParent(obj);
}
Call the AddHook() method in your Window constructor so you can spy on the messages. Look for WM_MOUSEHWHEEL, message 0x20e. Use wParam.ToInt32() >> 16 to get the movement amount, a multiple of 120.
Another solution using Attached Properties. It works for any Control that is either a ScrollViewer or contains a ScrollViewer. It is a fairly simple solution, and most importantly it is very easy to re-use. What I've done with my project is set this attached property in Generic.xaml for DataGrid, ListBox, ListView and friends. This way, it always just works.
This will work with multiple scroll viewers in the same UI, and will apply to whichever one currently has the mouse over it.
Here's the code for the attached property, as well as a helper class
note: C#6 syntax
Needed code
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
namespace MyTestProject
{
public class TiltWheelHorizontalScroller
{
public static bool GetEnableTiltWheelScroll(DependencyObject obj) => (bool)obj.GetValue(EnableTiltWheelScrollProperty);
public static void SetEnableTiltWheelScroll(DependencyObject obj, bool value) => obj.SetValue(EnableTiltWheelScrollProperty, value);
public static readonly DependencyProperty EnableTiltWheelScrollProperty =
DependencyProperty.RegisterAttached("EnableTiltWheelScroll", typeof(bool), typeof(TiltWheelHorizontalScroller), new UIPropertyMetadata(false, OnHorizontalMouseWheelScrollingEnabledChanged));
static HashSet<int> controls = new HashSet<int>();
static void OnHorizontalMouseWheelScrollingEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
Control control = d as Control;
if (control != null && GetEnableTiltWheelScroll(d) && controls.Add(control.GetHashCode()))
{
control.MouseEnter += (sender, e) =>
{
var scrollViewer = d.FindChildOfType<ScrollViewer>();
if (scrollViewer != null)
{
new TiltWheelMouseScrollHelper(scrollViewer, d);
}
};
}
}
}
class TiltWheelMouseScrollHelper
{
/// <summary>
/// multiplier of how far to scroll horizontally. Change as desired.
/// </summary>
private const int scrollFactor = 3;
private const int WM_MOUSEHWEEL = 0x20e;
ScrollViewer scrollViewer;
HwndSource hwndSource;
HwndSourceHook hook;
static HashSet<int> scrollViewers = new HashSet<int>();
public TiltWheelMouseScrollHelper(ScrollViewer scrollViewer, DependencyObject d)
{
this.scrollViewer = scrollViewer;
hwndSource = PresentationSource.FromDependencyObject(d) as HwndSource;
hook = WindowProc;
hwndSource?.AddHook(hook);
if (scrollViewers.Add(scrollViewer.GetHashCode()))
{
scrollViewer.MouseLeave += (sender, e) =>
{
hwndSource.RemoveHook(hook);
};
}
}
IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case WM_MOUSEHWEEL:
Scroll(wParam);
handled = true;
break;
}
return IntPtr.Zero;
}
private void Scroll(IntPtr wParam)
{
int delta = (HIWORD(wParam) > 0 ? 1 : -1) * scrollFactor;
scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + delta);
}
private static int HIWORD(IntPtr ptr) => (short)((((int)ptr.ToInt64()) >> 16) & 0xFFFF);
}
}
And you will need this extension method if you don't already have it.
/// <summary>
/// Finds first child of provided type. If child not found, null is returned
/// </summary>
/// <typeparam name="T">Type of chiled to be found</typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static T FindChildOfType<T>(this DependencyObject originalSource) where T : DependencyObject
{
T ret = originalSource as T;
DependencyObject child = null;
if (originalSource != null && ret == null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(originalSource); i++)
{
child = VisualTreeHelper.GetChild(originalSource, i);
if (child != null)
{
if (child is T)
{
ret = child as T;
break;
}
else
{
ret = child.FindChildOfType<T>();
if (ret != null)
{
break;
}
}
}
}
}
return ret;
}
Usage
Simple example of a Window with a DataGrid. Here DataItems is just some fake data I made up for the test case.
<Window x:Class="MyTestProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ap="clr-namespace:MyTestProject"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<DataGrid x:Name="dataGrid"
ItemsSource="{Binding DataItems}"
ap:TiltWheelHorizontalScroller.EnableTiltWheelScroll="True"/>
</Grid>
</Window>
Or, what I ended up doing, put this style in Generic.xaml, or your Window.Resources to apply to all datagrids. You can attach this property to any control that has a ScrollViewer in it (and of course that horizontal scrolling is not disabled).
<Style TargetType="{x:Type DataGrid}" BasedOn="{StaticResource {x:Type DataGrid}}">
<Setter Property="ap:TiltWheelHorizontalScroller.EnableTiltWheelScroll" Value="True"/>
</Style>
T. Webster posted a WPF code snippet that adds horizontal mouse scroll support to any ScrollViewer and DependancyObject. It utilizes the AddHook and window messages as others have described.
I was able to adapt this to a behavior pretty quickly and attach it to a ScrollViewer in XAML.
This Microsoft link provides your exact requirement:
horizontal scrolling
and then just override this method -
private void OnMouseTilt(int tilt)
{
// Write your horizontal handling codes here.
if(!mainScrollViewer.IsVisible) return;
if (tilt > 0)
{
mainScrollViewer.LineLeft();
}
else
{
mainScrollViewer.LineRight();
}
}
Related
I need a layout control similar to a Viewbox but with slightly different stretching rules. I copied the Viewbox sources from the .NET reference source, but now I see weird data binding issues in child controls.
Here is a simplified example:
<Window
x:Class="CustomViewbox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomViewbox"
Title="MainWindow" Height="450" Width="800"
>
<local:Viewbox Stretch="Uniform">
<Grid>
<StackPanel>
<TextBlock Foreground="Red" Margin="5" Text="{Binding Path=Round,StringFormat={}Round {0}}"/>
<Button Margin="5" Content="Next round" Click="next_round_Click"/>
</StackPanel>
</Grid>
</local:Viewbox>
</Window>
And the corresponding code-behind:
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace CustomViewbox
{
public class Game : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
int round = 1;
public int Round { get { return round; } set { round = value; NotifyPropertyChanged(); } }
}
public partial class MainWindow : Window
{
Game game;
public MainWindow()
{
InitializeComponent();
game = new Game();
DataContext = game;
}
private void next_round_Click(object sender, RoutedEventArgs e)
{
++game.Round;
}
}
}
When running that, the TextBlock is empty or invisible (not collapsed). So far I've found 3 unsatisfactory workarounds:
if I replace local:Viewbox with just Viewbox, the layout is wrong
if I remove the Foreground attribute from the TextBlock, the text is the wrong color
finally if I add the attribute DataContext="{Binding}" to the TextBlock, it looks OK, but this doesn't feel like a proper fix
Can someone explain what is going on, or how to debug that?
For completeness here is the modified Viewbox source code, which should be all the code you need to reproduce my problem:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace CustomViewbox
{
public class Viewbox : Decorator
{
private ContainerVisual _internalVisual;
public static readonly DependencyProperty StretchProperty = DependencyProperty.Register("Stretch", typeof(Stretch), typeof(Viewbox), new FrameworkPropertyMetadata(Stretch.Uniform, FrameworkPropertyMetadataOptions.AffectsMeasure), new ValidateValueCallback(ValidateStretchValue));
private static bool ValidateStretchValue(object value)
{
var s = (Stretch)value;
return s == Stretch.Uniform
|| s == Stretch.None
|| s == Stretch.Fill
|| s == Stretch.UniformToFill;
}
private ContainerVisual InternalVisual
{
get
{
if (_internalVisual == null)
{
_internalVisual = new ContainerVisual();
AddVisualChild(_internalVisual);
}
return _internalVisual;
}
}
private UIElement InternalChild
{
get
{
VisualCollection vc = InternalVisual.Children;
if (vc.Count != 0) return vc[0] as UIElement;
else return null;
}
set
{
VisualCollection vc = InternalVisual.Children;
if (vc.Count != 0) vc.Clear();
vc.Add(value);
}
}
private Transform InternalTransform
{
get
{
return InternalVisual.Transform;
}
set
{
InternalVisual.Transform = value;
}
}
public override UIElement Child
{
// everything is the same as on Decorator, the only difference is to insert intermediate Visual to specify scaling transform
get
{
return InternalChild;
}
set
{
UIElement old = InternalChild;
if (old != value)
{
//need to remove old element from logical tree
RemoveLogicalChild(old);
if (value != null)
{
AddLogicalChild(value);
}
InternalChild = value;
InvalidateMeasure();
}
}
}
protected override int VisualChildrenCount
{
get { return 1; /* Always have internal container visual */ }
}
protected override Visual GetVisualChild(int index)
{
if (index != 0)
{
throw new ArgumentOutOfRangeException("index", index, /*SR.Get(SRID.Visual_ArgumentOutOfRange)*/"argument out of range");
}
return InternalVisual;
}
public Stretch Stretch
{
get { return (Stretch)GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
}
/// <summary>
/// Updates DesiredSize of the Viewbox. Called by parent UIElement. This is the first pass of layout.
/// </summary>
/// <remarks>
/// Viewbox measures it's child at an infinite constraint; it allows the child to be however large it so desires.
/// The child's returned size will be used as it's natural size for scaling to Viewbox's size during Arrange.
/// </remarks>
/// <param name="constraint">Constraint size is an "upper limit" that the return value should not exceed.</param>
/// <returns>The Decorator's desired size.</returns>
protected override Size MeasureOverride(Size constraint)
{
var child = InternalChild;
var parentSize = new Size();
if (child != null)
{
// Initialize child constraint to infinity. We need to get a "natural" size for the child in absence of constraint.
// Note that an author *can* impose a constraint on a child by using Height/Width, &c... properties
var infiniteConstraint = new Size(double.PositiveInfinity, double.PositiveInfinity);
child.Measure(infiniteConstraint);
var childSize = child.DesiredSize;
var scalefac = ComputeScaleFactor(constraint, childSize, Stretch);
parentSize.Width = scalefac * childSize.Width;
parentSize.Height = scalefac * childSize.Height;
if (parentSize.Width > constraint.Width)
parentSize.Width = constraint.Width;
childSize = new Size(constraint.Width / scalefac, constraint.Height / scalefac);
child.Measure(childSize);
}
return parentSize;
}
/// <summary>
/// Viewbox always sets the child to its desired size. It then computes and applies a transformation
/// from that size to the space available: Viewbox's own input size less child margin.
///
/// Viewbox also calls arrange on its child.
/// </summary>
/// <param name="arrangeSize">Size in which Border will draw the borders/background and children.</param>
protected override Size ArrangeOverride(Size arrangeSize)
{
var child = InternalChild;
if (child != null)
{
var childSize = child.DesiredSize;
// Compute scaling factors from arrange size and the measured child content size
var scalefac = ComputeScaleFactor(arrangeSize, childSize, Stretch);
InternalTransform = new ScaleTransform(scalefac, scalefac);
childSize = new Size(arrangeSize.Width / scalefac, arrangeSize.Height / scalefac);
// Arrange the child to the desired size
child.Arrange(new Rect(new Point(), childSize));
// return the size occupied by scaled child
arrangeSize.Width = scalefac * childSize.Width;
arrangeSize.Height = scalefac * childSize.Height;
}
return arrangeSize;
}
/// <summary>
/// This is a helper function that computes scale factors depending on a target size and a content size
/// </summary>
/// <param name="availableSize">Size into which the content is being fitted.</param>
/// <param name="contentSize">Size of the content, measured natively (unconstrained).</param>
/// <param name="stretch">Value of the Stretch property on the element.</param>
internal static double ComputeScaleFactor(Size availableSize, Size contentSize, Stretch stretch)
{
// Compute scaling factors to use for axes
var scale = 1.0;
var isConstrainedHeight = !Double.IsPositiveInfinity(availableSize.Height);
if (isConstrainedHeight)
{
// Compute scaling factors for both axes
scale = (DoubleUtil.IsZero(contentSize.Height)) ? 0.0 : availableSize.Height / contentSize.Height;
}
return scale;
}
}
static class DoubleUtil
{
internal const double DBL_EPSILON = 2.2204460492503131e-016; /* smallest such that 1.0+DBL_EPSILON != 1.0 */
internal const float FLT_MIN = 1.175494351e-38F; /* Number close to zero, where float.MinValue is -float.MaxValue */
public static bool IsZero(double value)
{
return Math.Abs(value) < 10.0 * DBL_EPSILON;
}
}
}
It turns out the problem was the missing override of LogicalChildren in the Viewbox class, as suggested by Klaus Gütter. I wrongly assumed one cannot override an internal property, so I removed it. But it turns out I can as long as I remove the internal keyword from my override.
Here is the working code:
using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace CustomViewbox
{
/// <summary>
/// Returns an Enumerator that enumerates over nothing.
/// </summary>
internal class EmptyEnumerator : IEnumerator
{
// singleton class, private ctor
private EmptyEnumerator()
{
}
/// <summary>
/// Read-Only instance of an Empty Enumerator.
/// </summary>
public static IEnumerator Instance
{
get
{
if (_instance == null)
{
_instance = new EmptyEnumerator();
}
return _instance;
}
}
/// <summary>
/// Does nothing.
/// </summary>
public void Reset() { }
/// <summary>
/// Returns false.
/// </summary>
/// <returns>false</returns>
public bool MoveNext() { return false; }
#pragma warning disable 1634, 1691 // about to use PreSharp message numbers - unknown to C#
/// <summary>
/// Returns null.
/// </summary>
public object Current
{
get
{
#pragma warning disable 6503 // "Property get methods should not throw exceptions."
throw new InvalidOperationException();
#pragma warning restore 6503
}
}
#pragma warning restore 1634, 1691
private static IEnumerator _instance;
}
internal class SingleChildEnumerator : IEnumerator
{
internal SingleChildEnumerator(object Child)
{
_child = Child;
_count = Child == null ? 0 : 1;
}
object IEnumerator.Current
{
get { return (_index == 0) ? _child : null; }
}
bool IEnumerator.MoveNext()
{
_index++;
return _index < _count;
}
void IEnumerator.Reset()
{
_index = -1;
}
private int _index = -1;
private int _count = 0;
private object _child;
}
/// <summary>
/// </summary>
public class Viewbox : Decorator
{
//-------------------------------------------------------------------
//
// Constructors
//
//-------------------------------------------------------------------
#region Constructors
/*static Viewbox()
{
ControlsTraceLogger.AddControl(TelemetryControls.ViewBox);
}*/
/// <summary>
/// Default DependencyObject constructor
/// </summary>
/// <remarks>
/// Automatic determination of current Dispatcher. Use alternative constructor
/// that accepts a Dispatcher for best performance.
/// </remarks>
public Viewbox() : base()
{
}
#endregion
//-------------------------------------------------------------------
//
// Public Fields
//
//-------------------------------------------------------------------
#region Public Fields
/// <summary>
/// This is the DependencyProperty for the Viewbox's Stretch property.
///
/// Default: Stretch.Uniform
/// <seealso cref="Viewbox.Stretch" />
/// </summary>
public static readonly DependencyProperty StretchProperty
= DependencyProperty.Register(
"Stretch", // Property name
typeof(Stretch), // Property type
typeof(Viewbox), // Property owner
new FrameworkPropertyMetadata(Stretch.Uniform, FrameworkPropertyMetadataOptions.AffectsMeasure),
new ValidateValueCallback(ValidateStretchValue));
private static bool ValidateStretchValue(object value)
{
Stretch s = (Stretch)value;
return (s == Stretch.Uniform
|| s == Stretch.None
|| s == Stretch.Fill
|| s == Stretch.UniformToFill);
}
#endregion
//-------------------------------------------------------------------
//
// Public Methods
//
//-------------------------------------------------------------------
//-------------------------------------------------------------------
//
// Public Properties
//
//-------------------------------------------------------------------
#region Public Properties
private ContainerVisual InternalVisual
{
get
{
if (_internalVisual == null)
{
_internalVisual = new ContainerVisual();
AddVisualChild(_internalVisual);
}
return _internalVisual;
}
}
private UIElement InternalChild
{
get
{
VisualCollection vc = InternalVisual.Children;
if (vc.Count != 0) return vc[0] as UIElement;
else return null;
}
set
{
VisualCollection vc = InternalVisual.Children;
if (vc.Count != 0) vc.Clear();
vc.Add(value);
}
}
private Transform InternalTransform
{
get
{
return InternalVisual.Transform;
}
set
{
InternalVisual.Transform = value;
}
}
/// <summary>
/// The single child of a <see cref="Viewbox" />
/// </summary>
public override UIElement Child
{
//everything is the same as on Decorator, the only difference is to insert intermediate Visual to
//specify scaling transform
get
{
return InternalChild;
}
set
{
UIElement old = InternalChild;
if (old != value)
{
//need to remove old element from logical tree
RemoveLogicalChild(old);
if (value != null)
{
AddLogicalChild(value);
}
InternalChild = value;
InvalidateMeasure();
}
}
}
/// <summary>
/// Returns the Visual children count.
/// </summary>
protected override int VisualChildrenCount
{
get { return 1; /* Always have internal container visual */ }
}
/// <summary>
/// Returns the child at the specified index.
/// </summary>
protected override Visual GetVisualChild(int index)
{
if (index != 0)
{
throw new ArgumentOutOfRangeException(/*"index", index, SR.Get(SRID.Visual_ArgumentOutOfRange)*/);
}
return InternalVisual;
}
/// <summary>
/// Returns enumerator to logical children.
/// </summary>
protected override IEnumerator LogicalChildren
{
get
{
if (InternalChild == null)
{
return EmptyEnumerator.Instance;
}
return new SingleChildEnumerator(InternalChild);
}
}
/// <summary>
/// Gets/Sets the Stretch mode of the Viewbox, which determines how the content will be
/// fit into the Viewbox's space.
///
/// </summary>
/// <seealso cref="Viewbox.StretchProperty" />
/// <seealso cref="Stretch" />
public Stretch Stretch
{
get { return (Stretch)GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
}
#endregion Public Properties
//-------------------------------------------------------------------
//
// Protected Methods
//
//-------------------------------------------------------------------
#region Protected Methods
/// <summary>
/// Updates DesiredSize of the Viewbox. Called by parent UIElement. This is the first pass of layout.
/// </summary>
/// <remarks>
/// Viewbox measures it's child at an infinite constraint; it allows the child to be however large it so desires.
/// The child's returned size will be used as it's natural size for scaling to Viewbox's size during Arrange.
/// </remarks>
/// <param name="constraint">Constraint size is an "upper limit" that the return value should not exceed.</param>
/// <returns>The Decorator's desired size.</returns>
protected override Size MeasureOverride(Size constraint)
{
var child = InternalChild;
var parentSize = new Size();
if (child != null)
{
// Initialize child constraint to infinity. We need to get a "natural" size for the child in absence of constraint.
// Note that an author *can* impose a constraint on a child by using Height/Width, &c... properties
var infiniteConstraint = new Size(double.PositiveInfinity, double.PositiveInfinity);
child.Measure(infiniteConstraint);
var childSize = child.DesiredSize;
var scalefac = ComputeScaleFactor(constraint, childSize, Stretch);
parentSize.Width = scalefac * childSize.Width;
parentSize.Height = scalefac * childSize.Height;
if (parentSize.Width > constraint.Width)
parentSize.Width = constraint.Width;
childSize = new Size(constraint.Width / scalefac, constraint.Height / scalefac);
child.Measure(childSize);
}
return parentSize;
}
/// <summary>
/// Viewbox always sets the child to its desired size. It then computes and applies a transformation
/// from that size to the space available: Viewbox's own input size less child margin.
///
/// Viewbox also calls arrange on its child.
/// </summary>
/// <param name="arrangeSize">Size in which Border will draw the borders/background and children.</param>
protected override Size ArrangeOverride(Size arrangeSize)
{
var child = InternalChild;
if (child != null)
{
var childSize = child.DesiredSize;
// Compute scaling factors from arrange size and the measured child content size
var scalefac = ComputeScaleFactor(arrangeSize, childSize, Stretch);
InternalTransform = new ScaleTransform(scalefac, scalefac);
childSize = new Size(arrangeSize.Width / scalefac, arrangeSize.Height / scalefac);
// Arrange the child to the desired size
child.Arrange(new Rect(new Point(), childSize));
// return the size occupied by scaled child
arrangeSize.Width = scalefac * childSize.Width;
arrangeSize.Height = scalefac * childSize.Height;
}
return arrangeSize;
}
/// <summary>
/// This is a helper function that computes scale factors depending on a target size and a content size
/// </summary>
/// <param name="availableSize">Size into which the content is being fitted.</param>
/// <param name="contentSize">Size of the content, measured natively (unconstrained).</param>
/// <param name="stretch">Value of the Stretch property on the element.</param>
internal static double ComputeScaleFactor(Size availableSize, Size contentSize, Stretch stretch)
{
// Compute scaling factors to use for axes
var scale = 1.0;
var isConstrainedHeight = !Double.IsPositiveInfinity(availableSize.Height);
if (isConstrainedHeight)
{
// Compute scaling factors for both axes
scale = (DoubleUtil.IsZero(contentSize.Height)) ? 0.0 : availableSize.Height / contentSize.Height;
}
return scale;
}
#endregion Protected Methods
//-------------------------------------------------------------------
//
// Private Fields
//
//-------------------------------------------------------------------
#region Private Fields
private ContainerVisual _internalVisual;
#endregion
}
static class DoubleUtil
{
internal const double DBL_EPSILON = 2.2204460492503131e-016; /* smallest such that 1.0+DBL_EPSILON != 1.0 */
internal const float FLT_MIN = 1.175494351e-38F; /* Number close to zero, where float.MinValue is -float.MaxValue */
public static bool IsZero(double value)
{
return Math.Abs(value) < 10.0 * DBL_EPSILON;
}
}
}
Below is the standard d3drenderer.cs file coding:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using System.Xml.Linq;
namespace WPFMediaKit.DirectShow.Controls
{
/// <summary>
/// The D3DRenderer class provides basic functionality needed
/// to render a D3D surface. This class is abstract.
/// </summary>
public abstract class D3DRenderer : FrameworkElement
{
private string workingDirectory = AppDomain.CurrentDomain.BaseDirectory;
/// <summary>
/// The D3DImage used to render video
/// </summary>
private D3DImage m_d3dImage;
/// <summary>
/// The Image control that has the source
/// to the D3DImage
/// </summary>
private Image m_videoImage;
/// <summary>
/// We keep reference to the D3D surface so
/// we can delay loading it to avoid a black flicker
/// when loading new media
/// </summary>
private IntPtr m_pBackBuffer = IntPtr.Zero;
/// <summary>
/// Flag to tell us if we have a new D3D
/// Surface available
/// </summary>
private bool m_newSurfaceAvailable;
/// <summary>
/// A weak reference of D3DRenderers that have been cloned
/// </summary>
private readonly List<WeakReference> m_clonedD3Drenderers = new List<WeakReference>();
/// <summary>
/// Backing field for the RenderOnCompositionTargetRendering flag.
/// </summary>
private bool m_renderOnCompositionTargetRendering;
/// <summary>
/// Temporary storage for the RenderOnCompositionTargetRendering flag.
/// This is used to remember the value for when the control is loaded and unloaded.
/// </summary>
private bool m_renderOnCompositionTargetRenderingTemp;
#region Stretch
public static readonly DependencyProperty StretchProperty =
DependencyProperty.Register("Stretch", typeof(Stretch), typeof(D3DRenderer),
new FrameworkPropertyMetadata(Stretch.Uniform,
new PropertyChangedCallback(OnStretchChanged)));
/// <summary>
/// Defines what rules are applied to the stretching of the video
/// </summary>
public Stretch Stretch
{
get { return (Stretch)GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
}
private static void OnStretchChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((D3DRenderer)d).OnStretchChanged(e);
}
private void OnStretchChanged(DependencyPropertyChangedEventArgs e)
{
m_videoImage.Stretch = (Stretch) e.NewValue;
}
#endregion
#region IsRenderingEnabled
public static readonly DependencyProperty IsRenderingEnabledProperty =
DependencyProperty.Register("IsRenderingEnabled", typeof(bool), typeof(D3DRenderer),
new FrameworkPropertyMetadata(true));
/// <summary>
/// Enables or disables rendering of the video
/// </summary>
public bool IsRenderingEnabled
{
get { return (bool)GetValue(IsRenderingEnabledProperty); }
set { SetValue(IsRenderingEnabledProperty, value); }
}
#endregion
#region NaturalVideoHeight
private static readonly DependencyPropertyKey NaturalVideoHeightPropertyKey
= DependencyProperty.RegisterReadOnly("NaturalVideoHeight", typeof(int), typeof(MediaElementBase),
new FrameworkPropertyMetadata(0));
public static readonly DependencyProperty NaturalVideoHeightProperty
= NaturalVideoHeightPropertyKey.DependencyProperty;
/// <summary>
/// Gets the natural pixel height of the current media.
/// The value will be 0 if there is no video in the media.
/// </summary>
public int NaturalVideoHeight
{
get { return (int)GetValue(NaturalVideoHeightProperty); }
}
/// <summary>
/// Internal method to set the read-only NaturalVideoHeight DP
/// </summary>
protected void SetNaturalVideoHeight(int value)
{
SetValue(NaturalVideoHeightPropertyKey, value);
}
#endregion
#region NaturalVideoWidth
private static readonly DependencyPropertyKey NaturalVideoWidthPropertyKey
= DependencyProperty.RegisterReadOnly("NaturalVideoWidth", typeof(int), typeof(MediaElementBase),
new FrameworkPropertyMetadata(0));
public static readonly DependencyProperty NaturalVideoWidthProperty
= NaturalVideoWidthPropertyKey.DependencyProperty;
/// <summary>
/// Gets the natural pixel width of the current media.
/// The value will be 0 if there is no video in the media.
/// </summary>
public int NaturalVideoWidth
{
get { return (int)GetValue(NaturalVideoWidthProperty); }
}
/// <summary>
/// Internal method to set the read-only NaturalVideoWidth DP
/// </summary>
protected void SetNaturalVideoWidth(int value)
{
SetValue(NaturalVideoWidthPropertyKey, value);
}
#endregion
#region HasVideo
private static readonly DependencyPropertyKey HasVideoPropertyKey
= DependencyProperty.RegisterReadOnly("HasVideo", typeof(bool), typeof(MediaElementBase),
new FrameworkPropertyMetadata(false));
public static readonly DependencyProperty HasVideoProperty
= HasVideoPropertyKey.DependencyProperty;
/// <summary>
/// Is true if the media contains renderable video
/// </summary>
public bool HasVideo
{
get { return (bool)GetValue(HasVideoProperty); }
}
/// <summary>
/// Internal method for setting the read-only HasVideo DP
/// </summary>
protected void SetHasVideo(bool value)
{
SetValue(HasVideoPropertyKey, value);
}
#endregion
protected D3DRenderer()
{
InitializeD3DVideo();
/* Hook into the framework events */
Loaded += D3DRenderer_Loaded;
Unloaded += D3DRenderer_Unloaded;
}
/// <summary>
/// Handler for when the D3DRenderer is unloaded
/// </summary>
private void D3DRenderer_Unloaded(object sender, RoutedEventArgs e)
{
/* Remember what the property value was */
m_renderOnCompositionTargetRenderingTemp = RenderOnCompositionTargetRendering;
/* Make sure to unhook the static event hook because we are unloading */
RenderOnCompositionTargetRendering = false;
}
/// <summary>
/// Handler for when the D3DRenderer is loaded
/// </summary>
private void D3DRenderer_Loaded(object sender, RoutedEventArgs e)
{
/* Restore the property's value */
RenderOnCompositionTargetRendering = m_renderOnCompositionTargetRenderingTemp;
}
/// <summary>
/// Initializes the D3DRenderer control
/// </summary>
protected virtual void InitializeD3DVideo()
{
if (m_videoImage != null)
return;
/* Create our Image and it's D3DImage source */
m_videoImage = new Image();
m_d3dImage = new D3DImage();
/* We hook into this event to handle when a D3D device is lost */
D3DImage.IsFrontBufferAvailableChanged += D3DImage_IsFrontBufferAvailableChanged;
/* Set our default stretch value of our video */
m_videoImage.Stretch = (Stretch)StretchProperty.DefaultMetadata.DefaultValue;
var activeProfiles = XElement.Load(workingDirectory + "ProfilesDisplay.xml");
var a = activeProfiles.Element("_nABC").Value;
/* Our source of the video image is the D3DImage */
m_videoImage.Source = D3DImage;
/* Register the Image as a visual child */
AddVisualChild(m_videoImage);
}
/// <summary>
/// This should only fire when a D3D device is lost
/// </summary>
private void D3DImage_IsFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (!D3DImage.IsFrontBufferAvailable)
return;
/* Flag that we have a new surface, even
* though we really don't */
m_newSurfaceAvailable = true;
/* Force feed the D3DImage the Surface pointer */
SetBackBufferInternal(m_pBackBuffer);
}
protected override Size MeasureOverride(Size availableSize)
{
m_videoImage.Measure(availableSize);
return m_videoImage.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
m_videoImage.Arrange(new Rect(finalSize));
return finalSize;
}
protected override int VisualChildrenCount
{
get
{
return 1;
}
}
protected override Visual GetVisualChild(int index)
{
if (index > 0)
throw new IndexOutOfRangeException();
return m_videoImage;
}
protected D3DImage D3DImage
{
get
{
return m_d3dImage;
}
}
protected Image VideoImage
{
get
{
return m_videoImage;
}
}
/// <summary>
/// Renders the video with WPF's rendering using the CompositionTarget.Rendering event
/// </summary>
protected bool RenderOnCompositionTargetRendering
{
get
{
return m_renderOnCompositionTargetRendering;
}
set
{
/* If it is being set to true and it was previously false
* then hook into the event */
if (value && !m_renderOnCompositionTargetRendering)
CompositionTarget.Rendering += CompositionTarget_Rendering;
else if(!value)
CompositionTarget.Rendering -= CompositionTarget_Rendering;
m_renderOnCompositionTargetRendering = value;
m_renderOnCompositionTargetRenderingTemp = value;
}
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
InternalInvalidateVideoImage();
}
/// <summary>
/// Used as a clone for a D3DRenderer
/// </summary>
private class ClonedD3DRenderer : D3DRenderer
{}
/// <summary>
/// Creates a clone of the D3DRenderer. This is a work for the visual
/// brush not working cross-threaded
/// </summary>
/// <returns></returns>
public D3DRenderer CloneD3DRenderer()
{
var renderer = new ClonedD3DRenderer();
lock(m_clonedD3Drenderers)
{
m_clonedD3Drenderers.Add(new WeakReference(renderer));
}
renderer.SetBackBuffer(m_pBackBuffer);
return renderer;
}
/// <summary>
/// Cleans up any dead references we may have to any cloned renderers
/// </summary>
private void CleanZombieRenderers()
{
lock(m_clonedD3Drenderers)
{
var deadObjects = new List<WeakReference>();
for (int i = 0; i < m_clonedD3Drenderers.Count; i++)
{
if (!m_clonedD3Drenderers[i].IsAlive)
deadObjects.Add(m_clonedD3Drenderers[i]);
}
foreach(var deadGuy in deadObjects)
{
m_clonedD3Drenderers.Remove(deadGuy);
}
}
}
/// <summary>
/// Configures D3DImage with a new surface. The back buffer is
/// not set until we actually receive a frame, this way we
/// can avoid a black flicker between media changes
/// </summary>
/// <param name="backBuffer">The unmanaged pointer to the Direct3D Surface</param>
protected void SetBackBuffer(IntPtr backBuffer)
{
/* We only do this if target rendering is enabled because we must use an Invoke
* instead of a BeginInvoke to keep the Surfaces in sync and Invoke could be dangerous
* in other situations */
if(RenderOnCompositionTargetRendering)
{
if (!D3DImage.Dispatcher.CheckAccess())
{
D3DImage.Dispatcher.Invoke((Action)(() => SetBackBuffer(backBuffer)), DispatcherPriority.Render);
return;
}
}
/* Flag a new surface */
m_newSurfaceAvailable = true;
m_pBackBuffer = backBuffer;
/* Make a special case for target rendering */
if (RenderOnCompositionTargetRendering)
{
SetBackBufferInternal(m_pBackBuffer);
}
SetBackBufferForClones();
}
/// <summary>
/// Sets the backbuffer for any cloned D3DRenderers
/// </summary>
private void SetBackBufferForClones()
{
lock (m_clonedD3Drenderers)
{
CleanZombieRenderers();
foreach (var rendererRef in m_clonedD3Drenderers)
{
var renderer = rendererRef.Target as D3DRenderer;
if (renderer != null)
renderer.SetBackBuffer(m_pBackBuffer);
}
}
}
/// <summary>
/// Configures D3DImage with a new surface. This happens immediately
/// </summary>
private void SetBackBufferInternal(IntPtr backBuffer)
{
/* Do nothing if we don't have a new surface available */
if (!m_newSurfaceAvailable)
return;
if(!D3DImage.Dispatcher.CheckAccess())
{
D3DImage.Dispatcher.BeginInvoke((Action)(() => SetBackBufferInternal(backBuffer)));
return;
}
/* We have this around a try/catch just in case we
* lose the device and our Surface is invalid. The
* try/catch may not be needed, but testing needs
* to take place before it's removed */
try
{
D3DImage.Lock();
D3DImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, backBuffer);
D3DImage.Unlock();
SetNaturalWidthHeight();
}
catch (Exception)
{ }
/* Clear our flag, so this won't be ran again
* until a new surface is sent */
m_newSurfaceAvailable = false;
}
private void SetNaturalWidthHeight()
{
SetNaturalVideoHeight(m_d3dImage.PixelHeight);
SetNaturalVideoWidth(m_d3dImage.PixelWidth);
}
protected void InvalidateVideoImage()
{
if (!m_renderOnCompositionTargetRendering)
InternalInvalidateVideoImage();
}
/// <summary>
/// Invalidates the entire Direct3D image, notifying WPF to redraw
/// </summary>
protected void InternalInvalidateVideoImage()
{
/* Ensure we run on the correct Dispatcher */
if(!D3DImage.Dispatcher.CheckAccess())
{
D3DImage.Dispatcher.BeginInvoke((Action)(() => InvalidateVideoImage()));
return;
}
/* If there is a new Surface to set,
* this method will do the trick */
SetBackBufferInternal(m_pBackBuffer);
/* Only render the video image if possible, or if IsRenderingEnabled is true */
if (D3DImage.IsFrontBufferAvailable && IsRenderingEnabled && m_pBackBuffer != IntPtr.Zero)
{
try
{
/* Invalidate the entire image */
D3DImage.Lock();
D3DImage.AddDirtyRect(new Int32Rect(0, /* Left */
0, /* Top */
D3DImage.PixelWidth, /* Width */
D3DImage.PixelHeight /* Height */));
D3DImage.Unlock();
}
catch
{ }
}
/* Invalidate all of our cloned D3DRenderers */
InvalidateClonedVideoImages();
}
/// <summary>
/// Invalidates any possible cloned renderer we may have
/// </summary>
private void InvalidateClonedVideoImages()
{
lock(m_clonedD3Drenderers)
{
CleanZombieRenderers();
foreach(var rendererRef in m_clonedD3Drenderers)
{
var renderer = rendererRef.Target as D3DRenderer;
if(renderer != null)
renderer.InvalidateVideoImage();
}
}
}
}
}
In the InitializeD3DVideo() function, I try to read a xml file from a location from the harddisk. But it can't be done.
Is it that the d3drenderer.cs cannot read from a xml file? I just want to read a value from _nABC of the xml file.
This has nothing to do with the D3DRenderer.
The path to the XML file is wrong. There is a '/' (or '\') missing between the workingDirectory and the + "ProfilesDisplay.xml".
If you don't know your path strings, the best method to combine these is System.IO.Path.Combine.
I'm working on an application where I have a WPF front-end and a back-end both written in C#. The back-end is fully made with MVVM, but due to legacy reasons I still have to use old WinForms controls.
For that reason I wrote a wrapper that contain the WinForms control in the back-end and renders its result and transfer the image to the front-end.
The front-end is a plain WPF control that simply takes the Bitmap and draws it onto its render surface.
The only problem is, that event from the front-end are not delegated to the back-end - of course. I therefore intercepted the WndProc from the front-end control and delegated the messages from the front-end to the back-end by the native WinAPI call SendMessage.
Although everything works fine, the interesting thing is, that although I have not intercepted the mouse up event I get the Mouseup event from the control ( I really don't send a WM_MOUSEUP message). This seems to be a bit magic to me and I don't understand where the MouseUp comes from. Also MouseMove events get through if the left mouse button is pressed.
Attached is some demo code, maybe not fully working because the XAML code is missing:
using System;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
namespace ChartDemo
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
// Chart myChart;
BackEndControl Control;
public MainWindow()
{
InitializeComponent();
// myChart = new Chart();
// myChart.Dock = System.Windows.Forms.DockStyle.Fill;
// myChart.BackColor = System.Drawing.Color.Red;
// ChartArea area = new ChartArea();
// myChart.ChartAreas.Add(area);
// Series aSeries = new Series();
// for (int i = 0; i < 100; i++) aSeries.Points.AddXY(i, i);
// myChart.Series.Add(aSeries);
System.Windows.Forms.Button aControl = new System.Windows.Forms.Button();
aControl.Dock = System.Windows.Forms.DockStyle.Fill;
aControl.MouseUp += aControl_MouseUp;
aControl.MouseDown += aControl_MouseDown;
aControl.MouseMove += aControl_MouseMove;
Control = new BackEndControl(aControl);
Content = Control;
}
private void aControl_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
Console.WriteLine("MouseMove");
}
private void aControl_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
Console.WriteLine("MouseDown");
}
private void aControl_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{
Console.WriteLine("MouseUp");
}
}
/// <summary>
/// Back-End Control that wraps native WinForm controls and delegates
/// events back and forth.
/// </summary>
public class BackEndControl : Control, IDisposable
{
HwndSource source;
BitmapSource ScreenCapture;
System.Drawing.Bitmap WindowBitmap;
/// <summary>
/// Holds the associated native WinForms control.
/// </summary>
System.Windows.Forms.Control Control;
/// <summary>
/// Creates a new instance of BackEndControl.
/// </summary>
/// <param name="control"></param>
public BackEndControl(System.Windows.Forms.Control control)
{
Contract.Requires(control != null);
Control = control;
Control.CreateControl();
Control.Invalidated += Control_Invalidated;
Loaded += BackEndControl_Loaded;
}
/// <summary>
/// dtor
/// </summary>
~BackEndControl()
{
Dispose(false);
}
/// <summary>
/// Associate the
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void BackEndControl_Loaded(object sender, RoutedEventArgs e)
{
// Set the initial size to the control.
Control.Size = new System.Drawing.Size((int)ActualWidth, (int)ActualHeight);
source = PresentationSource.FromVisual(this) as HwndSource;
source.AddHook(WndProc);
// Remove the handler again, since there are problems in the Finalizer running in a different finalizer thread
// and the event can onmly be unhooked in the current thread.
Loaded -= BackEndControl_Loaded;
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, Int32 Msg, IntPtr wParam, IntPtr lParam);
public enum WndMessages
{
WM_LBUTTONDOWN = 0x0201,
WM_SIZE = 0x0005,
WM_SIZING = 0x0214,
WM_EXITSIZEMOVE = 0x0232
}
/// <summary>
/// Native MessageProc to intercept native window messages to the WPF control
/// and forward the messages to the original WinForms control.
/// </summary>
/// <param name="hwnd"></param>
/// <param name="msg"></param>
/// <param name="wParam"></param>
/// <param name="lParam"></param>
/// <param name="handled"></param>
/// <returns></returns>
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (hwnd == source.Handle)
{
// Forward only certain messages to the backend winforms control.
switch ((WndMessages)msg)
{
case WndMessages.WM_LBUTTONDOWN:
Console.WriteLine(msg);
return SendMessage(Control.Handle, msg, wParam, lParam);
case WndMessages.WM_SIZING:
Console.WriteLine("PAINT");
InvalidateVisual();
break;
case WndMessages.WM_EXITSIZEMOVE:
Console.WriteLine("EXIT");
Control.Size = new System.Drawing.Size((int)ActualWidth, (int)ActualHeight);
break;
}
}
return IntPtr.Zero;
}
/// <summary>
/// Event-Handler for invalidate events on the control.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Control_Invalidated(object sender, System.Windows.Forms.InvalidateEventArgs e)
{
if (!Control.Size.IsEmpty)
{
if (WindowBitmap != null)
{
if (Control.Size != WindowBitmap.Size)
{
WindowBitmap.Dispose();
WindowBitmap = null;
}
}
if (WindowBitmap == null)
{
WindowBitmap = new System.Drawing.Bitmap(Control.Width, Control.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
}
Control.DrawToBitmap(WindowBitmap, Control.ClientRectangle);
ScreenCapture = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
WindowBitmap.GetHbitmap(),
IntPtr.Zero,
System.Windows.Int32Rect.Empty,
BitmapSizeOptions.FromWidthAndHeight(WindowBitmap.Width, WindowBitmap.Height));
}
InvalidateVisual();
}
/// <summary>
/// Update the view.
/// </summary>
/// <param name="drawingContext"></param>
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
{
base.OnRender(drawingContext);
if (ScreenCapture != null)
{
drawingContext.DrawImage(ScreenCapture, new Rect(0, 0, ActualWidth, ActualHeight));
}
}
#region IDisposable
/// <summary>
/// Gets if the object has been disposed either by Dispose() or by the Finalizer.
/// </summary>
public bool IsDisposed
{
get;
private set;
}
/// <summary>
/// Disposes the allocated resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes the allocated resources.
/// </summary>
/// <param name="disposing"></param>
private void Dispose(bool disposing)
{
if (!IsDisposed)
{
IsDisposed = true;
ScreenCapture = null;
if (source != null)
{
source.RemoveHook(WndProc);
source.Dispose();
source = null;
}
if (WindowBitmap != null)
{
WindowBitmap.Dispose();
WindowBitmap = null;
}
if (Control != null)
{
Control.Invalidated -= Control_Invalidated;
Control.Dispose();
Control = null;
}
}
}
#endregion
}
}
What could be the problem/magic that the events pass through?
Thanks
Martin
I think I have found the problem. It seems that although the control is invisible it still gets the capture and therefore all mouse events are going to the control. In a situation where I have a real front-end/back-end it works fine. If I have the front-end/back-end on the same machine it doesn't work. I'll dig a little deeper to find a possible solution and will then post it here.
anyone know how to raise an event on a ListBox when its redrawn. I'm trying to conditionally mask content in one column but the conditional check seems to be done before the listbox has been drawn and so the mask does not work because there is nothing to mask:
/// <summary>
/// Locks or unlocks the quantity textbox based on 100% flour and activates or deactivate weights
/// </summary>
private void activatePieceQuantity()
{
if (isFlour100Percent())
{
((TextBox)NumberOfItemsTextBox as TextBox).IsEnabled = true;
weightsActive(true);
}
else
{
((TextBox)NumberOfItemsTextBox as TextBox).IsEnabled = false;
weightsActive(false);
}
}
/// <summary>
/// Send controls to search with control name and activate or deactivate flag
/// </summary>
/// <param name="activate"></param>
private void weightsActive(bool activate)
{
int locationInList = 0;
foreach (RecipieIngredient ri in activeRecipie.RecipieIngredients)
{
SearchTree(this.IngredientsListBox.ItemContainerGenerator.ContainerFromIndex(locationInList), "QuanityWeight", activate);
locationInList++;
}
}
/// <summary>
/// Find all weight related objects in the ingredients list and set the visibility accordingly
/// </summary>
/// <param name="targetElement"></param>
/// <param name="flagName">Derived from the Tag of the textbox</param>
/// <param name="enableFlag"></param>
private void SearchTree(DependencyObject targetElement, string flagName, bool enableFlag)
{
if (targetElement == null)
return;
var count = VisualTreeHelper.GetChildrenCount(targetElement);
if (count == 0)
return;
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(targetElement, i);
if (child is TextBlock)
{
TextBlock targetItem = (TextBlock)child;
if (targetItem.Name == flagName)
if (enableFlag)
{
((TextBlock)targetItem as TextBlock).Visibility = Visibility.Visible;
return;
}
else
{
((TextBlock)targetItem as TextBlock).Visibility = Visibility.Collapsed;
}
}
else
{
SearchTree(child, flagName, enableFlag);
}
}
}
I got it now, the problem was that the ListBox was not drawn when the SearchTree function was called so there was never any DependencyObject to pass to it.
I solved the problem (somewhat hackish in my opinion) by placing a flag in the code to say that the check had been done and then calling the masking function from a LayoutUpdated event
private void IngredientsListBox_LayoutUpdated(object sender, EventArgs e)
{
if (ingredientsListLoaded)
{
activatePieceQuantity();
ingredientsListLoaded = false;
}
}
I've added a trackbar to menu strip manually because vs 2008 doesn't allow me to do.
However, i can't get the value of trackbar.
Form1.cs:
[ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.MenuStrip |
ToolStripItemDesignerAvailability.ContextMenuStrip)]
public class TrackBarMenuItem : ToolStripControlHost
{
private TrackBar trackBar;
public TrackBarMenuItem()
: base(new TrackBar())
{
this.trackBar = this.Control as TrackBar;
trackBar.TickFrequency = 1;
trackBar.Maximum = 255;
trackBar.LargeChange = 5;
trackBar.SmallChange = 2;
}
}
Form1.Designer.cs:
private TrackBarMenuItem trackBar1;
//
// trackBar1
//
this.trackBar1.Name = "trackBar1";
this.trackBar1.Size = new System.Drawing.Size(104, 25);
and this is how i need to use it:
private void trackBar1_Scroll(object sender, System.EventArgs e)
{
int valueB = trackBar1.Value;
pictureBox2.Image = Deneme(new Bitmap(pictureBox1.Image),valueB);
}
but i get this error:
Error 1 'goruntuIsleme2.Form1.TrackBarMenuItem' does not contain a
definition for 'Value' and no extension method 'Value' accepting a
first argument of type 'goruntuIsleme2.Form1.TrackBarMenuItem' could
be found (are you missing a using directive or an assembly reference?)
any ideas?
Expose the value of the internal Trackbar object as a property on your new TrackBarMenuItem class:
Value { get { return trackBar.Value; } set { trackBar.Value = value; } }
i am adding the solution i found. someone might need it:
[System.ComponentModel.DesignerCategory("code")]
[ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.ContextMenuStrip | ToolStripItemDesignerAvailability.MenuStrip)]
public partial class ToolStripMenuItem : ToolStripControlHost
{
public ToolStripMenuItem()
: base(CreateControlInstance())
{
}
/// <summary>
/// Create a strongly typed property called TrackBar - handy to prevent casting everywhere.
/// </summary>
public TrackBar TrackBar
{
get
{
return Control as TrackBar;
}
}
/// <summary>
/// Create the actual control, note this is static so it can be called from the
/// constructor.
///
/// </summary>
/// <returns></returns>
private static Control CreateControlInstance()
{
TrackBar t = new TrackBar();
t.AutoSize = false;
t.Height = 16;
t.Maximum = 255;
// Add other initialization code here.
return t;
}
[DefaultValue(0)]
public int Value
{
get { return TrackBar.Value; }
set { TrackBar.Value = value; }
}
/// <summary>
/// Attach to events we want to re-wrap
/// </summary>
/// <param name="control"></param>
protected override void OnSubscribeControlEvents(Control control)
{
base.OnSubscribeControlEvents(control);
TrackBar trackBar = control as TrackBar;
trackBar.ValueChanged += new EventHandler(trackBar_ValueChanged);
}
/// <summary>
/// Detach from events.
/// </summary>
/// <param name="control"></param>
protected override void OnUnsubscribeControlEvents(Control control)
{
base.OnUnsubscribeControlEvents(control);
TrackBar trackBar = control as TrackBar;
trackBar.ValueChanged -= new EventHandler(trackBar_ValueChanged);
}
/// <summary>
/// Routing for event
/// TrackBar.ValueChanged -> ToolStripTrackBar.ValueChanged
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void trackBar_ValueChanged(object sender, EventArgs e)
{
// when the trackbar value changes, fire an event.
if (this.ValueChanged != null)
{
ValueChanged(sender, e);
}
}
// add an event that is subscribable from the designer.
public event EventHandler ValueChanged;
// set other defaults that are interesting
protected override Size DefaultSize
{
get
{
return new Size(200, 16);
}
}
}
if you add this to your code, you will be able to add trackbars as ToolStripMenuItem via Designer.
Does your class TrackBarMenuItem has a property called Value? If not, you have to add it.