WPF Window Drag/Move Boundary - wpf

just curious if you know of any way to setup a drag boundary for a window?
It would be nice to have these properties:
Me.MinLeft = 10
Me.MinTop = 10
Me.MaxLeft = 150
Me.MaxTop = 150
Those are made up properties, btw, which would be nice to have.
I know I could probably setup a timer to fire ever 10th of a second and check the left and top and then move it back if it's over. But it would be more elegant to have the window act like it hit a wall and can't go any farther, like moving to the edge of the screen or something similar.
Edit: There seems to be some confusion somewhere, the point I'm trying to make is in the paragraph above, dragging, not re-sizing.

Here is teh "magic" you need to create this functionality, all you have to do is set the Window_SourceInitialized method to the window's SourceInitialized event and insert you logic where the big comment is.
I combined this code from several sources, so there could be some syntax errors in it.
internal enum WM
{
WINDOWPOSCHANGING = 0x0046,
}
[StructLayout(LayoutKind.Sequential)]
internal struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public int flags;
}
private void Window_SourceInitialized(object sender, EventArgs ea)
{
HwndSource hwndSource = (HwndSource)HwndSource.FromVisual((Window)sender);
hwndSource.AddHook(DragHook);
}
private static IntPtr DragHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handeled)
{
switch ((WM)msg)
{
case WM.WINDOWPOSCHANGING:
{
WINDOWPOS pos = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));
if ((pos.flags & (int)SWP.NOMOVE) != 0)
{
return IntPtr.Zero;
}
Window wnd = (Window)HwndSource.FromHwnd(hwnd).RootVisual;
if (wnd == null)
{
return IntPtr.Zero;
}
bool changedPos = false;
// ***********************
// Here you check the values inside the pos structure
// if you want to override tehm just change the pos
// structure and set changedPos to true
// ***********************
if (!changedPos)
{
return IntPtr.Zero;
}
Marshal.StructureToPtr(pos, lParam, true);
handeled = true;
}
break;
}
return IntPtr.Zero;
}

As I have no doubt that Nir's answer will work spending a little time implementing it, I was able to do what I wanted a little bit more elegant with this code:
Private Sub myWindow_LocationChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.LocationChanged
Dim primaryBounds As System.Drawing.Rectangle = Windows.Forms.Screen.PrimaryScreen.Bounds
Dim windowBounds As System.Drawing.Rectangle = New System.Drawing.Rectangle(CInt(Me.Left), CInt(Me.Top), CInt(Me.Width), CInt(Me.Height))
If (windowBounds.Left < 0) Then
windowBounds = New System.Drawing.Rectangle(0, windowBounds.Top, windowBounds.Width, windowBounds.Height)
ElseIf (windowBounds.Right > primaryBounds.Right) Then
windowBounds = New System.Drawing.Rectangle(primaryBounds.Right - windowBounds.Width, windowBounds.Top, windowBounds.Width, windowBounds.Height)
End If
If (windowBounds.Top < 0) Then
windowBounds = New System.Drawing.Rectangle(windowBounds.Left, 0, windowBounds.Width, windowBounds.Height)
ElseIf (windowBounds.Bottom > primaryBounds.Bottom) Then
windowBounds = New System.Drawing.Rectangle(windowBounds.Left, primaryBounds.Bottom - windowBounds.Height, windowBounds.Width, windowBounds.Height)
End If
Me.Left = windowBounds.Left
Me.Top = windowBounds.Top
End Sub
This made the window being dragged stay within the primary screen (whole window), but you could easily change the bounds to whatever values you needed.

There are dependency properties for WPF's Window for this purpose.
Here they are:
Window.MaxWidth
Window.MaxHeight
These properties will constrain the size of the Window, just like the WinForm's Form.

Maybe you could handle PreviewMouseMove (either the event or override the corresponding protected method) and set e.Handled = true whenever the mouse movement would cause the window to move outside the region you want to constrain it to.
This seems like the most logical, WPF-like way of doing this.

Related

WPF Winforms Interop eating keystroke

I create a winform project with a single form with 4 textboxes and a button.
On button click, I perform the following:
Window1 w = new Window1();
ElementHost.EnableModelessKeyboardInterop(w);
w.Show();
Where window 1 is a Wpf window. Window1 has a single button on it and when that button is clicked the following occurs:
System.Windows.MessageBox.Show("HelloWOrld");
When you run the application the WinForm Form pops ups. If you hit tab it cycles through the 4 textboxes no problem. Then Click the button to open the WPF window. Click that button and popup the messagebox. Leave them open and then go back to the WinForm form you can no longer tab through the fields but you can type other characters. It appears as though the textboxes get the keystrokes but the form doesn't get them. I also get a system beep as though the model was getting the keystroke.
EDIT 9/9/2014 3:44PM
Hans responded in the comments and was correct. I tried describing a simpler case that would be easier for other people to reproduce that gave use the same symptoms. Our actual problem is that we have created a window base class that supports modal to parent capabilities. Here is the relevant code for our BaseWindow
public class BaseWindow: Window
{
[DllImport("user32.dll")]
static extern bool EnableWindow(IntPtr hWnd, bool bEnable);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetForegroundWindow(IntPtr hWnd);
public void ShowModalToParent(Window frmParent, Action<bool?> callback = null)
{
IntPtr myHandle = (new System.Windows.Interop.WindowInteropHelper(this)).Handle;
EnableWindow(myHandle,
SetForegroundWindow(myHandle);
this.Closing += Window_Closing;
ShowInTaskbar = false;
Owner = frmParent; // Keep on top of parent
ClosedCallBack += callback ?? (p => { _modalDialogResult = p; });
var parentHandle = (new System.Windows.Interop.WindowInteropHelper(frmParent)).Handle;
EnableWindow(parentHandle, false); // Prevent events for parent
new ShowAndWaitHelper(this).ShowAndWait();
}
internal class ShowAndWaitHelper
{
private readonly Window _window;
private DispatcherFrame _dispatcherFrame;
internal ShowAndWaitHelper(Window window)
{
if (window == null)
{
throw new ArgumentNullException("panel");
}
this._window = window;
}
internal void ShowAndWait()
{
if (this._dispatcherFrame != null)
{
throw new InvalidOperationException("Cannot call ShowAndWait while waiting for a previous call to ShowAndWait to return.");
}
this._window.Closed += new EventHandler(this.OnPanelClosed);
_window.Show();
this._dispatcherFrame = new DispatcherFrame();
Dispatcher.PushFrame(this._dispatcherFrame);
}
private void OnPanelClosed(object source, EventArgs eventArgs)
{
if (this._dispatcherFrame == null)
{
return;
}
this._window.Closed -= new EventHandler(this.OnPanelClosed);
this._dispatcherFrame.Continue = false;
this._dispatcherFrame = null;
}
}
}
I'm sure this code was taken from a Blog/Forum post of some sort but am unable to find any reference to it in code. We want to keep the modal to parent but some how address the odd key press issue. To reproduce the issue replace the button_click in Window1 to call ShowModalToParent on a window that uses this as a base class.

Any way to access mouse Raw Input from WPF?

I'm currently using a global mouse hook to make an app appear if it the mouse cursor reaches the corner of the screen. I just read about the existence of Raw Input and from what I understand, this is a more robust method as a slowdown in my hook will not impact the overall system.
Problem is I can't find any examples anywhere about using Raw Input in WPF.
Closest I got was SlimDX with the following code:
Device.RegisterDevice(UsagePage.Generic, UsageId.Mouse,
DeviceFlags.None);
Device.MouseInput += new EventHandler<MouseInputEventArgs>(mouse_MouseInput);
But that does not seem to work in WPF, only winforms.
Those DeviceFlags.None need to be InputSink to capture input in the background. SharpDX flags there are actually a wrapper for the RAWINPUTDEVICEFLAGS (InputSink = 0x00000100).
In WPF, you want to 1) override OnSourceInitialized and 2) hook WndProc there. While the window pointer is available you need to define the RAWINPUTDEVICE's you want to watch, flag InputSink.
It will look something like
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
source.AddHook(WndProc);
var win = source.Handle;
RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[2];
rid[0].UsagePage = (HIDUsagePage)0x01;
rid[0].Usage = (HIDUsage)0x05; // adds game pad
rid[0].Flags = RawInputDeviceFlags.InputSink; // accept input in background
rid[0].WindowHandle = win;
rid[1].UsagePage = (HIDUsagePage)0x01;
rid[1].Usage = (HIDUsage)0x04; // adds joystick
rid[1].Flags = RawInputDeviceFlags.InputSink; // accept input in background
rid[1].WindowHandle = win;
if (RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0])) == false)
{
var err = Marshal.GetLastWin32Error();
if (err > 0)
{
throw new Win32Exception($"GetLastWin32Error: {err}");
}
else
{
throw new Win32Exception("RegisterRawInputDevices failed with error code 0. Parameter count mis-match?");
}
}
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case WM_INPUT:
{
System.Diagnostics.Debug.WriteLine("Received WndProc.WM_INPUT");
DoTheThing(lParam);
}
break;
}
return hwnd;
}

Drag & Drop for SpecialFolders

I have an application that allows users to drag and drop files or entire folders into a special "drop area," at which point all files are processed. The application is being developed using WPF, and this particular XAML view sets "AllowDrop" to true and handles the Drop event in code-behind.
Everything is working for normal files and standrard Windows folders. However, if the user drops a special Windows folder (e.g., Pictures, Videos), then the functionality does not work. It would appear this is because the contents of DragEventArgs.Data are not a DataFormats.FileDrop enum. That's not the case with other folders or files.
My code for handling the drop, in part, is:
private void OnDrop(object Sender, DragEventArgs E)
{
if (E.Data.GetDataPresent(DataFormats.FileDrop))
{
var _droppedFilePaths = E.Data.GetData(DataFormats.FileDrop, true) as string[];
// Process the files....
}
}
Is there any way to identify that the drop data contains the Windows 7 pictures library and map back to its actual path?
Using the solution described here, I wrote the following method:
const string ShellIdListArrayName = "Shell IDList Array";
static IEnumerable<string> GetPathsFromShellIDListArray(IDataObject data)
{
if (data.GetDataPresent(ShellIdListArrayName))
{
var ms = (MemoryStream)data.GetData(ShellIdListArrayName);
byte[] bytes = ms.ToArray();
IntPtr p = Marshal.AllocHGlobal(bytes.Length);
Marshal.Copy(bytes, 0, p, bytes.Length);
uint cidl = (uint)Marshal.ReadInt32(p, 0);
int offset = sizeof(uint);
IntPtr parentpidl = (IntPtr)((int)p + (uint)Marshal.ReadInt32(p, offset));
StringBuilder path = new StringBuilder(256);
SHGetPathFromIDList(parentpidl, path);
for (int i = 1; i <= cidl; ++i)
{
offset += sizeof(uint);
IntPtr relpidl = (IntPtr)((int)p + (uint)Marshal.ReadInt32(p, offset));
IntPtr abspidl = ILCombine(parentpidl, relpidl);
if (SHGetPathFromIDList(abspidl, path) != 0)
{
yield return path.ToString();
}
ILFree(abspidl);
}
}
}
[DllImport("shell32.dll")]
public static extern int SHGetPathFromIDList(IntPtr pidl, StringBuilder pszPath);
[DllImport("shell32.dll")]
public static extern IntPtr ILCombine(IntPtr pidl1, IntPtr pidl2);
[DllImport("shell32.dll")]
public static extern void ILFree(IntPtr pidl);
You can just pass e.Data from your event handler to this method, and you will get a sequence of paths (assuming the items do have a path of course... for instance, "My computer" doesn't have a path)

Subscribing to mouse events of all controls in form

How can i easily catch the "mouse down" events of all the controls in a form, without manually subscribing to each and every event? (C#)
Something like the "KeyPreview" feature, but for the mouse events.
I found this to be the best solution for my purposes.
Create a new class derived from IMessageFilter:
public class GlobalMouseHandler : IMessageFilter
{
private const int WM_LBUTTONDOWN = 0x201;
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == WM_LBUTTONDOWN)
{
// do something
((YourMainForm)Form.ActiveForm).YourMainForm_Click(null, null);
}
return false;
}
}
Then in your main form add this to register the message filter:
GlobalMouseHandler globalClick = new GlobalMouseHandler();
Application.AddMessageFilter(globalClick);
And add this function to do whatever you have to, in your form:
public void YourMainForm_Click(object sender, EventArgs e)
{
// do anything here...
}
Solution 1
Subscribing to each and every event on every control within a form is certainly the most simplest approach to take, since you just use the code given by Ramesh.
However, another technique involves overriding the default windows message processing method ("WndProc") on the parent control - in this case, the form that contains all the controls.
This has a side effect that you won't be able to detect when the mouse cursor moves over controls contained inside another parent control.
For example, you won't be able to detect when the mouse cursor is over a TextBox that is contained inside a TabControl. This is because the TabControl will continue to process all mouse events.
Solution 2
The following solution will overcome all issues in attempting to detect which control the mouse cursor is over using a technique known as windows hooks.
Hooks essentially allow us to trap mouse and keyboard events even before they are dispatched to the window with focus.
Here's a sample:
public enum HookType : int
{
WH_JOURNALRECORD = 0,
WH_JOURNALPLAYBACK = 1,
WH_KEYBOARD = 2,
WH_GETMESSAGE = 3,
WH_CALLWNDPROC = 4,
WH_CBT = 5,
WH_SYSMSGFILTER = 6,
WH_MOUSE = 7,
WH_HARDWARE = 8,
WH_DEBUG = 9,
WH_SHELL = 10,
WH_FOREGROUNDIDLE = 11,
WH_CALLWNDPROCRET = 12,
WH_KEYBOARD_LL = 13,
WH_MOUSE_LL = 14
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential)]
public struct MouseHookStruct
{
public POINT pt;
public int hwnd;
public int hitTestCode;
public int dwExtraInfo;
}
[DllImport("user32.dll", SetLastError = true)]
static extern int SetWindowsHookEx(HookType hook, HookProc callback, IntPtr hInstance, uint dwThreadId);
[DllImport("user32.dll", SetLastError= true)]
static extern int CallNextHookEx(int hook, int code, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
static extern int GetLastError();
[DllImport("kernel32.dll")]
static extern int GetCurrentThreadId();
public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
private static int hHook;
public Form1()
{
InitializeComponent();
hHook = SetWindowsHookEx(HookType.WH_MOUSE, MouseHookProc, IntPtr.Zero, (uint)GetCurrentThreadId());
if (hHook == 0)
MessageBox.Show("GetLastError: " + GetLastError());
}
private int MouseHookProc(int code, IntPtr wParam, IntPtr lParam)
{
//Marshall the data from the callback.
MouseHookStruct mouseInfo = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));
if (code < 0)
{
return CallNextHookEx(hHook, code, wParam, lParam);
}
else
{
//Create a string variable that shows the current mouse coordinates.
String strCaption = "x = " + mouseInfo.pt.X.ToString("d") +
" y = " + mouseInfo.pt.Y.ToString("d");
//You must get the active form because it is a static function.
Form tempForm = Form.ActiveForm;
Control c = Control.FromHandle((IntPtr)mouseInfo.hwnd);
if (c != null)
label1.Text = c.Name;
else
label1.Text = "Control not found";
//Set the caption of the form.
tempForm.Text = strCaption;
return CallNextHookEx(hHook, code, wParam, lParam);
}
}
Other conttrols in the form cannot listen to the Mouse event handlers of the form. Because each control got its own mouse event listners.
But You can subscribe each controls mouse events to the forms mouse events
this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Form1_MDown);
this.label1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Form1_MDown);
this.ListBox1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Form1_MDown);
this way you can have single handler for all the controls mouse events.

Autofit WinForms RichTextBox to its contents

Does anybody know how can I dynamically resize a RichTextBox control to its contents?
I guess I am far too late but take a look at this
It's just two code lines:
private void rtb_ContentsResized(object sender, ContentsResizedEventArgs e)
{
((RichTextBox)sender).Height = e.NewRectangle.Height + 5;
}
Again assuming a fixed font could you do something like:
using (Graphics g = CreateGraphics())
{
richTextBox.Height = (int)g.MeasureString(richTextBox.Text,
richTextBox.Font, richTextBox.Width).Height;
}
It's kind of a pain - the C# RichTextBox is often frustrating to work with. Are you trying to size the box big enough to hold its contents without any scrollbar?
If the RichTextBox has a constant font, you can use TextRenderer.MeasureText to simply measure the required size, and pass in the box's width as a constraint.
The ContentsResized event gives you a ContentsResizedEventsArgs, which gives you a NewRectangle which tells you how big the text area is. But it only fires when the text changes, which isn't as useful if you simply want to measure an existing richtextbox (although you could probably just do something hacky like set the box's text to itself, triggering this event).
There are also a bunch of Win32 api calls, like using EM_GETLINECOUNT (http://ryanfarley.com/blog/archive/2004/04/07/511.aspx), etc.
A really cheap solution (one that is potentially fraught with problems) is to simultaneously fill an autofit label with text using the same font and size, then just copy the width of the label to the width of the RTB.
So, like this:
RichTextBox rtb = new RichTextBox();
rtb.Text = "this is some text";
rtb.Font = new Font("Franklin Gothic Medium Cond", 10, FontStyle.Regular);
Label fittingLabel = new Label();
fittingLabel.Text = rtb.Text;
fittingLabel.Font = rtb.Font;
fittingLabel.AutoSize = true;
//Not sure if it's necessary to add the label to the form for it to autosize...
fittingLabel.Location = new Point(-1000,-1000);
this.Controls.Add(fittingLabel);
rtb.Width = fittingLabel.Width;
this.Controls.Remove(fittingLabel);
I found a solution for the Rich text box height issues.. i have modified it a for general use..
Create following structs in your application....
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public Int32 left;
public Int32 top;
public Int32 right;
public Int32 bottom;
}
[StructLayout(LayoutKind.Sequential)]
public struct SCROLLBARINFO {
public Int32 cbSize;
public RECT rcScrollBar;
public Int32 dxyLineButton;
public Int32 xyThumbTop;
public Int32 xyThumbBottom;
public Int32 reserved;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public Int32[] rgstate;
}
Create following private variables in your class for form (where ever you need to calculate rich text height)
private UInt32 SB_VERT = 1;
private UInt32 OBJID_VSCROLL = 0xFFFFFFFB;
[DllImport("user32.dll")]
private static extern
Int32 GetScrollRange(IntPtr hWnd, UInt32 nBar, out Int32 lpMinPos, out Int32 lpMaxPos);
[DllImport("user32.dll")]
private static extern
Int32 GetScrollBarInfo(IntPtr hWnd, UInt32 idObject, ref SCROLLBARINFO psbi);
Add following method to your Class for form
private int CalculateRichTextHeight(string richText) {
int height = 0;
RichTextBox richTextBox = new RichTextBox();
richTextBox.Rtf = richText;
richTextBox.Height = this.Bounds.Height;
richTextBox.Width = this.Bounds.Width;
richTextBox.WordWrap = false;
int nHeight = 0;
int nMin = 0, nMax = 0;
SCROLLBARINFO psbi = new SCROLLBARINFO();
psbi.cbSize = Marshal.SizeOf(psbi);
richTextBox.Height = 10;
richTextBox.ScrollBars = RichTextBoxScrollBars.Vertical;
int nResult = GetScrollBarInfo(richTextBox.Handle, OBJID_VSCROLL, ref psbi);
if (psbi.rgstate[0] == 0) {
GetScrollRange(richTextBox.Handle, SB_VERT, out nMin, out nMax);
height = (nMax - nMin);
}
return height;
}
You may need to modify above method to make it work as per your requirement...
Make sure to send Rtf string as parameter to method not normal text and also make sure to assign available width and height to the Richtextbox variable in the method...
You can play with WordWrap depending on your requirement...
It's much easier to use GetPreferredSize, as described in this answer. Then you don't need to wait for a ContentsResized event.

Resources