WPF waiting message while rendering a template - wpf

In my legacy project I've a treeview and an OnClick event binded on treeview leafs. Basically each time I choose a leaf, I get the node type and I apply a template into a control such:
gridDati.Template = Resources["tmplBit"] as ControlTemplate;
The "tmplBit" is a DataTemplate with several columns. All works fine but sometimes when I have 20 columns and 30/40 records, it takes time (4/5 sec) while applying the chosen template.
I would like to show a waiting message, or something like that but I cannot find how. I've already a window that show a waiting message, I tried to use as:
var aboutBox = new winWaitingMessage(Global.LM.T("#_3261_Inizio export"));
aboutBox.Show();
// Template?
gridDati.Template = Resources["tmplBit"] as ControlTemplate;
aboutBox.Close();
but the box is closed immediately, so I cannot find an event that tell me when the template is applied and rendered. Any hints?

you can use task
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr FindWindow(IntPtr hwnd, string title);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern int PostMessage(IntPtr hwnd, int msg, uint wParam, uint lParam);
public const int WM_CLOSE = 0x10;
public const int WM_KEYDOWN = 0x0100;
public const int WM_KEYUP = 0x0101;
public const int VK_RETURN = 0x0D;
private void Button_Click(object sender, RoutedEventArgs e)
{
Task task1 = new Task(() =>
{
gridDati.Template = Resources["tmplBit"] as ControlTemplate;
});
task1.Start();
Task cwt = task1.ContinueWith(t=>
{
FindAndKillWindow("Warning");
});
MessageBox.Show("Waiting...", "Warning");
}
private static void FindAndKillWindow(string title)
{
IntPtr ptr = FindWindow(IntPtr.Zero, title);
if (ptr != IntPtr.Zero)
{
int ret = PostMessage(ptr, WM_CLOSE, 0, 0);
ptr = FindWindow(IntPtr.Zero, title);
if (ptr != IntPtr.Zero)
{
PostMessage(ptr, WM_KEYDOWN, VK_RETURN, 0);
PostMessage(ptr, WM_KEYUP, VK_RETURN, 0);
}
}
}

The framework provides no such event but you could handle the Loaded event for an element in the template and close the window when this event occurs, e.g.:
gridDati.Template = Resources["tmplBit"] as ControlTemplate;
gridDati.ApplyTemplate();
FrameworkElement fe = gridDati.Template.FindName("YourElement", gridDati) as FrameworkElement;
if (fe == null)
{
aboutBox.Close();
}
else
{
void Fe_Loaded(object sender, RoutedEventArgs e)
{
aboutBox.Close();
fe.Loaded -= Fe_Loaded;
}
fe.Loaded += Fe_Loaded;
}
XAML:
<ControlTemplate x:Key="tmplBit">
...
<Button x:Name="YourElement" ... />
Edit: If you don't want to or can't use a local function for whatever reason, you could use a lambda expression to achieve the same thing:
...
else
{
RoutedEventHandler loadedHandler = null;
loadedHandler = (s, e) =>
{
aboutBox.Close();
fe.Loaded -= loadedHandler;
};
fe.Loaded += loadedHandler;
}

Related

How to prevent MouseUp event after DoubleClick on Window TitleBar in WPF?

If user double-clicks window title bar to maximize window, the window will maximize on MouseDown event of the second click, but then the MouseUp event on that same second click will be registered by one of the controls in the window that are now under the cursor and trigger something user didn't want.
How can it be prevented?
This should work just fine:
Solution:
//WNDPROC INTEROP
private const int WM_NCLBUTTONDBLCLK = 0x00A3;
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
source.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
//TITLE BAR DOUBLE CLICK
if (msg == WM_NCLBUTTONDBLCLK)
{
Console.WriteLine("titlebar double click " + this.WindowState);
//WINDOW ABOUT TO GET MAXIMIZED
if (msg == WM_NCLBUTTONDBLCLK)
handleNextMouseup = true;
else if (msg == 0x202) // mouseup
{
if (handleNextMouseup)
handled = true;
handleNextMouseup = false;
}
}
return IntPtr.Zero;
}
another possible solution:
//WNDPROC INTEROP
private const int WM_NCLBUTTONDBLCLK = 0x00A3;
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
source.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
//TITLE BAR DOUBLE CLICK
if (msg == WM_NCLBUTTONDBLCLK)
{
Console.WriteLine("titlebar double click " + this.WindowState);
//WINDOW ABOUT TO GET MAXIMIZED
if (this.WindowState == WindowState.Normal)
{
fakehittestvisible = true;
}
}
return IntPtr.Zero;
}
private bool fakehittestvisible = false;
//ON PREVIEW MOUSE UP ABORT IF MIXIMIZING HAPPENED
private void this_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (fakehittestvisible)
{
e.Handled = true;
fakehittestvisible = false;
return;
}
fakehittestvisible = false;
Console.WriteLine("this mouse up state is " + this.WindowState);
}

Check if something is overlapping my window?

How do I check if something is overlapping my Window?
I found this WinForms code which should do the trick:
public static bool IsOverlapped(IWin32Window window)
{
if (window == null)
throw new ArgumentNullException("window");
if (window.Handle == IntPtr.Zero)
throw new InvalidOperationException("Window does not yet exist");
if (!IsWindowVisible(window.Handle))
return false;
IntPtr hWnd = window.Handle;
HashSet<IntPtr> visited = new HashSet<IntPtr> { hWnd };
// The set is used to make calling GetWindow in a loop stable by checking if we have already
// visited the window returned by GetWindow. This avoids the possibility of an infinate loop.
RECT thisRect;
GetWindowRect(hWnd, out thisRect);
while ((hWnd = GetWindow(hWnd, GW_HWNDPREV)) != IntPtr.Zero && !visited.Contains(hWnd))
{
visited.Add(hWnd);
RECT testRect, intersection;
if (IsWindowVisible(hWnd) && GetWindowRect(hWnd, out testRect) && IntersectRect(out intersection, ref thisRect, ref testRect))
return true;
}
return false;
}
[DllImport("user32.dll")]
private static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(IntPtr hWnd, [Out] out RECT lpRect);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool IntersectRect([Out] out RECT lprcDst, [In] ref RECT lprcSrc1, [In] ref RECT lprcSrc2);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool IsWindowVisible(IntPtr hWnd);
private const int GW_HWNDPREV = 3;
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
But I am not sure how to make it work on WPF, can someone help me out?
I tried a lot of things already..
If you want to get the handle for a given WPF window you can use the System.Windows.Interop.WindowInteropHelper class. An update to the IsOverlapped would allow you to interact with the WinForms code:
public static bool IsOverlapped(Window window)
{
if (window == null)
throw new ArgumentNullException("window");
var hWnd = new WindowInteropHelper(window).Handle;
if (hWnd == IntPtr.Zero)
throw new InvalidOperationException("Window does not yet exist");
if (!IsWindowVisible(hWnd))
return false;
HashSet<IntPtr> visited = new HashSet<IntPtr> { hWnd };
// The set is used to make calling GetWindow in a loop stable by checking if we have already
// visited the window returned by GetWindow. This avoids the possibility of an infinate loop.
RECT thisRect;
GetWindowRect(hWnd, out thisRect);
while ((hWnd = GetWindow(hWnd, GW_HWNDPREV)) != IntPtr.Zero && !visited.Contains(hWnd))
{
visited.Add(hWnd);
RECT testRect, intersection;
if (IsWindowVisible(hWnd) && GetWindowRect(hWnd, out testRect) && IntersectRect(out intersection, ref thisRect, ref testRect))
return true;
}
return false;
}
I solved it this way:
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
private void TmrCheckTopmost_Tick(object sender, EventArgs e)
{
if (!GetForegroundWindow().Equals(new WindowInteropHelper(this).Handle))
{
Topmost = false;
Topmost = true;
Focus();
}
}
public static bool IsOverlappedElement(FrameworkElement element)
{
var hwndYourWindow = ((HwndSource)PresentationSource.FromVisual(Window.GetWindow(element))).Handle;
var controlRect = GetAbsolutePlacement(element);
var overlappingWindows = GetOverlappingWindowsRectangle(WindowsListFactory.Load(), hwndYourWindow, controlRect).ToList();
return overlappingWindows.Any();
}
GetOverlappingWindowsRectangle methods inputs:
WindowsListFactory.Load() returns with all relevant active windows data
(the following github repository contains this function... click here.)
hwndYourWindow is your window which contains your UI element.
controlRect is your element path (to detect overlapping).
The following function will return with (real) windows which is overlapped your visual element (or window).
private IEnumerable<WindowEntry> GetOverlappingWindowsRectangle(IEnumerable<WindowEntry> filteredWindows, IntPtr interrupterHwnd, Rect displayedControl)
{
const uint GW_HWNDNEXT = 2;
var byHandle = filteredWindows.ToDictionary(win => win.HWnd);
for (IntPtr hWnd = GetTopWindow(IntPtr.Zero); hWnd != IntPtr.Zero; hWnd = GetWindow(hWnd, GW_HWNDNEXT))
{
if (interrupterHwnd == hWnd) break;
if (byHandle.ContainsKey(hWnd) == false) continue;
var result = GetWindowRect(new HandleRef(this, hWnd), out RECT rect);
if (result == false) continue;
byHandle[hWnd].Frame = CreateRectangle(rect);
if (displayedControl.IntersectsWith(byHandle[hWnd].Frame) == false) continue;
yield return byHandle[hWnd];
}
}
//Helper methods:
private Rect CreateRectangle(RECT rect)
{
var rectangle = new Rect();
rectangle.X = rect.Left;
rectangle.Y = rect.Top;
rectangle.Width = rect.Right - rect.Left + 1;
rectangle.Height = rect.Bottom - rect.Top + 1;
return rectangle;
}
private Rect GetAbsolutePlacement(FrameworkElement element, bool relativeToScreen = false)
{
var absolutePos = element.PointToScreen(new System.Windows.Point(0, 0));
if (relativeToScreen)
{
return new Rect(absolutePos.X, absolutePos.Y, element.ActualWidth, element.ActualHeight);
}
var posMW = Application.Current.MainWindow.PointToScreen(new System.Windows.Point(0, 0));
absolutePos = new System.Windows.Point(absolutePos.X - posMW.X, absolutePos.Y - posMW.Y);
return new Rect(absolutePos.X, absolutePos.Y, element.ActualWidth, element.ActualHeight);
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(HandleRef hWnd, out RECT lpRect);
[DllImport("user32.dll")]
private static extern IntPtr GetTopWindow(IntPtr hWnd);
[DllImport("User32")]
private static extern IntPtr GetWindow(IntPtr hWnd, uint wCmd);

WPF - Anchoring window position to other than TopLeft

I'm trying to anchor a WPF Window (NOT a control inside a window) to TopRight for example, by default Windows anchors all windows to top left.
I tried the following code
private void OnWindowSizeChanged(object sender, SizeChangedEventArgs e)
{
double delta = e.PreviousSize.Width - e.NewSize.Width;
Left += delta;
}
It works, but the window stutters/flickers during rapid size changes (e.g Animations)
I tried googling it but did not find a good solution, am I missing something ?
I managed to solve the problem with the help of this post.
Here's the code
public partial class MainWindow : Window
{
private double right;
public MainWindow()
{
InitializeComponent();
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
right = Left + Width;
source.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == (int)WindowMessage.WindowPositionChanging)
{
var windowPosition = (WindowPosition)Marshal.PtrToStructure(lParam, typeof(WindowPosition));
bool isMove = !windowPosition.Flags.HasFlag(WindowPositionFlags.NoMove); //0x0002
bool isSize = !windowPosition.Flags.HasFlag(WindowPositionFlags.NoSize); //0x0001
if (isMove)
{
right = windowPosition.Left + windowPosition.Width;
}
else if (isSize)
{
windowPosition.Left = (int)(right - windowPosition.Width);
windowPosition.Top = (int)Top;
windowPosition.Flags = (WindowPositionFlags)((int)windowPosition.Flags & 0xfffd); //remove the NoMove flag
Marshal.StructureToPtr(windowPosition, lParam, true);
}
}
return IntPtr.Zero;
}
Hope it helps someone

How can I prevent focus stealing, but still get focus when returning to my app using Alt+Tab?

Following MS guidelines, my WPF application's App constructor includes the following code for proper focus behavior:
HwndSource.DefaultAcquireHwndFocusInMenuMode = false;
Keyboard.DefaultRestoreFocusMode = RestoreFocusMode.None;
As explained in this article, these settings prevent focus stealing.
However, setting DefaultRestoreFocusMode to None has a bad side effect. When using Alt+Tab to leave a WPF application and then return to it, the WPF application doesn't get focus. However, if I don't set DefaultRestoreFocusMode to none, it does get focus as expected. Is there a way to prevent focus stealing but have focus still set when returning to a WPF application via Alt+Tab?
-Craig
I prevent my wpf window from getting focus by doing the below and i can still activate it by using ALT-TAB or clicking on it's taskbar item.
Here you change the window styles on your window so that it has no activate.
var yourWindow = new YourWindowType();
//set the windowstyle to noactivate so the window doesn't get focus
yourWindow.SourceInitialized += (s, e) =>
{
var interopHelper = new WindowInteropHelper(yourWindow);
int exStyle = User32.GetWindowLong(interopHelper.Handle, (int)WindowLongFlags.GWL_EXSTYLE);
User32.SetWindowLong(interopHelper.Handle, (int)WindowLongFlags.GWL_EXSTYLE, exStyle | (int)WindowStylesEx.WS_EX_NOACTIVATE);
//If you have trouble typing into your form's textboxes then do this
ElementHost.EnableModelessKeyboardInterop(yourWindow);
};
This is something i added as an extra precaution, plus it lets you drag your window around if it is borderless:
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
//don't activate the window when you click on it.
case WindowMessage.WM_MOUSEACTIVATE:
handled = true;
return (IntPtr)MouseActivate.MA_NOACTIVATE;
//For Borderless Windows: occurs while dragging. it reports new position before it has been finalized.
//otherwise you wont see the window moving while you're dragging it
case WindowMessage.WM_MOVING:
RECT rect = (RECT)Marshal.PtrToStructure(lParam, typeof(RECT));
User32.SetWindowPos(new WindowInteropHelper(this).Handle, Hwnd.HWND_TOPMOST,
rect.Left, rect.Top, rect.Width, rect.Height,
SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOSIZE);
break;
}
return IntPtr.Zero;
}
These add a hook so that WndProc is actually called in WPF:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
if (source == null) return;
source.AddHook(WndProc);
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
if (source == null) return;
source.RemoveHook(WndProc);
}
Just an FYI.. this still works even though you don't get focus:
private void WpfPillForm_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.DragMove();
}
Here's the Win32 API declarations so you don't have to look them up:
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public int flags;
}
[StructLayout(LayoutKind.Sequential)]
struct RECT
{
public int left, top, right, bottom;
}
public static class MouseActivate
{
public const int MA_ACTIVATE = 1;
public const int MA_ACTIVATEANDEAT = 2;
public const int MA_NOACTIVATE = 3;
public const int MA_NOACTIVATEANDEAT = 4;
}
public enum WindowLongFlags : int
{
GWL_EXSTYLE = -20,
GWLP_HINSTANCE = -6,
GWLP_HWNDPARENT = -8,
GWL_ID = -12,
GWL_STYLE = -16,
GWL_USERDATA = -21,
GWL_WNDPROC = -4,
DWLP_USER = 0x8,
DWLP_MSGRESULT = 0x0,
DWLP_DLGPROC = 0x4
}
public const int WM_MOVING = 0x0216;
public const uint WS_EX_NOACTIVATE = 0x08000000,
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowLong(IntPtr hwnd, int index);

How do I center the OpenFileDialog to its parent Window in WPF?

I'm using WPF's OpenFileDialog, and I'm looking for a way to make sure it is centered in the parent window when shown. It seems to be missing obvious properties like StartupPosition that might enable this.
Does anybody know the secret?
Update: It seems that the first time I open it, it does appear in the center of the parent, but if I move it, it then remembers its position, and doesn't open centered on subsequent occassions.
here is the code of a generic class that allows to play with "sub dialogs" like this one:
public class SubDialogManager : IDisposable
{
public SubDialogManager(Window window, Action<IntPtr> enterIdleAction)
:this(new WindowInteropHelper(window).Handle, enterIdleAction)
{
}
public SubDialogManager(IntPtr hwnd, Action<IntPtr> enterIdleAction)
{
if (enterIdleAction == null)
throw new ArgumentNullException("enterIdleAction");
EnterIdleAction = enterIdleAction;
Source = HwndSource.FromHwnd(hwnd);
Source.AddHook(WindowMessageHandler);
}
protected HwndSource Source { get; private set; }
protected Action<IntPtr> EnterIdleAction { get; private set; }
void IDisposable.Dispose()
{
if (Source != null)
{
Source.RemoveHook(WindowMessageHandler);
Source = null;
}
}
private const int WM_ENTERIDLE = 0x0121;
protected virtual IntPtr WindowMessageHandler(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_ENTERIDLE)
{
EnterIdleAction(lParam);
}
return IntPtr.Zero;
}
}
And this is how you would use it in a standard WPF app. Here I just copy the parent window size, but I'll let you do the center math :-)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
bool computed = false; // do this only once
int x = (int)Left;
int y = (int)Top;
int w = (int)Width;
int h = (int)Height;
using (SubDialogManager center = new SubDialogManager(this, ptr => { if (!computed) { SetWindowPos(ptr, IntPtr.Zero, x, y, w, h, 0); computed= true; } }))
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.ShowDialog(this);
}
}
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, int flags);
}
CommonDialog in WPF does not inherit from window class, so it does not have StartupPosition property.
Check this blog post for one solution: OpenFileDialog in .NET on Vista
In short, it wraps dialog in a window and then shows it.

Resources