WPF -> WinForms interop in BackEnd / FrontEnd behaviour - c#

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.

Related

Can put xml reader function into D3drenderer.cs?

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.

Custom C# button's DialogResult doesn't work correctly on modal form

Having a little trouble making a Winforms ImageButton class (C#). My ImageButton class implements the IButtonControl interface but when I add it to a form, and set the button's DialogResult to 'OK' , and then call ShowDialog() on the form, pressing the button does not close the form and return the DialogResult, as the normal Winforms Button control does.
Here's my implementation of ImageButton, feel free to do whatever you like with it.
/// <summary>
/// A control that displays an image and responds to mouse clicks on the image.
/// </summary>
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
[Designer(typeof(ImageButtonDesigner))]
[ToolboxItem(typeof(ImageButtonToolboxItem))]
public class ImageButton : PictureBox, IButtonControl, INotifyPropertyChanged
{
#region Constructors
/// <summary>
/// Initializes a new instance of the ImageButton class using the default initial values.
/// </summary>
public ImageButton()
{
DoubleBuffered = true;
BackColor = Color.Transparent;
SizeMode = PictureBoxSizeMode.AutoSize;
}
#endregion
#region Properties
/// <summary>
/// Backing field for the DialogResult property.
/// </summary>
private DialogResult dialogResult;
/// <summary>
/// Gets or sets a value that is returned to the parent form when the button is clicked.
/// </summary>
[Category("Behavior")]
[Description("The dialog-box result produced in a modal form by clicking the button")]
public DialogResult DialogResult
{
get
{
return dialogResult;
}
set
{
if (Enum.IsDefined(typeof(DialogResult), value))
dialogResult = value;
else
throw new InvalidEnumArgumentException("value", (int)value, typeof(DialogResult));
}
}
/// <summary>
/// Backing field for the Idle property.
/// </summary>
private Image idle;
/// <summary>
/// The image that will be displayed on the control when the mouse is not over a visible part of it.
/// </summary>
[Category("Appearance")]
[Description("The image that will be displayed on the Control when the mouse is not over a visible part of it.")]
public Image Idle
{
get
{
return idle;
}
set
{
idle = value;
NotifyPropertyChanged();
}
}
/// <summary>
/// Backing field for the Mouseover property
/// </summary>
private Image mouseover;
/// <summary>
/// The image that will be displayed on the control when the mouse is over a visible part of it.
/// </summary>
[Category("Appearance")]
[Description("The image that will be displayed on the control when the mouse is over a visible part of it.")]
public Image Mouseover
{
get { return mouseover; }
set
{
mouseover = value;
NotifyPropertyChanged();
}
}
/// <summary>
/// Backing field for the Mousedown property
/// </summary>
private Image mousedown;
/// <summary>
/// The image that will be displayed on the control when the left mouse button is held down and the mouse is over a visible part of it.
/// </summary>
[Category("Appearance")]
[Description("The image that will be displayed on the control when the left mouse button is held down and the mouse is over a visible part of it.")]
public Image Mousedown
{
get { return mousedown; }
set
{
mousedown = value;
NotifyPropertyChanged();
}
}
/// <summary>
/// Gets or sets the text associated with the control.
/// </summary>
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Category("Appearance")]
[Description("The text associated with the control.")]
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
}
}
/// <summary>
/// Gets or sets the font of the text displayed by the control.
/// </summary>
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Category("Appearance")]
[Description("The font used to display text in the control.")]
public override Font Font
{
get
{
return base.Font;
}
set
{
base.Font = value;
}
}
/// <summary>
/// Gets or sets the image that is displayed by the PictureBox.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Browsable(false)]
[Category("Appearance")]
[Description("The image displayed in the PictureBox.")]
new public Image Image
{
get
{
return base.Image;
}
set
{
base.Image = value;
}
}
/// <summary>
/// Backing field for the ButtonState property.
/// </summary>
private ButtonStates buttonState = ButtonStates.None;
/// <summary>
/// The current state of the button.
/// </summary>
private ButtonStates ButtonState
{
get { return buttonState; }
set
{
buttonState = value;
NotifyPropertyChanged();
}
}
/// <summary>
/// Gets the default size of the control.
/// </summary>
protected override Size DefaultSize
{
get
{
return new Size(75, 23);
}
}
#endregion
#region Enums
/// <summary>
/// Specifies the current state of a button.
/// </summary>
[Flags]
private enum ButtonStates : byte
{
/// <summary>
///
/// </summary>
[Description("")]
None = 0,
/// <summary>
///
/// </summary>
[Description("")]
Default = 1 << 0,
/// <summary>
///
/// </summary>
[Description("")]
Mouseover = 1 << 1,
/// <summary>
///
/// </summary>
[Description("")]
Mousedown = 1 << 2
}
#endregion
#region Events
/// <summary>
/// Occurs when a property value changes.
/// </summary>
[Category("Property Changed")]
[Description("Occurs when a property value changes.")]
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Methods
/// <summary>
/// Raises the System.ComponentModel.PropertyChanged event.
/// </summary>
/// <param name="propertyName">the name of the property that changed.</param>
protected virtual void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
if (propertyName == "Idle")
Image = Idle;
}
/// <summary>
/// Notifies the button whether it is the default button so that it can adjust its appearance accordingly.
/// </summary>
/// <param name="value">true if the button is to have the appearance of the default button; otherwise, false.</param>
public void NotifyDefault(bool value)
{
ButtonState = value ? ButtonState | ButtonStates.Default : ButtonState & ~ButtonStates.Default;
}
/// <summary>
/// Generates a Click event for a button.
/// </summary>
public void PerformClick()
{
if (CanSelect)
{
OnClick(EventArgs.Empty);
}
}
/// <summary>
/// Raises the System.Windows.Control.TextChanged event.
/// </summary>
/// <param name="e">A System.EventArgs that contains the event data.</param>
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
Refresh();
}
/// <summary>
/// Raises the System.Windows.Forms.Paint event.
/// </summary>
/// <param name="pe">A PaintEventArgs that contains the event data.</param>
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
if ((!string.IsNullOrEmpty(Text)) && (pe != null) && (base.Font != null))
{
SolidBrush drawBrush = new SolidBrush(base.ForeColor);
// Calculate the size of the text that will be drawn onto the control.
SizeF drawStringSize = pe.Graphics.MeasureString(base.Text, base.Font);
// The registration point used to draw the text.
PointF drawPoint;
if (base.Image != null)
drawPoint = new PointF(base.Image.Width / 2 - drawStringSize.Width / 2, base.Image.Height / 2 - drawStringSize.Height / 2);
else
drawPoint = new PointF(base.Width / 2 - drawStringSize.Width / 2, base.Height / 2 - drawStringSize.Height / 2);
pe.Graphics.DrawString(base.Text, base.Font, drawBrush, drawPoint);
}
}
/// <summary>
/// Raises the System.Windows.Forms.MouseEnter event.
/// </summary>
/// <param name="e">A System.EventArgs that contains the event data.</param>
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
ButtonState |= ButtonStates.Mouseover;
Image = Mouseover;
}
/// <summary>
/// Raises the System.Windows.Forms.MouseLeave event.
/// </summary>
/// <param name="e">A System.EventArgs that contains the event data.</param>
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
ButtonState &= ~ButtonStates.Mouseover;
Image = Idle;
}
/// <summary>
/// Raises the System.Windows.Forms.MouseDown event.
/// </summary>
/// <param name="e">A System.Windows.Forms.MouseEventArgs that contains the event data.</param>
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
ButtonState |= ButtonStates.Mousedown;
Image = Mousedown;
}
/// <summary>
/// Raises the System.Windows.Forms.MouseUp event.
/// </summary>
/// <param name="e">A System.Windows.Forms.MouseEventArgs that contains the event data.</param>
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
ButtonState &= ~ButtonStates.Mousedown;
Image = ((ButtonState & ButtonStates.Mouseover) != 0) ? Mouseover : idle;
}
#endregion
}
[Serializable]
internal class ImageButtonToolboxItem : ToolboxItem
{
public ImageButtonToolboxItem() : base(typeof(ImageButton)) { }
protected ImageButtonToolboxItem(SerializationInfo info, StreamingContext context)
{
Deserialize(info, context);
}
protected override IComponent[] CreateComponentsCore(IDesignerHost host)
{
ImageButton imageButton = (ImageButton)host.CreateComponent(typeof(ImageButton));
Assembly assembly = Assembly.GetAssembly(typeof(ImageButton));
using (Stream streamMouseover = assembly.GetManifestResourceStream("Mvc.Mouseover.png"))
using (Stream streamMousedown = assembly.GetManifestResourceStream("Mvc.Mousedown.png"))
using (Stream streamIdle = assembly.GetManifestResourceStream("Mvc.Idle.png"))
{
imageButton.Idle = Image.FromStream(streamIdle);
imageButton.Mouseover = Image.FromStream(streamMouseover);
imageButton.Mousedown = Image.FromStream(streamMousedown);
}
return new IComponent[] { imageButton };
}
}
internal class ImageButtonDesigner : ControlDesigner
{
protected override void PostFilterAttributes(System.Collections.IDictionary attributes)
{
base.PostFilterAttributes(attributes);
Attribute dockingBehaviour = new DockingAttribute(DockingBehavior.Never);
attributes[typeof(DockingAttribute)] = dockingBehaviour;
}
public override SelectionRules SelectionRules
{
get
{
return SelectionRules.Moveable;
}
}
}
Sorry about the messy toolbox item and designer code..
My Question is, Does there need to be anything special implementation wise to get a button control to work on a modal form (in the same way the normal button does)
(NB. The Form's BorderStyle property is set to None, don't know if thats important)
Thanks, in advance!
You need to actually apply the assigned DialogResult property, it is not automatic. Do so by overriding the OnClick() method:
protected override void OnClick(EventArgs e) {
var form = this.FindForm();
if (form != null) form.DialogResult = dialogResult;
base.OnClick(e);
}
Technically you should also notify accessibility clients of the state change, it isn't clear at all whether you care about this. It tends to be skipped for custom controls but you generally ought to. Insert these statements before the base.OnClick() call:
base.AccessibilityNotifyClients(AccessibleEvents.StateChange, -1);
base.AccessibilityNotifyClients(AccessibleEvents.NameChange, -1);

Is there a simple way to avoid or stop FileSystemWatcher raise event twice in C#?

I want to know what how to avoid or stop FileSystemWatcher raise event twice in C#? I have a solution that will detect everytime if there is newly created xml file from a folder. I test my application using creating xml file using notepad but from the listbox it displays twice.
How can I fix this issue?
Here is my code:
private void fileSystemWatcher1_Created(object sender, System.IO.FileSystemEventArgs e)
{
try
{
fileSystemWatcher1.EnableRaisingEvents = false;
listBox1.Items.Add(e.FullPath);
}
finally
{
fileSystemWatcher1.EnableRaisingEvents = true;
}
}
private void button1_Click(object sender, EventArgs e)
{
DialogResult dialogSelectFolder = folderBrowserDialog1.ShowDialog();
if (dialogSelectFolder.ToString() == "OK")
{
textBox1.Text = folderBrowserDialog1.SelectedPath;
button2.Enabled = true;
}
}
private void button2_Click(object sender, EventArgs e)
{
button1.Enabled = false;
button2.Enabled = false;
button3.Enabled = true;
fileSystemWatcher1.EnableRaisingEvents = true;
fileSystemWatcher1.Path = textBox1.Text;
fileSystemWatcher1.Filter = "*.xml";
}
private void button3_Click(object sender, EventArgs e)
{
button1.Enabled = true;
button3.Enabled = false;
textBox1.Text = "";
fileSystemWatcher1.EnableRaisingEvents = false;
}
}
I have stumbled upon this problem myself twice and i created a class that helps you get only one event at a time. You might also get false events when the file is not in read mode (such as when you copy a file).
You have to create a queue and store all events there and if a time interval passes then raise the appropriate event.
Unfortunately this is not a simple function thus i will include complete code.
using System;
using System.IO;
using System.Timers;
using System.Collections;
using System.ComponentModel;
namespace menelabs.core
{
/// <summary>
/// This class wraps FileSystemEventArgs and RenamedEventArgs objects and detection of duplicate events.
/// </summary>
internal class DelayedEvent
{
private readonly FileSystemEventArgs _args;
/// <summary>
/// Only delayed events that are unique will be fired.
/// </summary>
private bool _delayed;
public DelayedEvent(FileSystemEventArgs args)
{
_delayed = false;
_args = args;
}
public FileSystemEventArgs Args
{
get
{
return _args;
}
}
public bool Delayed
{
get
{
return _delayed;
}
set
{
_delayed = value;
}
}
public virtual bool IsDuplicate(object obj)
{
DelayedEvent delayedEvent = obj as DelayedEvent;
if (delayedEvent == null)
return false; // this is not null so they are different
FileSystemEventArgs eO1 = _args;
RenamedEventArgs reO1 = _args as RenamedEventArgs;
FileSystemEventArgs eO2 = delayedEvent._args;
RenamedEventArgs reO2 = delayedEvent._args as RenamedEventArgs;
// The events are equal only if they are of the same type (reO1 and reO2
// are both null or NOT NULL) and have all properties equal.
// We also eliminate Changed events that follow recent Created events
// because many apps create new files by creating an empty file and then
// they update the file with the file content.
return ((eO1 != null && eO2 != null && eO1.ChangeType == eO2.ChangeType
&& eO1.FullPath == eO2.FullPath && eO1.Name == eO2.Name) &&
((reO1 == null & reO2 == null) || (reO1 != null && reO2 != null &&
reO1.OldFullPath == reO2.OldFullPath && reO1.OldName == reO2.OldName))) ||
(eO1 != null && eO2 != null && eO1.ChangeType == WatcherChangeTypes.Created
&& eO2.ChangeType == WatcherChangeTypes.Changed
&& eO1.FullPath == eO2.FullPath && eO1.Name == eO2.Name);
}
}
/// <summary>
/// This class wraps a FileSystemWatcher object. The class is not derived
/// from FileSystemWatcher because most of the FileSystemWatcher methods
/// are not virtual. The class was designed to resemble FileSystemWatcher class
/// as much as possible so that you can use FileSystemSafeWatcher instead
/// of FileSystemWatcher objects.
/// FileSystemSafeWatcher will capture all events from the FileSystemWatcher object.
/// The captured events will be delayed by at least ConsolidationInterval milliseconds in order
/// to be able to eliminate duplicate events. When duplicate events are found, the last event
/// is droped and the first event is fired (the reverse is not recomended because it could
/// cause some events not be fired at all since the last event will become the first event and
/// it won't fire a if a new similar event arrives imediately afterwards).
/// </summary>
internal class FileSystemSafeWatcher
{
private readonly FileSystemWatcher _fileSystemWatcher;
/// <summary>
/// Lock order is _enterThread, _events.SyncRoot
/// </summary>
private readonly object _enterThread = new object(); // Only one timer event is processed at any given moment
private ArrayList _events;
private Timer _serverTimer;
private int _consolidationInterval = 1000; // milliseconds
#region Delegate to FileSystemWatcher
public FileSystemSafeWatcher()
{
_fileSystemWatcher = new FileSystemWatcher();
Initialize();
}
public FileSystemSafeWatcher(string path)
{
_fileSystemWatcher = new FileSystemWatcher(path);
Initialize();
}
public FileSystemSafeWatcher(string path, string filter)
{
_fileSystemWatcher = new FileSystemWatcher(path, filter);
Initialize();
}
/// <summary>
/// Gets or sets a value indicating whether the component is enabled.
/// </summary>
/// <value>true if the component is enabled; otherwise, false. The default is false. If you are using the component on a designer in Visual Studio 2005, the default is true.</value>
public bool EnableRaisingEvents
{
get
{
return _fileSystemWatcher.EnableRaisingEvents;
}
set
{
_fileSystemWatcher.EnableRaisingEvents = value;
if (value)
{
_serverTimer.Start();
}
else
{
_serverTimer.Stop();
_events.Clear();
}
}
}
/// <summary>
/// Gets or sets the filter string, used to determine what files are monitored in a directory.
/// </summary>
/// <value>The filter string. The default is "*.*" (Watches all files.)</value>
public string Filter
{
get
{
return _fileSystemWatcher.Filter;
}
set
{
_fileSystemWatcher.Filter = value;
}
}
/// <summary>
/// Gets or sets a value indicating whether subdirectories within the specified path should be monitored.
/// </summary>
/// <value>true if you want to monitor subdirectories; otherwise, false. The default is false.</value>
public bool IncludeSubdirectories
{
get
{
return _fileSystemWatcher.IncludeSubdirectories;
}
set
{
_fileSystemWatcher.IncludeSubdirectories = value;
}
}
/// <summary>
/// Gets or sets the size of the internal buffer.
/// </summary>
/// <value>The internal buffer size. The default is 8192 (8K).</value>
public int InternalBufferSize
{
get
{
return _fileSystemWatcher.InternalBufferSize;
}
set
{
_fileSystemWatcher.InternalBufferSize = value;
}
}
/// <summary>
/// Gets or sets the type of changes to watch for.
/// </summary>
/// <value>One of the System.IO.NotifyFilters values. The default is the bitwise OR combination of LastWrite, FileName, and DirectoryName.</value>
/// <exception cref="System.ArgumentException">The value is not a valid bitwise OR combination of the System.IO.NotifyFilters values.</exception>
public NotifyFilters NotifyFilter
{
get
{
return _fileSystemWatcher.NotifyFilter;
}
set
{
_fileSystemWatcher.NotifyFilter = value;
}
}
/// <summary>
/// Gets or sets the path of the directory to watch.
/// </summary>
/// <value>The path to monitor. The default is an empty string ("").</value>
/// <exception cref="System.ArgumentException">The specified path contains wildcard characters.-or- The specified path contains invalid path characters.</exception>
public string Path
{
get
{
return _fileSystemWatcher.Path;
}
set
{
_fileSystemWatcher.Path = value;
}
}
/// <summary>
/// Gets or sets the object used to marshal the event handler calls issued as a result of a directory change.
/// </summary>
/// <value>The System.ComponentModel.ISynchronizeInvoke that represents the object used to marshal the event handler calls issued as a result of a directory change. The default is null.</value>
public ISynchronizeInvoke SynchronizingObject
{
get
{
return _fileSystemWatcher.SynchronizingObject;
}
set
{
_fileSystemWatcher.SynchronizingObject = value;
}
}
/// <summary>
/// Occurs when a file or directory in the specified System.IO.FileSystemWatcher.Path is changed.
/// </summary>
public event FileSystemEventHandler Changed;
/// <summary>
/// Occurs when a file or directory in the specified System.IO.FileSystemWatcher.Path is created.
/// </summary>
public event FileSystemEventHandler Created;
/// <summary>
/// Occurs when a file or directory in the specified System.IO.FileSystemWatcher.Path is deleted.
/// </summary>
public event FileSystemEventHandler Deleted;
/// <summary>
/// Occurs when the internal buffer overflows.
/// </summary>
public event ErrorEventHandler Error;
/// <summary>
/// Occurs when a file or directory in the specified System.IO.FileSystemWatcher.Path is renamed.
/// </summary>
public event RenamedEventHandler Renamed;
/// <summary>
/// Begins the initialization of a System.IO.FileSystemWatcher used on a form or used by another component. The initialization occurs at run time.
/// </summary>
public void BeginInit()
{
_fileSystemWatcher.BeginInit();
}
/// <summary>
/// Releases the unmanaged resources used by the System.IO.FileSystemWatcher and optionally releases the managed resources.
/// </summary>
public void Dispose()
{
Uninitialize();
}
/// <summary>
/// Ends the initialization of a System.IO.FileSystemWatcher used on a form or used by another component. The initialization occurs at run time.
/// </summary>
public void EndInit()
{
_fileSystemWatcher.EndInit();
}
/// <summary>
/// Raises the System.IO.FileSystemWatcher.Changed event.
/// </summary>
/// <param name="e">A System.IO.FileSystemEventArgs that contains the event data.</param>
protected void OnChanged(FileSystemEventArgs e)
{
if (Changed != null)
Changed(this, e);
}
/// <summary>
/// Raises the System.IO.FileSystemWatcher.Created event.
/// </summary>
/// <param name="e">A System.IO.FileSystemEventArgs that contains the event data.</param>
protected void OnCreated(FileSystemEventArgs e)
{
if (Created != null)
Created(this, e);
}
/// <summary>
/// Raises the System.IO.FileSystemWatcher.Deleted event.
/// </summary>
/// <param name="e">A System.IO.FileSystemEventArgs that contains the event data.</param>
protected void OnDeleted(FileSystemEventArgs e)
{
if (Deleted != null)
Deleted(this, e);
}
/// <summary>
/// Raises the System.IO.FileSystemWatcher.Error event.
/// </summary>
/// <param name="e">An System.IO.ErrorEventArgs that contains the event data.</param>
protected void OnError(ErrorEventArgs e)
{
if (Error != null)
Error(this, e);
}
/// <summary>
/// Raises the System.IO.FileSystemWatcher.Renamed event.
/// </summary>
/// <param name="e">A System.IO.RenamedEventArgs that contains the event data.</param>
protected void OnRenamed(RenamedEventArgs e)
{
if (Renamed != null)
Renamed(this, e);
}
/// <summary>
/// A synchronous method that returns a structure that contains specific information on the change that occurred, given the type of change you want to monitor.
/// </summary>
/// <param name="changeType">The System.IO.WatcherChangeTypes to watch for.</param>
/// <returns>A System.IO.WaitForChangedResult that contains specific information on the change that occurred.</returns>
public WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType)
{
//TODO
throw new NotImplementedException();
}
/// <summary>
/// A synchronous method that returns a structure that contains specific information on the change that occurred, given the type of change you want to monitor
/// and the time (in milliseconds) to wait before timing out.
/// </summary>
/// <param name="changeType">The System.IO.WatcherChangeTypes to watch for.</param>
/// <param name="timeout">The time (in milliseconds) to wait before timing out.</param>
/// <returns>A System.IO.WaitForChangedResult that contains specific information on the change that occurred.</returns>
public WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType, int timeout)
{
//TODO
throw new NotImplementedException();
}
#endregion
#region Implementation
private void Initialize()
{
_events = ArrayList.Synchronized(new ArrayList(32));
_fileSystemWatcher.Changed += new FileSystemEventHandler(this.FileSystemEventHandler);
_fileSystemWatcher.Created += new FileSystemEventHandler(this.FileSystemEventHandler);
_fileSystemWatcher.Deleted += new FileSystemEventHandler(this.FileSystemEventHandler);
_fileSystemWatcher.Error += new ErrorEventHandler(this.ErrorEventHandler);
_fileSystemWatcher.Renamed += new RenamedEventHandler(this.RenamedEventHandler);
_serverTimer = new Timer(_consolidationInterval);
_serverTimer.Elapsed += new ElapsedEventHandler(this.ElapsedEventHandler);
_serverTimer.AutoReset = true;
_serverTimer.Enabled = _fileSystemWatcher.EnableRaisingEvents;
}
private void Uninitialize()
{
if (_fileSystemWatcher != null)
_fileSystemWatcher.Dispose();
if (_serverTimer != null)
_serverTimer.Dispose();
}
private void FileSystemEventHandler(object sender, FileSystemEventArgs e)
{
_events.Add(new DelayedEvent(e));
}
private void ErrorEventHandler(object sender, ErrorEventArgs e)
{
OnError(e);
}
private void RenamedEventHandler(object sender, RenamedEventArgs e)
{
_events.Add(new DelayedEvent(e));
}
private void ElapsedEventHandler(Object sender, ElapsedEventArgs e)
{
// We don't fire the events inside the lock. We will queue them here until
// the code exits the locks.
Queue eventsToBeFired = null;
if (System.Threading.Monitor.TryEnter(_enterThread))
{
// Only one thread at a time is processing the events
try
{
eventsToBeFired = new Queue(32);
// Lock the collection while processing the events
lock (_events.SyncRoot)
{
DelayedEvent current;
for (int i = 0; i < _events.Count; i++)
{
current = _events[i] as DelayedEvent;
if (current.Delayed)
{
// This event has been delayed already so we can fire it
// We just need to remove any duplicates
for (int j = i + 1; j < _events.Count; j++)
{
if (current.IsDuplicate(_events[j]))
{
// Removing later duplicates
_events.RemoveAt(j);
j--; // Don't skip next event
}
}
bool raiseEvent = true;
if (current.Args.ChangeType == WatcherChangeTypes.Created || current.Args.ChangeType == WatcherChangeTypes.Changed)
{
//check if the file has been completely copied (can be opened for read)
FileStream stream = null;
try
{
stream = File.Open(current.Args.FullPath, FileMode.Open, FileAccess.Read, FileShare.None);
// If this succeeds, the file is finished
}
catch (IOException)
{
raiseEvent = false;
}
finally
{
if (stream != null) stream.Close();
}
}
if (raiseEvent)
{
// Add the event to the list of events to be fired
eventsToBeFired.Enqueue(current);
// Remove it from the current list
_events.RemoveAt(i);
i--; // Don't skip next event
}
}
else
{
// This event was not delayed yet, so we will delay processing
// this event for at least one timer interval
current.Delayed = true;
}
}
}
}
finally
{
System.Threading.Monitor.Exit(_enterThread);
}
}
// else - this timer event was skipped, processing will happen during the next timer event
// Now fire all the events if any events are in eventsToBeFired
RaiseEvents(eventsToBeFired);
}
public int ConsolidationInterval
{
get
{
return _consolidationInterval;
}
set
{
_consolidationInterval = value;
_serverTimer.Interval = value;
}
}
protected void RaiseEvents(Queue deQueue)
{
if ((deQueue != null) && (deQueue.Count > 0))
{
DelayedEvent de;
while (deQueue.Count > 0)
{
de = deQueue.Dequeue() as DelayedEvent;
switch (de.Args.ChangeType)
{
case WatcherChangeTypes.Changed:
OnChanged(de.Args);
break;
case WatcherChangeTypes.Created:
OnCreated(de.Args);
break;
case WatcherChangeTypes.Deleted:
OnDeleted(de.Args);
break;
case WatcherChangeTypes.Renamed:
OnRenamed(de.Args as RenamedEventArgs);
break;
}
}
}
}
#endregion
}
}
You may find the code at:
https://github.com/melenaos/FileSystemSafeWatcher/blob/master/FileSystemSafeWatcher.cs

How to add Gif image to wpf

Now I am developing on one project with WPF and C#, some parts it related to animation image.
So can everyone tell me how to add .gif file the the form ?
Thanks.
This is a complete code,you have to just include below class in to your main namesapce.
and your main wpf form call it like this,
<UI:AnimatedGIFControl x:Name="GIFCtrl" Visibility="Collapsed" VerticalAlignment="Center" Margin="200,0,0,0" Width="380" Height="48"/>
where UI is the namesapce where AnimatedGIFControl is defined.you can call it like this
xmlns:UI="clr-namespace:xyz"
The Complete class code is ,
class AnimatedGIFControl : System.Windows.Controls.Image
{
private Bitmap _bitmap; // Local bitmap member to cache image resource
private BitmapSource _bitmapSource;
public delegate void FrameUpdatedEventHandler();
/// <summary>
/// Delete local bitmap resource
/// Reference: http://msdn.microsoft.com/en-us/library/dd183539(VS.85).aspx
/// </summary>
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool DeleteObject(IntPtr hObject);
/// <summary>
/// Override the OnInitialized method
/// </summary>
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
this.Loaded += new RoutedEventHandler(AnimatedGIFControl_Loaded);
this.Unloaded += new RoutedEventHandler(AnimatedGIFControl_Unloaded);
}
/// <summary>
/// Load the embedded image for the Image.Source
/// </summary>
void AnimatedGIFControl_Loaded(object sender, RoutedEventArgs e)
{
// Get GIF image from Resources
if (Properties.Resources.ProgressIndicator != null)
{
_bitmap = Properties.Resources.ProgressIndicator;
Width = _bitmap.Width;
Height = _bitmap.Height;
_bitmapSource = GetBitmapSource();
Source = _bitmapSource;
}
}
/// <summary>
/// Close the FileStream to unlock the GIF file
/// </summary>
private void AnimatedGIFControl_Unloaded(object sender, RoutedEventArgs e)
{
StopAnimate();
}
/// <summary>
/// Start animation
/// </summary>
public void StartAnimate()
{
ImageAnimator.Animate(_bitmap, OnFrameChanged);
}
/// <summary>
/// Stop animation
/// </summary>
public void StopAnimate()
{
ImageAnimator.StopAnimate(_bitmap, OnFrameChanged);
}
/// <summary>
/// Event handler for the frame changed
/// </summary>
private void OnFrameChanged(object sender, EventArgs e)
{
Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new FrameUpdatedEventHandler(FrameUpdatedCallback));
}
private void FrameUpdatedCallback()
{
ImageAnimator.UpdateFrames();
if (_bitmapSource != null)
_bitmapSource.Freeze();
// Convert the bitmap to BitmapSource that can be display in WPF Visual Tree
_bitmapSource = GetBitmapSource();
Source = _bitmapSource;
InvalidateVisual();
}
private BitmapSource GetBitmapSource()
{
IntPtr handle = IntPtr.Zero;
try
{
handle = _bitmap.GetHbitmap();
_bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(
handle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
}
finally
{
if (handle != IntPtr.Zero)
DeleteObject(handle);
}
return _bitmapSource;
}
}
and use like this
GIFCtrl.StartAnimate();
hope it may help you.
Unfortunately in WPF Gif animation is not visible. Even if you load one in a image control, you will only see its first frame.
This isn't supported by Image, but some of the solutions on this duplicate answer using a custom control or wrapping a WinForms control seem to be good solutions.

How to horizontally scroll in WPF using mouse tilt wheel?

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();
}
}

Categories

Resources