I have a high dpi setting on my monitor as the monitor is a fairly small 3840 x 2160 monitor.
This is causing issues with one of the applications I am writing as I am hosting a control in my main application. I developed it based off a nice example in the WPF-Samples.
On my screen, when the example is run in it's default state, the output looks like
I was able to account for the list control being the incorrect size by using
//MainWindow
PresentationSource source = PresentationSource.FromVisual(this); //Account for any scaling on the screen7
_listControl = new ControlHost(ControlHostElement.ActualWidth, ControlHostElement.ActualHeight, source.CompositionTarget.TransformToDevice.M11, source.CompositionTarget.TransformToDevice.M22);
...
//ControlHost
public ControlHost(double height, double width, double dpXScale, double dpYScale)
{
_hostHeight = (int) (height * dpXScale);
_hostWidth = (int) (width * dpYScale);
}
However, even when it is the correct size, the hosted UI is not scaled as the rest of the program is.
How could the program scale the hosted UI based on the DPI of the user's screen?
There are three main files that make up the this example.
ControlHost.cs
// // Copyright (c) Microsoft. All rights reserved.
// // Licensed under the MIT license. See LICENSE file in the project root for full license information.
#region Using directives
using System;
using System.Runtime.InteropServices;
using System.Windows.Interop;
#endregion
namespace WPFHostingWin32Control
{
public class ControlHost : HwndHost
{
internal const int
WsChild = 0x40000000,
WsVisible = 0x10000000,
LbsNotify = 0x00000001,
HostId = 0x00000002,
ListboxId = 0x00000001,
WsVscroll = 0x00200000,
WsBorder = 0x00800000;
private readonly int _hostHeight;
private readonly int _hostWidth;
private IntPtr _hwndHost;
public ControlHost(double height, double width, double dpXScale, double dpYScale)
{
_hostHeight = (int) (height * dpXScale);
_hostWidth = (int) (width * dpYScale);
}
public IntPtr HwndListBox { get; private set; }
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
{
HwndListBox = IntPtr.Zero;
_hwndHost = IntPtr.Zero;
_hwndHost = CreateWindowEx(0, "static", "",
WsChild | WsVisible,
0, 0,
_hostHeight, _hostWidth,
hwndParent.Handle,
(IntPtr) HostId,
IntPtr.Zero,
0);
HwndListBox = CreateWindowEx(0, "listbox", "",
WsChild | WsVisible | LbsNotify
| WsVscroll | WsBorder,
0, 0,
_hostHeight, _hostWidth,
_hwndHost,
(IntPtr) ListboxId,
IntPtr.Zero,
0);
return new HandleRef(this, _hwndHost);
}
protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
handled = false;
return IntPtr.Zero;
}
protected override void DestroyWindowCore(HandleRef hwnd)
{
DestroyWindow(hwnd.Handle);
}
//PInvoke declarations
[DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateWindowEx(int dwExStyle,
string lpszClassName,
string lpszWindowName,
int style,
int x, int y,
int width, int height,
IntPtr hwndParent,
IntPtr hMenu,
IntPtr hInst,
[MarshalAs(UnmanagedType.AsAny)] object pvParam);
[DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)]
internal static extern bool DestroyWindow(IntPtr hwnd);
}
}
MainWindow.xaml
<Window x:Class="WPFHostingWin32Control.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFHostingWin32Control"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" Loaded="On_UIReady">
<DockPanel Background="LightGreen">
<Border Name="ControlHostElement"
Width="200"
Height="200"
HorizontalAlignment="Right"
VerticalAlignment="Top"
BorderBrush="LightGray"
BorderThickness="3"
DockPanel.Dock="Right"/>
<StackPanel>
<Label HorizontalAlignment="Center"
Margin="0,10,0,0"
FontSize="14"
FontWeight="Bold">Control the Control</Label>
<TextBlock Margin="10,10,10,10" >Selected Text: <TextBlock Name="selectedText"/></TextBlock>
<TextBlock Margin="10,10,10,10" >Number of Items: <TextBlock Name="numItems"/></TextBlock>
<Line X1="0" X2="200"
Stroke="LightYellow"
StrokeThickness="2"
HorizontalAlignment="Center"
Margin="0,20,0,0"/>
<Label HorizontalAlignment="Center"
Margin="10,10,10,10">Append an Item to the List</Label>
<StackPanel Orientation="Horizontal">
<Label HorizontalAlignment="Left"
Margin="10,10,10,10">Item Text</Label>
<TextBox HorizontalAlignment="Left"
Name="txtAppend"
Width="200"
Margin="10,10,10,10" />
</StackPanel>
<Button HorizontalAlignment="Left"
Click="AppendText"
Width="75"
Margin="10,10,10,10">Append</Button>
<Line X1="0" X2="200"
Stroke="LightYellow"
StrokeThickness="2"
HorizontalAlignment="Center"
Margin="0,20,0,0"/>
<Label HorizontalAlignment="Center"
Margin="10,10,10,10">Delete the Selected Item</Label>
<Button Click="DeleteText"
Width="125"
Margin="10,10,10,10"
HorizontalAlignment="Left">Delete</Button>
</StackPanel>
</DockPanel>
</Window>
MainWindow.cs
// // Copyright (c) Microsoft. All rights reserved.
// // Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
namespace WPFHostingWin32Control
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
internal const int
LbnSelchange = 0x00000001,
WmCommand = 0x00000111,
LbGetcursel = 0x00000188,
LbGettextlen = 0x0000018A,
LbAddstring = 0x00000180,
LbGettext = 0x00000189,
LbDeletestring = 0x00000182,
LbGetcount = 0x0000018B;
private Application _app;
private IntPtr _hwndListBox;
private int _itemCount;
private ControlHost _listControl;
private Window _myWindow;
private int _selectedItem;
public MainWindow()
{
InitializeComponent();
}
private void On_UIReady(object sender, EventArgs e)
{
_app = Application.Current;
_myWindow = _app.MainWindow;
_myWindow.SizeToContent = SizeToContent.WidthAndHeight;
PresentationSource source = PresentationSource.FromVisual(this); //Account for any scaling on the screen7
_listControl = new ControlHost(ControlHostElement.ActualWidth, ControlHostElement.ActualHeight, source.CompositionTarget.TransformToDevice.M11, source.CompositionTarget.TransformToDevice.M22);
ControlHostElement.Child = _listControl;
_listControl.MessageHook += ControlMsgFilter;
_hwndListBox = _listControl.HwndListBox;
for (var i = 1; i <= 100; i++) //populate listbox
{
var itemText = "Item" + i;
SendMessage(_hwndListBox, LbAddstring, IntPtr.Zero, itemText);
}
_itemCount = SendMessage(_hwndListBox, LbGetcount, IntPtr.Zero, IntPtr.Zero);
numItems.Text = "" + _itemCount;
}
private void AppendText(object sender, EventArgs args)
{
if (txtAppend.Text != string.Empty)
{
SendMessage(_hwndListBox, LbAddstring, IntPtr.Zero, txtAppend.Text);
}
_itemCount = SendMessage(_hwndListBox, LbGetcount, IntPtr.Zero, IntPtr.Zero);
numItems.Text = "" + _itemCount;
}
private void DeleteText(object sender, EventArgs args)
{
_selectedItem = SendMessage(_listControl.HwndListBox, LbGetcursel, IntPtr.Zero, IntPtr.Zero);
if (_selectedItem != -1) //check for selected item
{
SendMessage(_hwndListBox, LbDeletestring, (IntPtr) _selectedItem, IntPtr.Zero);
}
_itemCount = SendMessage(_hwndListBox, LbGetcount, IntPtr.Zero, IntPtr.Zero);
numItems.Text = "" + _itemCount;
}
private IntPtr ControlMsgFilter(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
int textLength;
handled = false;
if (msg == WmCommand)
{
switch ((uint) wParam.ToInt32() >> 16 & 0xFFFF) //extract the HIWORD
{
case LbnSelchange: //Get the item text and display it
_selectedItem = SendMessage(_listControl.HwndListBox, LbGetcursel, IntPtr.Zero, IntPtr.Zero);
textLength = SendMessage(_listControl.HwndListBox, LbGettextlen, IntPtr.Zero, IntPtr.Zero);
var itemText = new StringBuilder();
SendMessage(_hwndListBox, LbGettext, _selectedItem, itemText);
selectedText.Text = itemText.ToString();
handled = true;
break;
}
}
return IntPtr.Zero;
}
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
internal static extern int SendMessage(IntPtr hwnd,
int msg,
IntPtr wParam,
IntPtr lParam);
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
internal static extern int SendMessage(IntPtr hwnd,
int msg,
int wParam,
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder lParam);
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
internal static extern IntPtr SendMessage(IntPtr hwnd,
int msg,
IntPtr wParam,
string lParam);
}
}
Winforms does not handle high-DPI settings very well by default. As stated in the comments, you can try some of the available manifest settings to get it to resize. If that won't work for your situation then you will need to scale the control yourself. Most Winforms controls have a Scale method you could call when you adjust the height and width:
_listControl.Scale(dpXScale, dpYScale);
of course, since your actual code uses a custom control your mileage may vary.
Related
I'm trying to integrate a unity application (.exe) inside a WPF XAML application. I've managed to get the unity window to open inside the WPF window, but it's stuck in the top left corner of the window and does not resize when I resize the WPF window.
Here is my code, (the unity application is called unityWindow.exe):
MainWindow.xaml
<Window x:Class="UnityApplicationIntegration.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:UnityApplicationIntegration"
mc:Ignorable="d"
Title="MainWindow" MinHeight="488" MinWidth="815">
<Grid x:Name="unityContent" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Interop;
using System.Threading;
namespace UnityApplicationIntegration
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
[DllImport("User32.dll")]
static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);
internal delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam);
[DllImport("user32.dll")]
internal static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);
[DllImport("user32.dll")]
static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
private Process _process;
private IntPtr _unityHWND = IntPtr.Zero;
private const int WM_ACTIVATE = 0x0006;
private readonly IntPtr WA_ACTIVE = new IntPtr(1);
private readonly IntPtr WA_INACTIVE = new IntPtr(0);
//Frame p = MainWindow.Instance.floatingFrame;
Grid UnityContent;
bool initialized = false;
public MainWindow()
{
InitializeComponent();
UnityContent = unityContent;
//MainWindow.Instance.MainWindowClosing += Application_Exit;
System.Windows.Threading.DispatcherTimer dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
dispatcherTimer.Tick += attemptInit;
dispatcherTimer.Interval = new TimeSpan(0, 0, 1);
dispatcherTimer.Start();
}
void attemptInit(object sender, EventArgs e)
{
if (initialized)
return;
HwndSource source = (HwndSource)HwndSource.FromVisual(UnityContent);
Debug.WriteLine("attempting to get handle...");
if (source == null)
{
Debug.WriteLine("Failed to get handle source");
return;
}
IntPtr hWnd = source.Handle;
try
{
_process = new Process();
_process.StartInfo.FileName = "UnityWindow.exe";
_process.StartInfo.Arguments = "-parentHWND " + hWnd.ToInt32() + " " + Environment.CommandLine;
_process.StartInfo.UseShellExecute = true;
_process.StartInfo.CreateNoWindow = true;
_process.Start();
_process.WaitForInputIdle();
// Doesn't work for some reason ?!
//hWnd = _process.MainWindowHandle;
EnumChildWindows(hWnd, WindowEnum, IntPtr.Zero);
//unityHWNDLabel.Text = "Unity HWND: 0x" + unityHWND.ToString("X8");
Debug.WriteLine("Unity HWND: 0x" + _unityHWND.ToString("X8"));
// TODO: rename. What are the Args?
UnityContentResize(this, EventArgs.Empty);
initialized = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + ".\nCheck if Container.exe is placed next to UnityGame.exe.");
}
}
private void ActivateUnityWindow()
{
SendMessage(_unityHWND, WM_ACTIVATE, WA_ACTIVE, IntPtr.Zero);
}
private void DeactivateUnityWindow()
{
SendMessage(_unityHWND, WM_ACTIVATE, WA_INACTIVE, IntPtr.Zero);
}
private int WindowEnum(IntPtr hwnd, IntPtr lparam)
{
_unityHWND = hwnd;
ActivateUnityWindow();
return 0;
}
private void UnityContentResize(object sender, EventArgs e)
{
MoveWindow(_unityHWND, 0, 0, (int)UnityContent.Width, (int)UnityContent.Height, true);
Debug.WriteLine("RESIZED UNITY WINDOW TO: " + (int)UnityContent.Width + "x" + (int)UnityContent.Height);
ActivateUnityWindow();
}
// Close Unity application
private void ApplicationExit(object sender, EventArgs e)
{
try
{
_process.CloseMainWindow();
Thread.Sleep(1000);
while (!_process.HasExited)
_process.Kill();
}
catch (Exception)
{
}
}
private void UnityContentActivate(object sender, EventArgs e)
{
ActivateUnityWindow();
}
private void UnityContentDeactivate(object sender, EventArgs e)
{
DeactivateUnityWindow();
}
}
}
I've tried placing the unity window inside a content presenter like so:
<ContentPresenter x:Name="unityContent" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
</ContentPresenter>
But resizing still didn't work.
I've read in many places to use WindowsFormsHost but since this is not a forms application, this does not work.
Could anyone tell me where I'm going wrong?
Thanks for your time.
EDIT:
I managed to get the grid with the unity window to resize by changing:
MoveWindow(_unityHWND, 0, 0, (int)UnityContent.Width, (int)UnityContent.Height, true);
to
MoveWindow(_unityHWND, 0, 0, (int)UnityContent.ActualWidth, (int)UnityContent.ActualHeight, true);
But now, after the first resize I get blank space on the sides of the unity window. I need it to fill all available space. Please see the image.
I fixed this issue by doing 2 things:
By changing MoveWindow(_unityHWND, 0, 0, (int)UnityContent.Width, (int)UnityContent.Height, true);
to
MoveWindow(_unityHWND, 0, 0, (int)UnityContent.ActualWidth, (int)UnityContent.ActualHeight, true);
The unity window was not the same scale as the WPF window, so I had to add the following to mainwindow.xaml.cs
double scaleX, scaleY;
if (s != null)
{
scaleX = s.CompositionTarget.TransformToDevice.M11;
scaleY = s.CompositionTarget.TransformToDevice.M22;
}
var actualHeight = (int)UnityContent.ActualHeight * scaleY;
var actualWidth = (int)UnityContent.ActualWidth * scaleX;
MoveWindow(_unityHWND, 0, 0, (int)actualWidth, (int)actualHeight, true);
I'm working on implementing communicate with another process via HwndSource in WPF.
I want My Program (Let's call it A) to communicate with My Program (also A) that is on another process on windows.
following images will help me to explain what I've been doing and what I want to implement.
two windows are exactly same program (just different background-color), and they know exactly their Handle each other. If I notice two process have same title, then I call sendMessage which is defined in user32.dll with Message -Raw input message constant that can pump message globally- 0x0100WM_KEYDOWN.
// if same program - but different process
IntPtr lpData = Marshal.StringToHGlobalAnsi("Hi, Im Sender");
MessageBox.Show(Marshal.PtrToStringAnsi(lpData));
DisplayAnotherProcessHandle.Text = p.MainWindowHandle.ToString();
SendMessage(p.MainWindowHandle, WM_RawInput_key, IntPtr.Zero, lpData);
I successfully pass WM_RawInput_Key, IntPtr.Zero to opponent program. but I have problem with lParam. I can't get lParam data. I tried to Marshal c# string with Marshal.StringToBSTR or Marshal.StringToAuto ... but in following method I just get Empty.String.
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg != WM_RawInput_key) return IntPtr.Zero;
if (wParam == IntPtr.Zero) // <-- opponent process hit this conditional syntax nicely.
{
var str = Marshal.PtrToStringAnsi(lParam); // but I'm failed pass lParam, or Marshalling lParam I don't know ...
MessageBox.Show(str);
DisplaySucceedOrNot.Text = str;
}
return IntPtr.Zero;
}
I don't know which memory parameter IntPtr lParam indicates. I don't know even this technique is valid. I need your help. thank you for reading
following source code is my entire source code of example program.
<Window x:Class="wndProc_IPC_WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:wndProc_IPC_WPF"
mc:Ignorable="d"
Title="WndProc-IPC-WPF" Height="450" Width="800">
<StackPanel>
<Button Name="AddHandler" Width="Auto" Height="100" Content="Add Handler to This Handle" Click="AddHandler_Click"></Button>
<Button Name="CreateNewProcess" Width="Auto" Height="100" Content="Create New Process Window" Click="CreateNewProcess_Click"></Button>
<Button Width="Auto" Height="100" Content="SendMessage To Another Process" Name="SendMessageBtn" Click="SendMessage_Click"></Button>
<StackPanel Orientation="Horizontal">
<TextBlock Text="MyHandle : "></TextBlock>
<TextBlock x:Name="DisplayMyProcessHandle" Margin="50 0 0 0"></TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Another Handle : "></TextBlock>
<TextBlock x:Name="DisplayAnotherProcessHandle" Margin="50 0 0 0"></TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Succed?"></TextBlock>
<TextBlock x:Name="DisplaySucceedOrNot" Margin="50 0 0 0"></TextBlock>
</StackPanel>
</StackPanel>
</Window>
public partial class MainWindow : Window
{
[DllImport("user32.dll")]
private static extern bool SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
string AppName;
public static UInt32 WM_RawInput_key = 0x0100;
public MainWindow()
{
InitializeComponent();
AppName = this.Title;
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg != WM_RawInput_key) return IntPtr.Zero;
if (wParam == IntPtr.Zero)
{
var str = Marshal.PtrToStringAnsi(lParam);
MessageBox.Show(str);
DisplaySucceedOrNot.Text = str;
}
return IntPtr.Zero;
}
private void CreateNewProcess_Click(object sender, RoutedEventArgs e)
{
if (Process.GetProcessesByName(AppName).Length > 1)
{
MessageBox.Show("Already 2 Process Exist.");
return;
}
var path = (System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
Process p = new Process();
p.StartInfo.FileName = path;
p.Start();
}
private void SendMessage_Click(object sender, RoutedEventArgs e)
{
foreach (var p in Process.GetProcessesByName(AppName))
{
if (p.MainWindowHandle == Process.GetCurrentProcess().MainWindowHandle) continue;
{
IntPtr lpData = Marshal.StringToHGlobalAnsi("Hi, Im Sender");
MessageBox.Show(Marshal.PtrToStringAnsi(lpData));
DisplayAnotherProcessHandle.Text = p.MainWindowHandle.ToString();
SendMessage(p.MainWindowHandle, WM_RawInput_key, IntPtr.Zero, lpData);
}
}
}
private void AddHandler_Click(object sender, RoutedEventArgs e)
{
HwndSource source = HwndSource.FromHwnd(Process.GetCurrentProcess().MainWindowHandle);
source.AddHook(new HwndSourceHook(WndProc));
}
}
The wparam and lparam are just a pointers to your local process memory, which the other process cannot access. The wparam works well in your example as you just process the pointer value and not the data the it points to.
For a similar purpose I used WM_COPYDATA message where you provide a pointer to a COPYDATASTRUCT. There you can encapsulate also complex data structs, which can be handled read-only in your receiving process.
Microsoft provides a useful example here:
https://learn.microsoft.com/en-us/windows/win32/dataxchg/using-data-copy
Thank you Mihaeru, actually I was on finding example source of sendMessage/wndProc with WM_COPYDATA in Wpf after posting my question. Like Raw-Input message WM_KEYDOWN, WM_COPYDATA also provide global message pump(right?, I don't know exact theory on old windows... -_-;;)
I failed many times. and I found that I should allocate cbData with Length of string. if I add Attribute [MarshalAs(UnmanagedType.LpWStr)] which 2 byte per character, then I need allocate string.length*2 to cbData.
I succeed to pass message to opponent process.
following source is answer source
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
[MarshalAs(UnmanagedType.LPStr)]
public string lpData;
}
[DllImport("user32.dll")]
private static extern bool SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, ref COPYDATASTRUCT lParam);
string AppName;
public static UInt32 WM_COPYDATA = 0x004A;
public MainWindow()
{
InitializeComponent();
AppName = this.Title;
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg != WM_COPYDATA) return IntPtr.Zero;
if (wParam == IntPtr.Zero)
{
COPYDATASTRUCT cd = new COPYDATASTRUCT();
cd = (COPYDATASTRUCT)Marshal.PtrToStructure(lParam, typeof(COPYDATASTRUCT));
MessageBox.Show(cd.lpData);
DisplaySucceedOrNot.Text = cd.lpData;
}
return IntPtr.Zero;
}
private void CreateNewProcess_Click(object sender, RoutedEventArgs e)
{
if (Process.GetProcessesByName(AppName).Length > 1)
{
MessageBox.Show("Already 2 Process Exist.");
return;
}
var path = (System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
Process p = new Process();
p.StartInfo.FileName = path;
p.Start();
}
private void SendMessage_Click(object sender, RoutedEventArgs e)
{
foreach (var p in Process.GetProcessesByName(AppName))
{
if (p.MainWindowHandle == Process.GetCurrentProcess().MainWindowHandle) continue;
{
IntPtr lpData = Marshal.StringToHGlobalAnsi("Hi, Im Sender");
COPYDATASTRUCT cd = new COPYDATASTRUCT();
cd.lpData = "Hi, Im Sender";
cd.dwData = p.MainWindowHandle;
cd.cbData = cd.lpData.Length+1;
MessageBox.Show(cd.lpData);
DisplayAnotherProcessHandle.Text = p.MainWindowHandle.ToString();
SendMessage(p.MainWindowHandle, WM_COPYDATA, IntPtr.Zero, ref cd);
}
}
}
private void AddHandler_Click(object sender, RoutedEventArgs e)
{
HwndSource source = HwndSource.FromHwnd(Process.GetCurrentProcess().MainWindowHandle);
source.AddHook(new HwndSourceHook(WndProc));
DisplayMyProcessHandle.Text = Process.GetCurrentProcess().MainWindowHandle.ToString();
}
I have created a borderless application in WPF, and it works pretty good. However, when I set the WindowState to full screen, the application takes up more space than my screen resolution, so there are some pixels outside the screen in all directions! (looks like some hard coded negative margins are added to hide the default border)
Any Ideas how to prevent this from happening?
My xaml:
<Window x:Class="MyApp.Shell"
WindowStyle="None"
BorderThickness="0"
AllowsTransparency="True"
Background="Transparent"
ResizeMode="CanResizeWithGrip"
WindowState="{Binding MainApplicationWindowState}"
...
Also, another problem I have seen is that the Windows toolbar / taskbar is covered in the fullsize state, so it looks like the "actual" screen height is used and not the "available" screen height, meaning screen height minus the windows toolbar / taskbar!
Anyone found a solution to these issues?
Thanks
I found this great solution
<Window WindowStyle="None" WindowStartupLocation="CenterScreen" WindowState="Maximized"
MaxWidth="{Binding Source={x:Static SystemParameters.WorkArea}, Path=Width}"
MaxHeight="{Binding Source={x:Static SystemParameters.WorkArea}, Path=Height}"
>
...
</Window>
But error for me still persists, windows is offset by few pixels on top and left... I tried to set Left and Top to 0 after I change window state but nothing happens.
I solved the problem this way:
XAML:
WindowStyle="None"
Left="0"
Top="0"
Width="{Binding WPFSettings.Width}"
Height="{Binding WPFSettings.Height}">
Visual Basic:
Public Class WPFSettings
Public ReadOnly Property Width() As Double
Get
Return System.Windows.SystemParameters.PrimaryScreenWidth
End Get
End Property
Public ReadOnly Property Height() As Double
Get
Return System.Windows.SystemParameters.PrimaryScreenHeight
End Get
End Property
End Class
It works pretty good.
Try this
Width="{Binding Source={x:Static SystemParameters.WorkArea}, Path=Width}"
Height="{Binding Source={x:Static SystemParameters.WorkArea}, Path=Height}"
A newer feature of .NET has solved this problem.
Leave your WindowStyle="SingleBorderWindow" or "ThreeDBorderWindow"
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 window pane, but will still allow resizing and will not cover the task bar when maximized.
In my case the XAML tag of the Window had the Property SizeToContent="True" and all I had to do was to remove it.
In Xaml set the following binding on the Window.MaxHeight:
MaxHeight="{DynamicResource {x:Static SystemParameters.MaximizedPrimaryScreenHeightKey}}"
No need for an additional utility class.
I needed this to work across different size displays (like most people it sounds). So I simply did this...
<Window x:Class="MyApp.MainWindow"
ResizeMode="CanResize"
WindowStyle="SingleBorderWindow"
SizeChanged="Window_SizeChanged">
....
Code
....
</Window>
public void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (this.WindowState == WindowState.Maximized)
{
this.BorderThickness = new System.Windows.Thickness(8);
}
else
{
this.BorderThickness = new System.Windows.Thickness(0);
}
}
I am pretty late to the club, but I believe someone coming after me may benefit out of it.
I tried several solutions but most of them failed to work in multiple displays. So, I am sharing a solution that worked perfectly for me in a commercial application.
I registered SourceInitialized event. And in the callback for the event, I added the following code,
private void Window_SourceInitialized (object sender, EventArgs e)
{
WindowSizing.WindowInitialized(this);
}
While following is the code for WindowSizing class,
public static class WindowSizing
{
const int MONITOR_DEFAULTTONEAREST = 0x00000002;
#region DLLImports
[DllImport("shell32", CallingConvention = CallingConvention.StdCall)]
public static extern int SHAppBarMessage (int dwMessage, ref APPBARDATA pData);
[DllImport("user32", SetLastError = true)]
static extern IntPtr FindWindow (string lpClassName, string lpWindowName);
[DllImport("user32")]
internal static extern bool GetMonitorInfo (IntPtr hMonitor, MONITORINFO lpmi);
[DllImport("user32")]
internal static extern IntPtr MonitorFromWindow (IntPtr handle, int flags);
#endregion
private static MINMAXINFO AdjustWorkingAreaForAutoHide (IntPtr monitorContainingApplication, MINMAXINFO mmi)
{
IntPtr hwnd = FindWindow("Shell_TrayWnd", null);
if ( hwnd == null )
return mmi;
IntPtr monitorWithTaskbarOnIt = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if ( !monitorContainingApplication.Equals(monitorWithTaskbarOnIt) )
return mmi;
APPBARDATA abd = new APPBARDATA();
abd.cbSize = Marshal.SizeOf(abd);
abd.hWnd = hwnd;
SHAppBarMessage((int) ABMsg.ABM_GETTASKBARPOS, ref abd);
int uEdge = GetEdge(abd.rc);
bool autoHide = System.Convert.ToBoolean(SHAppBarMessage((int) ABMsg.ABM_GETSTATE, ref abd));
if ( !autoHide )
return mmi;
switch ( uEdge )
{
case (int) ABEdge.ABE_LEFT:
mmi.ptMaxPosition.x += 2;
mmi.ptMaxTrackSize.x -= 2;
mmi.ptMaxSize.x -= 2;
break;
case (int) ABEdge.ABE_RIGHT:
mmi.ptMaxSize.x -= 2;
mmi.ptMaxTrackSize.x -= 2;
break;
case (int) ABEdge.ABE_TOP:
mmi.ptMaxPosition.y += 2;
mmi.ptMaxTrackSize.y -= 2;
mmi.ptMaxSize.y -= 2;
break;
case (int) ABEdge.ABE_BOTTOM:
mmi.ptMaxSize.y -= 2;
mmi.ptMaxTrackSize.y -= 2;
break;
default:
return mmi;
}
return mmi;
}
private static int GetEdge (RECT rc)
{
int uEdge = -1;
if ( rc.top == rc.left && rc.bottom > rc.right )
uEdge = (int) ABEdge.ABE_LEFT;
else if ( rc.top == rc.left && rc.bottom < rc.right )
uEdge = (int) ABEdge.ABE_TOP;
else if ( rc.top > rc.left )
uEdge = (int) ABEdge.ABE_BOTTOM;
else
uEdge = (int) ABEdge.ABE_RIGHT;
return uEdge;
}
public static void WindowInitialized (Window window)
{
IntPtr handle = (new System.Windows.Interop.WindowInteropHelper(window)).Handle;
System.Windows.Interop.HwndSource.FromHwnd(handle).AddHook(new System.Windows.Interop.HwndSourceHook(WindowProc));
}
private static IntPtr WindowProc (System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled)
{
switch ( msg )
{
case 0x0024:
WmGetMinMaxInfo(hwnd, lParam);
handled = true;
break;
}
return (IntPtr) 0;
}
private static void WmGetMinMaxInfo (IntPtr hwnd, IntPtr lParam)
{
MINMAXINFO mmi = (MINMAXINFO) Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
IntPtr monitorContainingApplication = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if ( monitorContainingApplication != System.IntPtr.Zero )
{
MONITORINFO monitorInfo = new MONITORINFO();
GetMonitorInfo(monitorContainingApplication, monitorInfo);
RECT rcWorkArea = monitorInfo.rcWork;
RECT rcMonitorArea = monitorInfo.rcMonitor;
mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
mmi.ptMaxTrackSize.x = mmi.ptMaxSize.x; //maximum drag X size for the window
mmi.ptMaxTrackSize.y = mmi.ptMaxSize.y; //maximum drag Y size for the window
mmi.ptMinTrackSize.x = 800; //minimum drag X size for the window
mmi.ptMinTrackSize.y = 600; //minimum drag Y size for the window
mmi = AdjustWorkingAreaForAutoHide(monitorContainingApplication, mmi); //need to adjust sizing if taskbar is set to autohide
}
Marshal.StructureToPtr(mmi, lParam, true);
}
public enum ABEdge
{
ABE_LEFT = 0,
ABE_TOP = 1,
ABE_RIGHT = 2,
ABE_BOTTOM = 3
}
public enum ABMsg
{
ABM_NEW = 0,
ABM_REMOVE = 1,
ABM_QUERYPOS = 2,
ABM_SETPOS = 3,
ABM_GETSTATE = 4,
ABM_GETTASKBARPOS = 5,
ABM_ACTIVATE = 6,
ABM_GETAUTOHIDEBAR = 7,
ABM_SETAUTOHIDEBAR = 8,
ABM_WINDOWPOSCHANGED = 9,
ABM_SETSTATE = 10
}
[StructLayout(LayoutKind.Sequential)]
public struct APPBARDATA
{
public int cbSize;
public IntPtr hWnd;
public int uCallbackMessage;
public int uEdge;
public RECT rc;
public bool lParam;
}
[StructLayout(LayoutKind.Sequential)]
public struct MINMAXINFO
{
public POINT ptReserved;
public POINT ptMaxSize;
public POINT ptMaxPosition;
public POINT ptMinTrackSize;
public POINT ptMaxTrackSize;
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class MONITORINFO
{
public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));
public RECT rcMonitor = new RECT();
public RECT rcWork = new RECT();
public int dwFlags = 0;
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int x;
public int y;
public POINT (int x, int y)
{
this.x = x;
this.y = y;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
}
This works like a magic for many scenarios, like maximizing the window with no border covers the task bar as well. This solution keeps the taskbar uncovered as well.
Regards,
Umar
I use a trigger on the window:
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},Path=WindowState}" Value="{x:Static WindowState.Maximized}">
<Setter Property="BorderThickness" Value="8"/>
</DataTrigger>
</Style.Triggers>
this trigger create a Border when the windowstate is Maximized. I think it's useful.
I'm looking for an event that would happen when the screen saver become active or inactive. I want to be notified. I do not want to use any timer.
I do not want to poll for it. I don't want to use: SystemParametersInfo( SPI_GETSCREENSAVERRUNNING, 0, ref isRunning, 0 );
I tried that without success, I never receive the SC_SCREENSAVE...
// ************************************************************************
public MainWindow()
{
InitializeComponent();
}
// ************************************************************************
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
IntPtr mainWindowPtr = new WindowInteropHelper(this).Handle;
HwndSource mainWindowSrc = HwndSource.FromHwnd(mainWindowPtr);
if (mainWindowSrc != null)
{
mainWindowSrc.AddHook(WndProc);
}
//HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
//source.AddHook(WndProc);
}
// ************************************************************************
private const Int32 WM_SYSCOMMAND = 0x112;
private const int SC_SCREENSAVE = 0xF140;
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_SYSCOMMAND)
{
Debug.Print("SysCommand : " + wParam);
if (wParam.ToInt32() == SC_SCREENSAVE)
{
Debug.Print(DateTime.Now.ToString());
}
}
return IntPtr.Zero;
}
Anybody has any other idea or does know what is wrong with my code ???
if (wParam.ToInt32() == SC_SCREENSAVE)
That's not correct. It is an odd quirk in the WM_SYSCOMMAND message, surely dating to a long gone era where they had to squeeze a GUI operating system in 640 kilobytes. The low 4 bits in the command value are used for internal purposes. You'll have to mask them out before you compare. Fix:
if ((wParam.ToInt32() & 0xfff0) == SC_SCREENSAVE)
You'd probably had figured this out yourself by using the proper Debug statement formatting :)
Debug.Print("SysCommand : 0x{0:X}", wParam);
Hans answer seems to not work for me on Windows 7 when it is activated normally by the timer (at least when your account is part of a domain and a policy make screen saver parameters as readonly).
Using SystemParametersInfo with SPI_GETSCREENSAVERRUNNING work fine but it require a timer (polling) which is to my point of view: horrible poorly designed code... but up to now, it is the only way I found that works all the time... :-(
I left my code as reference. It include many of my tests.
There is a lot more but just remove what you don't need.
You could consult the HotKey excellent class there (I couldn't found my initial source but this one is exactly the same): https://github.com/shellscape/Lumen/blob/master/Application/HotkeyHandler.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using HQ.Util.General;
using HQ.Util.Unmanaged;
using HQ.Wpf.Util;
using Microsoft.Win32;
using SpreadsheetGear;
namespace MonitorMe
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
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
}
delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
private HotKeyHandeler _hotKeyHandler = null;
private Timer _timerToMonitorScreenSaver = null;
// ************************************************************************
public MainWindow()
{
InitializeComponent();
SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
Monitor.Instance.Add(SessionSwitchReason.SessionLogon);
_timerToMonitorScreenSaver = new Timer(TimerCallbackMonitorScreenSaver, this, 3000, 3000);
}
private void TimerCallbackMonitorScreenSaver(object state)
{
int active = 1;
SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, ref active, 0);
if (active == 1)
{
Debug.Print("Timer detected Screen Saver activated on: " + DateTime.Now.ToString());
}
}
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetWindowsHookEx(HookType hookType, HookProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll")]
static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
static extern IntPtr LoadLibrary(string lpFileName);
// Signatures for unmanaged calls
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool SystemParametersInfo(
int uAction, int uParam, ref int lpvParam,
int flags);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool SystemParametersInfo(
int uAction, int uParam, ref bool lpvParam,
int flags);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int PostMessage(IntPtr hWnd,
int wMsg, int wParam, int lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr OpenDesktop(
string hDesktop, int Flags, bool Inherit,
uint DesiredAccess);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseDesktop(
IntPtr hDesktop);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool EnumDesktopWindows(
IntPtr hDesktop, EnumDesktopWindowsProc callback,
IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool IsWindowVisible(
IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetForegroundWindow();
// Callbacks
private delegate bool EnumDesktopWindowsProc(
IntPtr hDesktop, IntPtr lParam);
// Constants
private const int SPI_GETSCREENSAVERACTIVE = 16;
private const int SPI_SETSCREENSAVERACTIVE = 17;
private const int SPI_GETSCREENSAVERTIMEOUT = 14;
private const int SPI_SETSCREENSAVERTIMEOUT = 15;
private const int SPI_GETSCREENSAVERRUNNING = 114;
private const int SPIF_SENDWININICHANGE = 2;
private const uint DESKTOP_WRITEOBJECTS = 0x0080;
private const uint DESKTOP_READOBJECTS = 0x0001;
private const int WM_CLOSE = 16;
[DllImport("User32.dll")]
public static extern int SendMessage
(IntPtr hWnd,
uint Msg,
uint wParam,
uint lParam);
public enum SpecialHandles
{
HWND_DESKTOP = 0x0,
HWND_BROADCAST = 0xFFFF
}
// Registers a hot key with Windows.
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
// Unregisters the hot key with Windows.
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
private IntPtr _hHook;
// ************************************************************************
void _hotKeyHandler_HotKeyPressed(object sender, HotKeyEventArgs e)
{
ActivateScreenSaver();
}
// ************************************************************************
private IntPtr MessageHookProc(int code, IntPtr wParam, IntPtr lParam)
{
if (code == WM_SYSCOMMAND)
{
Debug.Print("SysCommand : " + wParam);
if ((wParam.ToInt32() & 0xfff0) == SC_SCREENSAVE)
{
Debug.Print("MessageHookProc" + DateTime.Now.ToString());
}
}
return CallNextHookEx(_hHook, code, wParam, lParam);
}
// ************************************************************************
private const Int32 WM_SYSCOMMAND = 0x0112;
private const int SC_SCREENSAVE = 0xF140;
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_SYSCOMMAND)
{
Debug.Print("SysCommand : " + wParam);
if ((wParam.ToInt32() & 0xfff0) == SC_SCREENSAVE)
{
// Works fine in almost all cases. I already did some code in the past which activate the screen saver
// but I never receive any WM_SYSCOMMAND for that code, I do not have source code for it anymore).
Debug.Print("WndProc" + DateTime.Now.ToString());
}
}
return IntPtr.Zero;
}
// ************************************************************************
void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
Monitor.Instance.Add(e.Reason);
}
// ************************************************************************
private void CmdExportToExcel_Click(object sender, RoutedEventArgs e)
{
ExportToExcel();
}
// ************************************************************************
private void ExportToExcel()
{
IWorkbook wb = Factory.GetWorkbook();
IWorksheet workSheet = wb.Worksheets[0];
int col = 1;
for (int dayIndex = -30; dayIndex <= 0; dayIndex++)
{
DateTime day = DateTime.Today + new TimeSpan(dayIndex, 0, 0, 0);
Debug.Print(day.ToString("yyyy-MM-dd"));
workSheet.Cells[0, col].Value = day.DayOfWeek.ToString();
workSheet.Cells[1, col].Value = day;
DayMonitor dayMonitor = Monitor.Instance.DayMonitors.FirstOrDefault(dm => dm.Day == day);
if (dayMonitor != null)
{
workSheet.Cells[2, col].Value = "Last - First";
workSheet.Cells[2, col + 1].Value = dayMonitor.TotalHours;
workSheet.Cells[3, col].Value = "Has estimated time (7h00 or 18h00)";
workSheet.Cells[3, col + 1].Value = dayMonitor.HasSomeEstimatedHours;
int row = 5;
foreach (var state in dayMonitor.SessionLockStateChanges)
{
workSheet.Cells[row, col].Value = state.SessionSwitchReason.ToString();
workSheet.Cells[row, col + 1].Value = state.DateTime;
row++;
}
}
workSheet.Cells[1, col].ColumnWidth = 18;
workSheet.Cells[1, col + 1].ColumnWidth = 18;
col += 2;
}
if (wb != null)
{
if (FileAssociation.IsFileAssociationExistsForExtensionWithDot(".xlsx"))
{
string path = TempFolderAutoClean.GetTempFileName("xlsx", "Export ");
wb.SaveAs(path, FileFormat.OpenXMLWorkbook);
Process p = new Process();
p.StartInfo.UseShellExecute = true;
p.StartInfo.FileName = path;
p.Start();
}
else
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.InitialDirectory = Environment.CurrentDirectory;
saveFileDialog.Filter = "Excel file (*.xlsx)|*.xlsx";
saveFileDialog.DefaultExt = ".xlsx";
if (saveFileDialog.ShowDialog(Application.Current.MainWindow) == true)
{
string path = saveFileDialog.FileName;
wb.SaveAs(path, FileFormat.OpenXMLWorkbook);
}
}
}
}
// ************************************************************************
private void PrintOnExecuted(object sender, ExecutedRoutedEventArgs e)
{
ExportToExcel();
}
// ************************************************************************
private void ExitOnExecuted(object sender, ExecutedRoutedEventArgs e)
{
this.Close();
}
// ************************************************************************
private void PrintOnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.ContinueRouting = false;
}
// ************************************************************************
private void ExitOnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.ContinueRouting = false;
}
// ************************************************************************
private void CmdLaunchScreenSaver_Click(object sender, RoutedEventArgs e)
{
ActivateScreenSaver();
}
// ************************************************************************
private void ActivateScreenSaver()
{
// Next won't work in a secured Screen Saver by Account Policy (ex: by a domain policy)
//int nullVar = 0;
//SystemParametersInfo(SPI_SETSCREENSAVERACTIVE, 1, ref nullVar, SPIF_SENDWININICHANGE);
// This works fine all the time... and also send appropriate message to every top level window
SendMessage(new IntPtr((int)SpecialHandles.HWND_BROADCAST), WM_SYSCOMMAND, SC_SCREENSAVE, 0);
}
// ************************************************************************
private void MainWindow_OnInitialized(object sender, EventArgs e)
{
int error;
IntPtr hMod = LoadLibrary("user32.dll"); // Hans Passant info in stackOverflow
_hHook = SetWindowsHookEx(HookType.WH_GETMESSAGE, MessageHookProc, IntPtr.Zero, (uint)Thread.CurrentThread.ManagedThreadId);
error = Marshal.GetLastWin32Error(); // Error 87
_hHook = SetWindowsHookEx(HookType.WH_GETMESSAGE, MessageHookProc, hMod, (uint)Thread.CurrentThread.ManagedThreadId);
error = Marshal.GetLastWin32Error(); // Error 87
_hHook = SetWindowsHookEx(HookType.WH_GETMESSAGE, MessageHookProc, hMod, 0);
error = Marshal.GetLastWin32Error(); // Work fine, got a hook, but never receive the WM_SYSCOMMAND:SC_SCREENSAVE
}
// ************************************************************************
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
}
// ************************************************************************
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
// Method 1
//IntPtr mainWindowPtr = new WindowInteropHelper(this).Handle;
//HwndSource mainWindowSrc = HwndSource.FromHwnd(mainWindowPtr);
//if (mainWindowSrc != null)
//{
// mainWindowSrc.AddHook(WndProc);
//}
// Method 2
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
source.AddHook(WndProc);
_hotKeyHandler = new HotKeyHandeler(this);
_hotKeyHandler.HotKeyPressed += _hotKeyHandler_HotKeyPressed;
// _hotKeyHandler.RegisterHotKey(0, Key.NumPad0);
this.Visibility = Visibility.Hidden;
}
// ************************************************************************
}
}
I am trying to move winamps main window, with this code:
[DllImport("user32.dll")]
static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
[DllImport("user32.dll")]
static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
static void resize()
{
Process w = new Process();
w.StartInfo = new ProcessStartInfo("winamp.exe");
w.Start();
Thread.Sleep(5000);
IntPtr hWnd = GetForegroundWindow();
RECT rect;
GetWindowRect(hWnd, out rect);
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
MoveWindow(hWnd, 0, 0, width, height, true);
}
This code snippet works with all processes that I tested, except Winamp. When I use the mainWindowHandle of the process it moves another window.
Anyone an idea how to get it working with Winamp?
With the following code I can confirm changing WinAmp's main window size does not work via the Win32 API's.
Changing other winamp window sizes via the Win32 API's did work, but is not a solution:
public partial class Form1 : Form
{
[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr i);
[DllImport("user32.dll")]
static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
[DllImport("user32.dll")]
static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; // x position of upper-left corner
public int Top; // y position of upper-left corner
public int Right; // x position of lower-right corner
public int Bottom; // y position of lower-right corner
}
private System.IntPtr hWnd;
private void button1_Click(object sender, EventArgs e)
{
Process p = Process.Start(#"C:\Program Files\Winamp\winamp.exe");
try
{
do
{
p.Refresh();
}
while (p.MainWindowHandle.ToInt32() == 0);
hWnd = new IntPtr(p.MainWindowHandle.ToInt32());
}
catch (Exception ex)
{
//Do some stuff...
throw;
}
}
private void button2_Click(object sender, EventArgs e)
{
//Make sure we have a handle to the shelled exe
if (hWnd == new IntPtr(0)) return;
ResizeExternalExeChildWindows(hWnd);
}
private void ResizeExternalExeChildWindows(IntPtr parent)
{
List<IntPtr> childWindows = GetChildWindows(hWnd);
foreach (IntPtr childWindow in childWindows)
{
RECT rect;
GetWindowRect(childWindow, out rect);
int width = rect.Right - rect.Left;
int height = rect.Bottom - rect.Top;
MoveWindow(hWnd, 0, 0, width, height, true);
}
}
// <summary>
/// Returns a list of child windows
/// </summary>
/// <param name="parent">Parent of the windows to return</param>
/// <returns>List of child windows</returns>
public static List<IntPtr> GetChildWindows(IntPtr parent)
{
List<IntPtr> result = new List<IntPtr>();
GCHandle listHandle = GCHandle.Alloc(result);
try
{
EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
}
finally
{
if (listHandle.IsAllocated)
listHandle.Free();
}
return result;
}
/// <summary>
/// Callback method to be used when enumerating windows.
/// </summary>
/// <param name="handle">Handle of the next window</param>
/// <param name="pointer">Pointer to a GCHandle that holds a reference to the list to fill</param>
/// <returns>True to continue the enumeration, false to bail</returns>
private static bool EnumWindow(IntPtr handle, IntPtr pointer)
{
GCHandle gch = GCHandle.FromIntPtr(pointer);
List<IntPtr> list = gch.Target as List<IntPtr>;
if (list == null)
{
throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
}
list.Add(handle);
// You can modify this to check to see if you want to cancel the operation, then return a null here
return true;
}
/// <summary>
/// Delegate for the EnumChildWindows method
/// </summary>
/// <param name="hWnd">Window handle</param>
/// <param name="parameter">Caller-defined variable; we use it for a pointer to our list</param>
/// <returns>True to continue enumerating, false to bail.</returns>
public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
}
}
Cause
Because WinAmp is skinnable it supports resizing via config files (not via external apps using win32 API's).
Solution
Open the file C:\Users[username]\AppData\Roaming\Winamp\studio.xnf and edit the following values:
<entry name="Bento_nomax_h" value="492" />
<entry name="Bento_nomax_w" value="633" />
<entry name="Bento_nomax_x" value="27" />
<entry name="Bento_nomax_y" value="16" />
I am using the Bento Skin. If you open the WinAmp.ini file you can detect the skin being used:
skin=Bento
The solution to set the initial size for WinAmp's main window is to edit the config file before shelling WinAmp with Process.Start.
I have tried your code, it is not working always, and it is not re-sizing windows, it was moving it only,
Actually I found a problem in the 2000 ms sleep, which you can change to a while loop checking if the handle is zero then proceed with the code,
while (p.MainWindowHandle == 0)
{
}
IntPtr hWnd = p.MainWindowHandle;
this may help if the winamp takes too much time to build its own window
This worked for me using the latest version, please give it a try:
internal struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, ExactSpelling = true, SetLastError = true)]
internal static extern bool GetWindowRect(IntPtr hWnd, ref RECT rect);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);
public static class HWND
{
public static IntPtr
NoTopMost = new IntPtr(-2),
TopMost = new IntPtr(-1),
Top = new IntPtr(0),
Bottom = new IntPtr(1);
}
[Flags]
public enum SetWindowPosFlags : uint
{
SWP_ASYNCWINDOWPOS = 0x4000,
SWP_DEFERERASE = 0x2000,
SWP_DRAWFRAME = 0x0020,
SWP_FRAMECHANGED = 0x0020,
SWP_HIDEWINDOW = 0x0080,
SWP_NOACTIVATE = 0x0010,
SWP_NOCOPYBITS = 0x0100,
SWP_NOMOVE = 0x0002,
SWP_NOOWNERZORDER = 0x0200,
SWP_NOREDRAW = 0x0008,
SWP_NOREPOSITION = 0x0200,
SWP_NOSENDCHANGING = 0x0400,
SWP_NOSIZE = 0x0001,
SWP_NOZORDER = 0x0004,
SWP_SHOWWINDOW = 0x0040,
}
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
static void Resize()
{
IntPtr winampMainWindow = IntPtr.Zero;
while (true)
{
winampMainWindow = FindWindow("BaseWindow_RootWnd", "Main Window");
if (winampMainWindow != IntPtr.Zero)
{
RECT rect = new RECT();
GetWindowRect(winampMainWindow, ref rect);
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
SetWindowPos(winampMainWindow,
HWND.Top,
0,
0,
width,
height,
SetWindowPosFlags.SWP_SHOWWINDOW | SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOSENDCHANGING | SetWindowPosFlags.SWP_NOOWNERZORDER | SetWindowPosFlags.SWP_NOCOPYBITS);
break;
}
Thread.Sleep(1000);
}
}
[STAThread]
static void Main()
{
Process.Start("winamp.exe");
Resize();
}
Absolute nightmare!
However, right clicking in the "song window" DID open up the docking menu - I think the undock-(something) worked. One of these options allowed me to move the god-damned thing. It was a fluke, but something opened up there that worked.