WPF Minimize on Taskbar Click - c#

I have a WPF application that by stakeholder requirement must have a WindowStyle="None", ResizeMode="NoResize" and AllowTransparency="True". I know that by not using the Windows chrome, you have to re-implement many of the OS window-handling features. I was able to create a working custom minimize button, however I was not able to re-implement the feature where Windows minimize the application when you click on the Taskbar icon at the bottom of your screen.
The user requirement is such that the application should minimize on taskbar icon click and restore on clicking again. The latter has never stopped working but I have not been able to implement the former. Here is the code that I am using:
public ShellView(ShellViewModel viewModel)
{
InitializeComponent();
// Set the ViewModel as this View's data context.
this.DataContext = viewModel;
this.Loaded += new RoutedEventHandler(ShellView_Loaded);
}
private void ShellView_Loaded(object sender, RoutedEventArgs e)
{
var m_hWnd = new WindowInteropHelper(this).Handle;
HwndSource.FromHwnd(m_hWnd).AddHook(WindowProc);
}
private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == NativeMethods.CS_DBLCLKS)
{
this.WindowState = WindowState.Minimized;
// handled = true
}
return IntPtr.Zero;
}
/// <summary>
/// http://msdn.microsoft.com/en-us/library/ms646360(v=vs.85).aspx
/// </summary>
internal class NativeMethods
{
public const int SC_RESTORE = 0xF120;
public const int SC_MINIMIZE = 0xF020;
public const int SC_CLOSE = 0xF060;
public const int WM_SYSCOMMAND = 0x0112;
public const int WS_SYSMENU = 0x80000;
public const int WS_MINIMIZEBOX = 0x20000;
public const int CS_DBLCLKS = 0x8;
NativeMethods() { }
}

Use ResizeMode="CanMinimize". This will allow you to minimize to the taskbar.

I have used this code in the past to minimize/maximize Windows using WPF's WindowStyle=None
private void MinimizeButton_Click(object sender, RoutedEventArgs e)
{
this.WindowState = WindowState.Minimized;
}
private void MaximizeButton_Click(object sender, RoutedEventArgs e)
{
AdjustWindowSize();
}
private void AdjustWindowSize()
{
if (this.WindowState == WindowState.Maximized)
{
this.WindowState = WindowState.Normal;
}
else
{
this.WindowState = WindowState.Maximized;
}
}
private void FakeTitleBar_MouseDown(object sender, MouseButtonEventArgs e)
{
if(e.ChangedButton == MouseButton.Left)
{
if (e.ClickCount == 2)
{
AdjustWindowSize();
}
else
{
Application.Current.MainWindow.DragMove();
}
}
}

I just realized that if ResizeMode=NoResize than this happens, if it is equal to CanResize than you do not disable the OS feature to minimize via Taskbar icon click. I'm voting to close this question.

Related

c# Detect if specific window (IE Browser, file explorer etc) closes/user presses X button?

Currently doing a WPF app where you will be able to open apps suchs as IE and file explorer (more to come), and when you start it, a button is created and put in a custom appbar/taskbar.
So to the problem, we are using Process.Start() to start the apps, so we are currently checking this
this.ProgramProcess.EnableRaisingEvents = true;
this.ProgramProcess.Exited += (sender, e) => { RemoveRunningProgram((Process)sender); };
and the RemoveRunningProgram method looks like this
private void RemoveRunningProgram(Process process)
{
App.Current.Dispatcher.Invoke((Action)delegate { TaskbarWindow.RunningPrograms.Remove(this); });
}
So the main thought is, if you close a window by the user pressing the X button in the top right corner to close the window, the appbar button/icon will disappear.
So couple of the problems;
If you open 1 IE browser, it works fine, but if you open more than one, you will have to close all windows, for all the icons to disappear.
It takes a LONG time for the computer to see if the file explorer is closed or not, sometimes not even notice it.
Am I thinking at the right spot where I should make adjustments?
RunningProgram.cs:
public class RunningProgram : Button
{
private IntPtr ProgramHandle { get; set; }
private string ProgramName { get; set; }
public Process ProgramProcess { get; set; }
public List<Process> procList = new List<Process>();
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int lParam);
[DllImport("user32.dll")]
private static extern int SetForegroundWindow(IntPtr hwnd);
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
public RunningProgram(IntPtr programHandle, string programName, Thickness margin, string imagePath, Process programProcess)
{
this.ProgramHandle = programHandle;
this.ProgramName = programName;
this.Margin = margin;
this.Background = SetButtonBackground(imagePath);
this.Width = 70;
this.Height = 70;
this.Click += new RoutedEventHandler(ShowWindow_Click);
this.BorderBrush = Brushes.Transparent;
this.ProgramProcess = programProcess;
this.procList.Add(ProgramProcess);
this.ProgramProcess.EnableRaisingEvents = true;
this.ProgramProcess.Exited += (sender, e) => { RemoveRunningProgram((Process)sender); };
}
private void p_Exited(object sender, EventArgs e)
{
RemoveRunningProgram((Process)sender);
}
private void RemoveRunningProgram(Process process)
{
App.Current.Dispatcher.Invoke((Action)delegate { TaskbarWindow.RunningPrograms.Remove(this); });
}
private void ShowWindow_Click(object sender, RoutedEventArgs e)
{
ShowWindow(ProgramHandle, 3);
SetForegroundWindow(ProgramHandle);
GetForegroundWindow();
}
}
Thanks in advance

Exception when using console window in C# (Windows form application)

I have a program WFA that also has and command Window. I open the window with AllocConsole(); When I close the console window, I use FreeConsole(); but when I open it again with AllocConsole(); I wanna write and read from it and it throws an exeption.
The code:
namespace WindowsFormsApplication2
{
class classx
{
[DllImport("kernel32.dll")]
public static extern Int32 AllocConsole();
[DllImport("kernel32.dll")]
public static extern bool FreeConsole();
[DllImport("kernel32")]
public static extern bool AttachConsole();
[DllImport("kernel32")]
public static extern bool GetConsoleWindow();
public static bool z = false;
[DllImport("kernel32")]
public static extern bool SetConsoleCtrlHandler(HandlerRoutine HandlerRoutine, bool Add);
public delegate bool HandlerRoutine(uint dwControlType);
}
public partial class Form1 : Form
{
NotifyIcon icontask;
Icon iconone_active;
Icon iconone_inactive;
/*Icon icontwo;
Icon iconthree;
Icon iconfour;
Icon iconfive;*/
Thread Threadworkermy;
public Form1()
{
InitializeComponent();
this.WindowState = FormWindowState.Minimized;
this.ShowInTaskbar = false;
iconone_active = new Icon(".../iconone_active.ico");
iconone_inactive = new Icon(".../iconone_inactive.ico");
icontask = new NotifyIcon();
icontask.Icon = iconone_active;
icontask.Visible = true;
Threadworkermy = new Thread(new ThreadStart(checkActivityThread));
Threadworkermy.Start();
MenuItem Nameapp = new MenuItem("xr");
MenuItem quitappitem = new MenuItem("quit program");
MenuItem OpenGUI = new MenuItem("Open GUI");
MenuItem Advancedmodewindow = new MenuItem("x");
ContextMenu contextmenu = new ContextMenu();
quitappitem.Click += quitappitem_click;
OpenGUI.Click += OpenGUI_click;
Advancedmodewindow.Click += Advancedmodewindow_click;
contextmenu.MenuItems.Add(Nameapp);
contextmenu.MenuItems[0].Enabled = false;
contextmenu.MenuItems.Add("-");
contextmenu.MenuItems.Add(OpenGUI);
contextmenu.MenuItems.Add(Advancedmodewindow);
contextmenu.MenuItems.Add("-");
contextmenu.MenuItems.Add(quitappitem);
icontask.ContextMenu = contextmenu;
icontask.Icon = iconone_active;
icontask.Visible = true;
}
private void Advancedmodewindow_click(object sender, EventArgs e)
{
classx.AllocConsole();
Console.WriteLine("X");
classx.FreeConsole();
}
private void OpenGUI_click(object sender, EventArgs e)
{
this.ShowInTaskbar = true;
this.WindowState = FormWindowState.Normal;
}
private void quitappitem_click(object sender, EventArgs e)
{
Threadworkermy.Abort();
icontask.Dispose();
this.Close();
}
public void checkActivityThread()
{
try
{
while(true)
{
Thread.Sleep(100);
}
} catch(ThreadAbortException tbe)
{
}
}
private void button1_Click(object sender, EventArgs e)
{
this.WindowState = FormWindowState.Minimized;
this.ShowInTaskbar = false;
}
}
}
Exception that it throws out 'System.IO.IOException' in mscorlib.dll
Additional information: The handle is invalid.
To those who will be saying to change the type, I can't. (it needs to be WFA application)
there seems to be an issue with destroying the consolewindow, so you could just hide it.
For hiding the window you need an additional DllImport from user32.dll and change the returnvalue of GetConsoleWindow to IntPtr:
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("kernel32.dll")]
public static extern IntPtr GetConsoleWindow();
Now check if a console-handle already exists. If it does show the console otherwise create the consolewindow:
private void Advancedmodewindow_click(object sender, EventArgs e)
{
IntPtr handle = classx.GetConsoleWindow();
if (handle == IntPtr.Zero)
{
classx.AllocConsole();
handle = classx.GetConsoleWindow();
}
else
{
//shows the window with the given handle
classx.ShowWindow(handle, 8);
}
Console.WriteLine("X");
//hides the window with the given handle
classx.ShowWindow(handle, 0);
}
The original solution can be found here:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/cdee5d88-3325-47ce-9f6b-83aa4447f8ca/console-exception-on-windows-8?forum=clr

Globally register keyboard presses

I'm programming kind of security application
it records the keyboard keys ..
i want to hide the application and then show it when the user presses a key
i tried the following
Hide Button :
private void button4_Click(object sender, EventArgs e)
{
ShowInTaskbar = false;
this.Visible = false;
this.TopMost = true;
}
and key event
private void KeyEvent(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Control && e.Modifiers== Keys.F12) {
this.Visible = true;
}
}
and of course the form load
private void Form2_Load(object sender, EventArgs e)
{
KeyPreview = true;
this.KeyUp+=new System.Windows.Forms.KeyEventHandler(KeyEvent);
}
But no matter how many times i press the keys .. i wont show !!
What should i do ??
As others have stated, your app won't have input focus and won't be listening to key presses.
You need to hook into RegisterHotKey in user32.dll, e.g:
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vk);
Example:
public class GlobalHotKey
{
private int modifier;
private int key;
private IntPtr hWnd;
private int id;
public GlobalHotKey(int modifier, Keys key, Form form)
{
this.modifier = modifier;
this.key = (int)key;
this.hWnd = form.Handle;
id = this.GetHashCode();
}
public bool Register()
{
return RegisterHotKey(hWnd, id, modifier, key);
}
public bool Unregister()
{
return UnregisterHotKey(hWnd, id);
}
public override int GetHashCode()
{
return modifier ^ key ^ hWnd.ToInt32();
}
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vk);
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
}
public static class Constants
{
public const int NOMOD = 0x0000;
public const int ALT = 0x0001;
public const int CTRL = 0x0002;
public const int SHIFT = 0x0004;
public const int WIN = 0x0008;
public const int WM_HOTKEY_MSG_ID = 0x0312;
}
Usage:
private GlobalHotKey globalHotKey;
// Registering your hotkeys
private void Form2_Load(object sender, EventArgs e)
{
globalHotKey = new HotKeys.GlobalHotKey(Constants.CTRL, Keys.F12, this);
bool registered = globalHotKey.Register();
// Handle instances where the hotkey failed to register
if(!registered)
{
MessageBox.Show("Hotkey failed to register");
}
}
// Listen for messages matching your hotkeys
protected override void WndProc(ref Message m)
{
if (m.Msg == HotKeys.Constants.WM_HOTKEY_MSG_ID)
{
HandleHotkey();
}
base.WndProc(ref m);
}
// Do something when the hotkey is pressed
private void HandleHotkey()
{
if(this.Visible)
this.Hide();
else
this.Show();
}
You'll want to make sure you unregister the key when the app closes too:
private void Form2_FormClosing(object sender, FormClosingEventArgs e)
{
if (!globalHotKey.Unregister())
{
Application.Exit();
}
}
This is because your application does not have input focus and therefore will not be picking up the key presses. You need to hook into lower-level OS to get keyboard input when your application does not have focus.
A similar question was posted and answered here: Global keyboard capture in C# application
Read documentation? Aan application only gets the key presses for its windows. Logically this means a hidden window can not get key presses.
You hook into the forms handler so you only see the presses on your forms, which are invisible so never can have the focus to get key presses.
There are HOOKS you can use in Windows to hook into the general processing, but beare side effects (i.e. other programs also reacting or you blocking keys).
I suggest looking into this:
Processing global mouse and keyboard hooks from C#
In essense, what you want lies outside of .net capabilities, and must be implemented via Windows API, and, consequently, using a native language. However when you recieve input through winAPI, you can pass it to your application, using project i linked to as a guide.

TreeView CheckBoxes=true and ShowNodeToolTips=true GDI objects leak

TreeView leaks resource (2 DC handles and 2 Bitmap handles) when TreeView CheckBoxes=true because of a bug, and I dealt with it:
public class TreeViewCustom : TreeView
{
public new bool CheckBoxes
{
get
{
return base.CheckBoxes;
}
set
{
if (base.CheckBoxes == false)
{
base.CheckBoxes = true;
IntPtr handle = SendMessage(this.Handle, TVM_GETIMAGELIST, new IntPtr(TVSIL_STATE), IntPtr.Zero);
if (handle != IntPtr.Zero)
_checkboxImageList = handle;
}
}
}
/// <summary>
/// The handle to the state ImageList
/// </summary>
private IntPtr _checkboxImageList = IntPtr.Zero;
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x203) // double click
m.Result = IntPtr.Zero;
else
base.WndProc(ref m);
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (e.Button == MouseButtons.Left || e.Button == MouseButtons.Right)
{
Focus();
SelectedNode = GetNodeAt(e.X, e.Y);
}
}
[DllImport("comctl32.dll", CharSet = CharSet.Auto)]
public static extern bool ImageList_Destroy(IntPtr hImageList);
[DllImport("comctl32.dll", CharSet = CharSet.Auto)]
public static extern bool ImageList_RemoveAll(IntPtr hImageList);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern System.IntPtr SendMessage(IntPtr hWnd, uint Msg,
IntPtr wParam, IntPtr lParam);
const uint TV_FIRST = 0x1100;
const uint TVM_GETIMAGELIST = TV_FIRST + 8;
const int TVSIL_NORMAL = 0;
const int TVSIL_STATE = 2;
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_checkboxImageList != IntPtr.Zero)
{
ImageList_Destroy(_checkboxImageList);
}
base.Dispose(disposing);
this.DestroyHandle();
}
else base.Dispose(disposing);
}
}
And everything is OK, except when treeview.ShowNodeToolTips is set to true, then the resources still leak, even with the fix. I think the tooltip is a problem (ToolTips also leak handles if you don't dispose of them properly)
EDIT:
(Why the leak happens when CheckBoxes = true)
When CheckBoxes is set to true, the style is set to the TreeView, and it can be done only once. This causes an CheckBoxImageList to be created. The list contains 2 bitmaps: the checked and unchecked box, every object in the list has a DC handle and a BITMAP handle (that's why 4 handles are leaked for each instance). When the TreeView is disposed, the list of images isn't destroyed, hence the leak.
(Why the leak happens when ShowNodeToolTips=true - my suspicions, http://reflector.webtropy.com/default.aspx/DotNET/DotNET/8#0/untmp/whidbey/REDBITS/ndp/fx/src/WinForms/Managed/System/WinForms/TreeView#cs/2/TreeView#cs)
The TootTip holds the reference to the control, if the ToolTip is not disposed, the control stays in the memory too. When ShowNodeToolTips = true, the ToolTip is created and associated with the TreeView, and it isn't disposed when TreeView is disposed.
The code I use for testing:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Form_Tv form = new Form_Tv();
form.StartPosition = FormStartPosition.CenterParent; ;
form.ShowDialog();
form.Dispose();
GC.Collect();
}
}
public partial class Form_Tv : Form
{
public Form_Tv()
{
InitializeComponent();
for (int i = 0; i < 100; i++)
{
TreeViewCustom tv = new TreeViewCustom();
tv.CheckBoxes = true;
tv.ShowNodeToolTips = true;
this.Controls.Add(tv);
}
this.FormClosed += new FormClosedEventHandler(Form_Tv_FormClosed);
this.ControlRemoved += new ControlEventHandler(Form_Tv_ControlRemoved);
}
void Form_Tv_ControlRemoved(object sender, ControlEventArgs e)
{
e.Control.Dispose();
}
void Form_Tv_FormClosed(object sender, FormClosedEventArgs e)
{
this.Controls.Clear();
}
}
Test results (Windows 7):
Form1 is the main form of the application. After it's shown:
DC: 7 Bitmap: 3
After Form_Tv is shown:
DC: 409 Bitmap: 405
After Form_Tv is closed:
DC: 209 Bitmap: 205
My question:
How can I get a hold of the ToolTip that is set for the TreeView when ShowNodeToolTips is set to true, and how can I properly dispose of it?

Move form without border through a panel

I been searching around for a solution for my problem but for now I wasn't able to get any sucessfull code for what I want to do. So, I have a form without border that is filled with 2 custom panels, so there is no way to the user click on the frame, thinking in that, I implement a code that when user click on a panel, this will call a function on my form that recive by parameter the event of the mouse.
This is the code of my Panel (note that both of my panels in the frame are the same class, it's just 2 diferent instances)
public class MyPanel : System.Windows.Forms.Panel{
(...)
private void MyPanel_MouseDown(object sender, MouseEventArgs e)
{
BarraSms.getInstance().mouseDown(e);
}
private void MyPanel_MouseMove(object sender, MouseEventArgs e)
{
BarraSms.getInstance().mouseMove(e);
}
}
And this is the code of my form:
public partial class BarraSms : Form
{
private Point mousePoint;
(...)
public void mouseDown(MouseEventArgs e) {
mousePoint = new Point(-e.X, -e.Y);
}
public void mouseMove(MouseEventArgs e) {
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
Point mousePos = Control.MousePosition;
mousePos.Offset(mousePoint .X, mousePoint .Y);
this.Location = mousePos;
}
}
}
Is there something that I'm missing?
Thank you in advance for the help.
The working code (update), problem solved by: x4rf41
The MyPanel Class :
MouseMove += MyPanel_MouseMove; // added in class constructer
the BarraSms class (Form)
public partial class BarraSms : Form
{
public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HT_CAPTION = 0x2;
[DllImportAttribute("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImportAttribute("user32.dll")]
public static extern bool ReleaseCapture();
public void mouseMove(MouseEventArgs e) {
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
ReleaseCapture();
SendMessage(this.Handle, WM_NCLBUTTONDOWN, new IntPtr(HT_CAPTION), IntPtr.Zero);
Point loc = this.Location;
writeCoordToBin(loc.X, loc.Y);
}
}
}
There is much better solution for that using the windows api function.
The way you use it, you will have a problem when you move the form very fast and the mouse goes out of the panel. I had the exact same problem.
try this:
using System.Runtime.InteropServices;
and in your Form class
public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HT_CAPTION = 0x2;
[DllImportAttribute("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImportAttribute("user32.dll")]
public static extern bool ReleaseCapture();
public void mouseMove(MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
ReleaseCapture();
SendMessage(this.Handle, WM_NCLBUTTONDOWN, new IntPtr(HT_CAPTION), IntPtr.Zero);
}
}
no need for a mouseDown event for this
Your private methods in MyPanel cannot be called by the framework. You need to declare them as follows:
protected override void OnMouseDown(MouseEventArgs e)
{
var parent = this.Parent as BarraSms;
parent.mouseDown(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
var parent = this.Parent as BarraSms;
parent.mouseMove(e);
}
Try this:
public void mouseMove(MouseEventArgs e) {
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
Point currentPos = Location;
currentPos.Offset(e.X + mousePoint.X, e.Y + mousePoint.Y);
this.Location = currentPos;
}
//Or simply use Location.Offset(e.X + mousePoint.X, e.Y + mousePoint.Y);
}
You have to use Location (the current location of the form), not the Control.MousePosition which is the location of mouse on screen.
UPDATE: Looks like you don't even know how to register event handlers, try modifying your panel class like this:
public class MyPanel : System.Windows.Forms.Panel{
//(...)
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
BarraSms.getInstance().mouseDown(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
BarraSms.getInstance().mouseMove(e);
}
}

Categories

Resources