I wanted to have a customized window so followed a few tutorials which enable this by setting the window style to none, and then adding the title-bar/restore/minimize/close buttons yourself. The minimize is achieved by simply handling the click event and setting the Window-state to minimized, but this doesn't show the minimize animation you see on Windows 7, and just instantly hides the window, which feels very odd when used with other windows that do animate, as you tend to feel the application is closing.
So, is there anyway of enabling that animation? .. it seems to be disabled when you change the WindowStyle to none.
Edit : Test code
public partial class MainWindow : Window
{
public MainWindow()
{
WindowStyle = WindowStyle.None;
InitializeComponent();
}
[DllImport("user32.dll")]
static extern int SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
// this doesnt seem to animate
SendMessage(new WindowInteropHelper(this).Handle, 0x0112, (IntPtr)0xF020, IntPtr.Zero);
}
protected override void OnMouseRightButtonDown(MouseButtonEventArgs e)
{
base.OnMouseRightButtonDown(e);
WindowStyle = WindowStyle.SingleBorderWindow;
WindowState = WindowState.Minimized;
}
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() => WindowStyle = WindowStyle.None));
}
}
A newer feature of .NET has solved this problem.
Leave your WindowStyle="SingleBorder" or "ThreeDBorder"
Leave ResizeMode="CanResize"
Then add this to the xaml inside the
<Window>
<WindowChrome.WindowChrome>
<WindowChrome GlassFrameThickness="0" CornerRadius="0" CaptionHeight="0" UseAeroCaptionButtons="False" ResizeBorderThickness="7"/>
</WindowChrome.WindowChrome>
</Window>
The window will not have any of the default border, but will still allow resizing and will not cover the task bar when maximized. It will also show the minimize animation as before.
EDIT
Unfortunately, when using WindowStyle="None" it still disables the animation and covers the taskbar. So this method does not work if you're trying to make a transparent window.
Edited the answer after experimenting a bit.
There are two options:
1. You can change the Style just before minimising and activating the window:
private void Button_OnClick(object sender, RoutedEventArgs e)
{
//change the WindowStyle to single border just before minimising it
this.WindowStyle = WindowStyle.SingleBorderWindow;
this.WindowState = WindowState.Minimized;
}
private void MainWindow_OnActivated(object sender, EventArgs e)
{
//change the WindowStyle back to None, but only after the Window has been activated
Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() => WindowStyle = WindowStyle.None));
}
This solution has one limitation - it doesn't animate the window if you minimise it from the taskbar.
2. Minimise the Window by sending it WM_SYSCOMMAND message with SC_MINIMIZE parameter and changing the border style by hooking into the message (HwndSource.FromHwnd(m_hWnd).AddHook(WindowProc)).
internal class ApiCodes
{
public const int SC_RESTORE = 0xF120;
public const int SC_MINIMIZE = 0xF020;
public const int WM_SYSCOMMAND = 0x0112;
}
private IntPtr hWnd;
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
private void Window_Loaded(object sender, RoutedEventArgs e)
{
hWnd = new WindowInteropHelper(this).Handle;
HwndSource.FromHwnd(hWnd).AddHook(WindowProc);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SendMessage(hWnd, ApiCodes.WM_SYSCOMMAND, new IntPtr(ApiCodes.SC_MINIMIZE), IntPtr.Zero);
}
private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == ApiCodes.WM_SYSCOMMAND)
{
if (wParam.ToInt32() == ApiCodes.SC_MINIMIZE)
{
WindowStyle = WindowStyle.SingleBorderWindow;
WindowState = WindowState.Minimized;
handled = true;
}
else if (wParam.ToInt32() == ApiCodes.SC_RESTORE)
{
WindowState = WindowState.Normal;
WindowStyle = WindowStyle.None;
handled = true;
}
}
return IntPtr.Zero;
}
Neither of the above methods are great, because they are just hacks. The biggest downside is that you can actually see the border reappearing for a moment when you click the button. I'd like to see what others come up with as I don't consider this as a good answer myself.
If you handle the WM_NCCALCSIZE message by returning 0, handle the WM_NCHITTEST message using either your own code (if you want to do manual hit-testing) or also returning 0, and set the WindowStyle to SingleBorder, the window will function like a borderless window but it will have the animations enabled.
If completely necessary, you may also need to handle the WM_GETMINMAXINFO to fix the maximize size - it clips the borders off because the window's style is SingleBorder.
I have found another solution, if you need AllowTransparency = True.
It is not beautiful, rather a bit hacky.
But it is very simple and works great. This uses a empty Window, which is shortly shown when you Minimize/Maximize/Restore your Window, and it has the same position, widht, size and height as your Window. It always has the same Window State as your Window, and it does the animations, which YourWindow lacks because of WindowStyle None and AllowTransparency True. The empty Window has a Window Style SingleBorderWindow and AllowTransparency = false. (by default, so i dont need to set it manually) This is a must or it would not animate. After it has animated, it is completely hidden. You could adjust the look of the Fake Window (BackgroundColor etc...) to YourWindow if it doesnt look good.
public partial Class YourWindowClass : Window
{
Window w;
public YourWindowClass()
{
InitializeComponent();
w = new Window();
w.Width = Width;
w.Height = Height;
w.WindowStartupLocation = this.WindowStartupLocation;
}
Then, you place this in your state changed event:
private void YourWindowClass_StateChanged(object sender, EventArgs e)
{
w.Left = Left;
w.Top = Top;
w.Width = Width;
w.Height = Height;
w.Show();
if (WindowState == WindowState.Minimized)
{
if (w.WindowState == WindowState.Minimized) w.WindowState = WindowState.Normal;
w.WindowState = WindowState.Minimized;
CloseWindow();
}
if (WindowState == WindowState.Normal)
{
w.WindowState = WindowState.Normal;
w.Left = this.Left;
Activate();
CloseWindow();
}
if (WindowState == WindowState.Maximized)
{
w.WindowState = WindowState.Maximized;
Activate();
CloseWindow();
}
}
Finally, create this async Task in YourWindowClass. It will wait shortly and then hide the extra Window.
public async Task CloseWindow()
{
await Task.Delay(600);
w.Visibility = Visibility.Hidden;
}
This will remove the hidden hack Window, so if you close the real Window, the hacky animation Window will close too. Else it wouldnt be Visible to the user because its hidden, but it will still be open and so parts of your App are open. This is a behaviour we dont want, so put this as your Closed Event:
private void YourWindowClass_Closed(object sender, EventArgs e)
{
w.Close();
}
Related
I'm using WinForms and on my Form I have a RichTextBox. When my form is out of focus but visible and I try to highlight/select text, it does not allow me to until the form or textbox itself has focus.
I've tried:
txtInput.MouseDown += (s, e) => { txtInput.Focus(); }
but to no avail and I can't seem to find anything online about this issue.
When testing with another program like Notepad, it does possess the desired behavior.
MouseDown is too late.
This is a workaround for sure, but may be all you need:
private void txtInput_MouseMove(object sender, MouseEventArgs e)
{
txtInput.Focus();
}
or of course:
txtInput.MouseMove += (s, e) => { txtInput.Focus(); }
As it is it may steal focus from other controls on your form when you move over the textbox. If this is a problem you could prevent it by checking if your program is active using one the of answers here..
You can make the selection manually using MouseDown and MouseMove events. The answer is based on Taw's first idea:
int start = 0;
private void richTextBox1_MouseDown(object sender, MouseEventArgs e)
{
start = richTextBox1.GetTrueIndexPositionFromPoint(e.Location);
richTextBox1.SelectionStart = start;
}
private void richTextBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button.HasFlag(MouseButtons.Left))
{
var current = richTextBox1.GetTrueIndexPositionFromPoint(e.Location);
richTextBox1.SelectionStart = Math.Min(current, start);
richTextBox1.SelectionLength = Math.Abs(current - start);
}
}
And here is the codes for GetTrueIndexPositionFromPoint method which has taken from Justin:
public static class RichTextBoxExtensions
{
private const int EM_CHARFROMPOS = 0x00D7;
public static int GetTrueIndexPositionFromPoint(this RichTextBox rtb, Point pt)
{
POINT wpt = new POINT(pt.X, pt.Y);
int index = (int)SendMessage(new HandleRef(rtb, rtb.Handle), EM_CHARFROMPOS, 0, wpt);
return index;
}
[DllImport("User32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, POINT lParam);
}
This worked for me;
Extend RichTextBox and override WindowProc with this
protected override void WndProc(ref Message m) {
const int WM_MOUSEACTIVATE = 0x21;
if (m.Msg == WM_MOUSEACTIVATE) {
// Take focus to enable click-through behavior for setting selection
this.Focus();
}
// Let the base handle the event.
base.WndProc(ref m);
}
This soulution didn't work for me since my child window had a TextBox that would lose focus when I would hover over the RichTextBox. After some trial and error, I've managed to find another solution:
private const int WM_PARENTNOTIFY = 0x0210;
private Form Form = new Form(); // Your Form here!
private RichTextBox RTB = new RichTextBox(); // Your RichTextBox here!
protected override void WndProc(ref Message m)
{
if ((m.Msg == WM_PARENTNOTIFY) && (Form != null) && (Form.Visible) && (GetChildAtPoint(PointToClient(Cursor.Position)) == RTB))
{
RTB.Focus();
}
base.WndProc(ref m);
}
The WM_PARENTNOTIFY message can be sent multiple times (including when the main Form is being initialized) so it is important to check that that your Form isn't null otherwise you'll receive an exception.
I know that when doing drag-drop I can do something like
private void Form_DragOver(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Copy;
}
to make the cursor have a plus image meaning copy. I am just wondering if I can do this when I am not doing drag-drop (for example when the user clicks a specific place the cursor changes to this style until the user clicks somewhere else). I tried using Cursor = Cursors.<style> but it does not contain this. Any ideas ?
This is quite difficult to do unless you want to display a wait cursor. A special case, handled by the Application.UseWaitCursor property. The problem is that every control by itself affects the cursor shape, as selected by its Cursor property. A TextBox for example will insist on changing the shape to an I-bar.
You are somewhat ahead by only wanting this to do between two clicks. Some trickery is possible in that case, you can capture the mouse when the button is clicked so that the cursor shape is solely controlled by the button. A hack is required when the user clicks the mouse again, that click will go to the same button and not whatever control is clicked. That needs to be fixed by synthesizing another click. This sample code gets this done:
bool CustomCursorShown;
private void button1_MouseUp(object sender, MouseEventArgs e) {
if (button1.DisplayRectangle.Contains(e.Location)) {
this.BeginInvoke(new Action(() => {
CustomCursorShown = true;
button1.Cursor = Cursors.Help; // Change this to the cursor you want
button1.Capture = true;
}));
}
}
private void button1_MouseDown(object sender, MouseEventArgs e) {
if (CustomCursorShown) {
var pos = this.PointToClient(button1.PointToScreen(e.Location));
var ctl = this.GetChildAtPoint(pos);
if (ctl != null && e.Button == MouseButtons.Left) {
// You may want to alter this if a special action is required
// I'm just synthesizing a MouseDown event here...
pos = ctl.PointToClient(button1.PointToScreen(e.Location));
var lp = new IntPtr(pos.X + pos.Y << 16);
// NOTE: taking a shortcut on wparam here...
PostMessage(ctl.Handle, 0x201, (IntPtr)1, lp);
}
}
button1.Capture = false;
}
private void button1_MouseCaptureChanged(object sender, EventArgs e) {
if (!button1.Capture) {
CustomCursorShown = false;
button1.Cursor = Cursors.Default;
}
}
[System.Runtime.InteropServices.DllImport("user32.dll")]
private extern static IntPtr PostMessage(IntPtr hwnd, int msg, IntPtr wp, IntPtr lp);
My C# application consists of a taskbar icon (NotifyIcon) and an overhead window initially hidden. I want the user to be able to toggle the window visibility by clicking on the NotifyIcon (left, single click). Also the window is being hidden when loosing focus.
This is what I have so far, a subclassed System.Windows.Forms.Form:
Initialization:
this.ControlBox = false;
this.ShowIcon = false;
this.ShowInTaskbar = false;
// Instance variables: bool allowVisible;
// System.Windows.Forms.NotifyIcon notifyIcon;
this.allowVisible = false;
this.notifyIcon = new NotifyIcon();
this.notifyIcon.MouseUp += new MouseEventHandler(NotifyIconClicked);
this.Deactivate += new EventHandler(HideOnEvent);
Instance methods:
private void NotifyIconClicked(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
if (this.Visible)
this.Hide();
else
this.Show();
}
}
new public void Show()
{
this.allowVisible = true;
this.Visible = true;
this.Activate();
}
new public void Hide()
{
this.allowVisible = false;
this.Visible = false;
}
private void HideOnEvent(object sender, EventArgs e)
{
this.Hide();
}
protected override void SetVisibleCore(bool visible)
{
base.SetVisibleCore(this.allowVisible ? visible : this.allowVisible);
}
Clicking the icon reveals the window like it should. But clicking it again hides it for as long as the mouse is being pressed, then resets it to visible.
My guess is that the mouse down event steals the focus from the window so it disappears. Then the mouse up event is triggered, showing the window as it is hidden.
My next idea was to read the window visibility at mouse down event, so I tested three events and logged the UNIX time as they are called:
notifyIcon.MouseDown
notifyIcon.MouseUp
this.LostFocus
The result is pretty weird: Let's say the window is visible. This happens when I click the icon: Focus lost is called immediately. Mouse down is called as soon as I release the mouse, right before the mouse up event.
1312372231 focus lost
1312372235 mouse down
1312372235 mouse up
Why is the mouse down event delayed?
How can I toggle the window?
I think this may work for you.
I found an expert exchange post which contains a class which provides a method for checking whether the cursor is currently over the tray.
NotifyIcon - Detect MouseOut
Using this class I modified your HideOnEvent method like so:
private void HideOnEvent(object sender, EventArgs e)
{
if (!WinAPI.GetTrayRectangle().Contains(Cursor.Position))
{
this.Hide();
}
}
Which seems to do what you need.
I have included the class below:
using System.Runtime.InteropServices;
using System.Drawing;
public class WinAPI
{
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
public override string ToString()
{
return "(" + left + ", " + top + ") --> (" + right + ", " + bottom + ")";
}
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow(string strClassName, string strWindowName);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, IntPtr windowTitle);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
public static IntPtr GetTrayHandle()
{
IntPtr taskBarHandle = WinAPI.FindWindow("Shell_TrayWnd", null);
if (!taskBarHandle.Equals(IntPtr.Zero))
{
return WinAPI.FindWindowEx(taskBarHandle, IntPtr.Zero, "TrayNotifyWnd", IntPtr.Zero);
}
return IntPtr.Zero;
}
public static Rectangle GetTrayRectangle()
{
WinAPI.RECT rect;
WinAPI.GetWindowRect(WinAPI.GetTrayHandle(), out rect);
return new Rectangle(new Point(rect.left, rect.top), new Size((rect.right - rect.left) + 1, (rect.bottom - rect.top) + 1));
}
}
It is not a perfect solution but I hope this helps.
I basically want to have my WPF window to go in full screen mode, when F11 is pressed or the maximize button in the right top corner of the window is pressed.
While the following works like a charm for pressing F11:
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.F11)
{
WindowStyle = WindowStyle.None;
WindowState = WindowState.Maximized;
ResizeMode = ResizeMode.NoResize;
}
}
This will still displays the Windows taskbar (tested with Windows 7):
protected override void OnStateChanged(EventArgs e)
{
if (WindowState == WindowState.Maximized)
{
WindowStyle = WindowStyle.None;
WindowState = WindowState.Maximized;
ResizeMode = ResizeMode.NoResize;
}
base.OnStateChanged(e);
}
What am I missing here? Or can I do it even more elegant?
WPF seems to be making the decision about whether to go full-screen or respect the taskbar based on the WindowStyle at the time of maximisation. So a kludgy but effective solution is to switch the window back to non-maximised, set the WindowStyle, and then set the window back to maximised again:
private bool _inStateChange;
protected override void OnStateChanged(EventArgs e)
{
if (WindowState == WindowState.Maximized && !_inStateChange)
{
_inStateChange = true;
WindowState = WindowState.Normal;
WindowStyle = WindowStyle.None;
WindowState = WindowState.Maximized;
ResizeMode = ResizeMode.NoResize;
_inStateChange = false;
}
base.OnStateChanged(e);
}
Although the code is obviously ugly, the transition to Normal and then back to Maximized doesn't seem to make the user experience any worse. On my display, I noticed flicker with both the F11 code and the kludge maximise, but not noticeably worse on the kludge maximise. But your mileage may vary!
try this
Topmost="True" and WindowState="Maximized"
you can see your window will cover all screen and hide all with windows taskbar
You need to set the Window.Topmost property.
Edit
Check this blog post Maximizing window (with WindowStyle=None) considering Taskbar
Another solution that worked for me:
You can set the MaxHeight property of that window to
SystemParameters.MaximizedPrimaryScreenHeight using the constructor.
public MainWindow()
{
InitializeComponent();
this.MaxHeight = SystemParameters.MaximizedPrimaryScreenHeight;
}
Warning: This might not work on extended desktop.
Source: Maximize window with WindowState Problem (application will hide windows taskbar)
If you happen to be using WindowChrome to create a custom chrome experience, you'll need to set the GlassFrameThickness to something other than 0 (at least that was the last thing I needed to do to get the TaskBar to be hidden behind the window). That is in addition to the steps provided in the accepted answer.
If there is still someone that need a smooth full screen of course tested only on windows 10! In windows 10 minimized do less flickering if you maintain this code order!
public bool IsFullscreen = false;
public WindowState lastWindowState;
private void player_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (IsFullscreen)
{
this.WindowStyle = WindowStyle.SingleBorderWindow;
this.WindowState = lastWindowState;
IsFullscreen = false;
}
else
{
lastWindowState = this.WindowState;
this.WindowStyle = WindowStyle.None;
if (this.WindowState == WindowState.Maximized)
this.WindowState = WindowState.Minimized;
this.WindowState = WindowState.Maximized;
IsFullscreen = true;
}
}
You can hide the taskbar if you import user32.dll...
[DllImport("user32.dll")]
private static extern int FindWindow(string className, string windowText);
[DllImport("user32.dll")]
private static extern int ShowWindow(int hwnd, int command);
private const int SW_HIDE = 0;
private const int SW_SHOW = 1;
Usage:
int hwnd = FindWindow("Shell_TrayWnd","");
ShowWindow(hwnd,SW_HIDE);
I have found easy way to achieve fullscreen in WPF:
private double LastHeight, LastWidth;
private System.Windows.WindowState LastState;
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.F11)
{
if (WindowStyle != WindowStyle.None)
{
LastHeight = Height;
LastWidth = Width;
LastState = WindowState;
Topmost = true;
Width = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width;
Height = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height;
Top = 0;
Left = 0;
WindowState = System.Windows.WindowState.Normal;
WindowStyle = WindowStyle.None;
ResizeMode = System.Windows.ResizeMode.NoResize;
}
else
{
WindowStyle = WindowStyle.SingleBorderWindow;
WindowState = LastState; ;
ResizeMode = ResizeMode.CanResizeWithGrip;
Topmost = false;
Width = LastWidth;
Height = LastHeight;
}
}
}
This works good in Windows 7 with fixed Taskbar.
In my case, minimizing and maximizing will make the fullscreen size to be a little bit bigger than the screen, so the alternative I found is to temporarily set the Visibility to Collapsed then back to Visible afterwards to force redraw.
Visibility = Visibility.Collapsed;
WindowStyle = WindowStyle.None;
WindowState = WindowState.Maximized;
ResizeMode = ResizeMode.NoResize;
Visibility = Visibility.Visible;
Basically, I need a window to look like the following image: http://screenshots.thex9.net/2010-05-31_2132.png
(Is NOT resizeable, yet retains the glass border)
I've managed to get it working with Windows Forms, but I need to be using WPF. To get it working in Windows Forms, I used the following code:
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x84 /* WM_NCHITTEST */)
{
m.Result = (IntPtr)1;
return;
}
base.WndProc(ref m);
}
This does exactly what I want it to, but I can't find a WPF-equivalent. The closest I've managed to get with WPF caused the Window to ignore any mouse input.
Any help would be hugely appreciated :)
A very simple solution is to set the Min and Max size of each window equal to each other and to a fix number in the window constructor. just like this:
public MainWindow()
{
InitializeComponent();
this.MinWidth = this.MaxWidth = 300;
this.MinHeight = this.MaxHeight = 300;
}
this way the user can not change the width and height of the window. also you must set the "WindowStyle=None" property in order the get the glass border.
You need to add a hook for the message loop :
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var interopHelper = new WindowInteropHelper(this);
var hwndSource = HwndSource.FromHwnd(interopHelper.Handle);
hwndSource.AddHook(WndProcHook);
}
private IntPtr WndProcHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == 0x84 /* WM_NCHITTEST */)
{
handled = true;
return (IntPtr)1;
}
}