I'm facing the following issue:
In WPF I have a window with WindowStyle="None" and so I add a button to move the window with the DragMove() method. This part is working fine. What I want in addition is that when the window reach a certain position it's stop the DragMove.
My idea was to make it by raising a MouseLeftButtonLeft thinking it will interrupt the DragMove but it isn't.
Button to move window:
<Button Grid.Column="0" x:Name="MoveButton" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="3" Cursor="Hand">
<Image x:Name="MoveImage" Source="images/move.png" MouseLeftButtonDown="MoveWindow" MouseLeftButtonUp="Poney" />
</Button>
Method to move the window:
// Move the window with drag of the button. Ensure that we are not over the taskbar
private void MoveWindow(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
DragMove();
// https://stackoverflow.com/questions/48399180/wpf-current-screen-dimensions
System.Drawing.Rectangle workingArea = Screen.FromHandle(new System.Windows.Interop.WindowInteropHelper(this).Handle).WorkingArea;
//The left property of the window is calculated on the width of all screens, so use VirtualScreenWidth to have the correct width
if (Top > (workingArea.Height - Height))
{
Top = (workingArea.Height - Height);
}
else if (Left > (SystemParameters.VirtualScreenWidth - Width))
{
Left = (SystemParameters.VirtualScreenWidth - Width);
}
else if (Left < 0)
{
Left = 0;
}
}
Method to raise event :
private void MainWindow_LocationChanged(object sender, EventArgs e)
{
// https://stackoverflow.com/questions/48399180/wpf-current-screen-dimensions
System.Drawing.Rectangle workingArea = Screen.FromHandle(new System.Windows.Interop.WindowInteropHelper(this).Handle).WorkingArea;
if(Left > 2000)
{
MouseButtonEventArgs mouseButtonEvent = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left)
{
RoutedEvent = MouseLeftButtonUpEvent,
Source = MoveImage
};
MoveImage.RaiseEvent(mouseButtonEvent);
//InputManager.Current.ProcessInput(mouseButtonEvent);
}
}
Check that the event is raised :
public void Poney(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("Poney");
}
I have the 'poney' displaying in my console, so i guess the code to raise the event is working?
In short version I need a method to interrupt the dragmove so I can make some change and the restart DragMove.
Thanks :)
PS : The 2000 value is used for test, in "real" the position is calculated.
You could use the native mouse_event function to synthesize a mouse up event, e.g.:
const int MOUSEEVENTF_LEFTUP = 0x04;
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint cButtons, uint dwExtraInfo);
void MainWindow_LocationChanged(object sender, EventArgs e)
{
if (Left > 1000)
{
Point point = Mouse.GetPosition(this);
mouse_event(MOUSEEVENTF_LEFTUP, (uint)point.X, (uint)point.Y, 0, 0);
}
}
Related
I want am making an AI and make it just like a player. When it does not know what to do it will do a random move and I called this rndPlay(); It will randomly choose a location to click. It can choose from 9 locations. press1();, press2(); etc. I make it repeat until it pressed two buttons in a row. Now here is the problem
When the for loop is executing I tell it to press on 850, 400 there is a button there so let's call that btn1 and that executes btnPressed1();, for example, I see the mouse move there and it clicks but it does not execute btnPressed1();. Instead, it clicks on the next random location and keeps repeating 10000 times because it did not do a successful move(it has to press two things in a certain order). Then when it exits the for loop it executes btnPressed1();. I do not want it to do this after it stops because ontop of every button is another button lets call it btnMove1 with the number corresponding with the button under it. for example, It had pressed button 1, 1 and 8, it should after pressing btn1 show btnMove1 but since it does not execute instantly it presses btn1 again (not btnMove1) and then btn8 then when it finishes it executes btnPressed1(); then btnPressed1(); again and finally btnPressed8(); How do I make it execute the code it pressed before pressing the next thing?
I have tried to use Thread.Sleep(100) to make it stop but it just waits and does not execute the button it just pressed. I also made my own for loop with if statement because I thought it might be the for loop that stops the input from being executed.
XAML code:
<button x:Name="btn1" Click="btnPressed1"/>
<button x:Name="btnMove1" Click="btnMove1" Visibility="Hidden"/>
<button x:Name="btn2" Click="btnPressed2"/>
<button x:Name="btnMove2" Click="btnPressedMove2" Visibility="Hidden"/>
<button x:Name="btn3" Click="btnPressed3"/>
<button x:Name="btnMove3" Click="btnMove3" Visibility="Hidden"/>
//etc
C# code:
private void btnPressed1(object sender, RoutedEventArgs e)
{
btnMove1.Visibility = Visibility.Visible
}
private void btnPressed2(object sender, RoutedEventArgs e)
{
btnMove2.Visibility = Visibility.Visible
}
//etc
private void btnPressedMove1(object sender, RoutedEventArgs e)
{
//some code
}
private void btnPressedMove2(object sender, RoutedEventArgs e)
{
//some code
}
//etc
private void MoveTo(int x, int y)
{
mouse_event(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, x, y, 0, 0);
}
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint cButtons, uint dwExtraInfo);
//Mouse actions
private const int MOUSEEVENTF_LEFTDOWN = 0x02;
private const int MOUSEEVENTF_LEFTUP = 0x04;
private const int MOUSEEVENTF_RIGHTDOWN = 0x08;
private const int MOUSEEVENTF_RIGHTUP = 0x10;
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private extern bool SetCursorPos(uint X, uint Y);
private void LeftMouseClick(uint X, uint Y)
{
SetCursorPos(X, Y);
//Call the imported function with the cursor's current
mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, X, Y, 0, 0);
Thread.Sleep(1000);
return;
}
private void press1()
{
LeftMouseClick(28000, 25000);
last2Moves("1");
}
private void press2()
{
LeftMouseClick(33000, 25000);
last2Moves("2");
}
//etc
private void rndPlay()
{
for (int r = 0; moveSucces != true && r < 10000; r++)
{
Random rndP = new Random();
int rndPress = rndP.Next(1, 10);
if (rndPress == 1)
{
press1();
}
if (rndPress == 2)
{
press2();
}
//etc
}
}
I want it to first execute btnPressed1(); before pressing the next button but it saves the things it presses until it finishes the code. How do I make it finish the code before it pressed the next thing?
Applications that have a GUI are event driven. All user actions (and some other events) are posted to a message queue and handled one by one by the message loop. You can read this for some background information:
https://learn.microsoft.com/en-us/windows/win32/winmsg/about-messages-and-message-queues
If you write code like your for loop that takes a long time to execute, then you are blocking this message loop from handling any events.
One way you can avoid blocking the message loop is by rewriting rndPlay as an async method:
private async Task rndPlay()
{
for (int r = 0; moveSucces != true && r < 10000; r++)
{
Random rndP = new Random();
int rndPress = rndP.Next(1, 10);
if (rndPress == 1)
{
press1();
}
if (rndPress == 2)
{
press2();
}
//etc
await Task.Delay(1000);
}
}
This has the effect of pausing the execution of rndPlay at each await, and posting the continuation of the method execution to the message queue after a 1000ms delay. That way, other events on the message queue get a chance of getting processed by the message loop.
Note that your example code doesn't show the caller of rndPlay. You might have to make some changes there also.
I have a panel whose AutoScroll = true;
I can scroll the panel using scrollbars.
I also find mousewheel "vertical scroll" with "mouse" wheel by writing:
void panelInner_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
{
panelInner.Focus();
}
However, I want to scroll horizontally by "wheeling mouse + shift" too.
What do I need to do for that to happen?
In your designer file, you'll need to manually add a MouseWheel event delegate.
this.panelInner.MouseWheel += new System.Windows.Forms.MouseEventHandler(this.panelInner_MouseWheel);
Then, in your code behind, you can add the following.
private const int WM_SCROLL = 276; // Horizontal scroll
private const int SB_LINELEFT = 0; // Scrolls one cell left
private const int SB_LINERIGHT = 1; // Scrolls one line right
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
private void panelInner_MouseWheel(object sender, MouseEventArgs e)
{
if (ModifierKeys == Keys.Shift)
{
var direction = e.Delta > 0 ? SB_LINELEFT : SB_LINERIGHT;
SendMessage(this.panelInner.Handle, WM_SCROLL, (IntPtr)direction, IntPtr.Zero);
}
}
References:
Shift + mouse wheel horizontal scrolling
Mouse tilt wheel horizontal scrolling in C#
I have a ListView in which i want to create an event when the VScrollBar appears. I actully dont want a horizontal scrollbar and whenever the VScrollbar appears i want to resize the columns so that it fits the window. I already can check for the visiblity of a scrollbar but i dont know the name of the event which is triggered when the ScrollBars appear.
Here is my code :
private const int WS_VSCROLL = 0x200000;
private const int GWL_STYLE = -16;
[DllImport("user32.dll")]
public static extern int GetWindowLong(IntPtr hWnd, int Index);
private static bool IsScrollbarVisible(IntPtr hWnd)
{
bool bVisible = false;
int nMessage = WS_VSCROLL;
int nStyle = GetWindowLong(hWnd, GWL_STYLE);
bVisible = ((nStyle & nMessage) != 0);
return bVisible;
}
And Works Like this :
if (IsScrollbarVisible(listview.Handle))
{
columnHeader1.Width = listview.ClientRectangle.Width - (columnHeader2.Width + columnHeader3.Width);
}
Someone Please Help Me!
ClientSizeChanged Event will fire but to get it work correct we have to add BeginUpdate() and EndUpdate()..
This Code does everything :
private void listview_ClientSizeChanged(object sender, EventArgs e)
{
listview.BeginUpdate();
if (IsScrollbarVisible(listview.Handle))
{
columnHeader1.Width = listview.ClientRectangle.Width - (columnHeader2.Width + columnHeader3.Width);
}
listview.EndUpdate();
}
How do I implement Auto-Scrolling (e.g. the ListView scrolls when you near the top or bottom) in a Winforms ListView? I've hunted around on google with little luck. I can't believe this doesn't work out of the box!
Thanks in advance
Dave
Thanks for the link (http://www.knowdotnet.com/articles/listviewdragdropscroll.html), I C#ised it
class ListViewBase:ListView
{
private Timer tmrLVScroll;
private System.ComponentModel.IContainer components;
private int mintScrollDirection;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
const int WM_VSCROLL = 277; // Vertical scroll
const int SB_LINEUP = 0; // Scrolls one line up
const int SB_LINEDOWN = 1; // Scrolls one line down
public ListViewBase()
{
InitializeComponent();
}
protected void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.tmrLVScroll = new System.Windows.Forms.Timer(this.components);
this.SuspendLayout();
//
// tmrLVScroll
//
this.tmrLVScroll.Tick += new System.EventHandler(this.tmrLVScroll_Tick);
//
// ListViewBase
//
this.DragOver += new System.Windows.Forms.DragEventHandler(this.ListViewBase_DragOver);
this.ResumeLayout(false);
}
protected void ListViewBase_DragOver(object sender, DragEventArgs e)
{
Point position = PointToClient(new Point(e.X, e.Y));
if (position.Y <= (Font.Height / 2))
{
// getting close to top, ensure previous item is visible
mintScrollDirection = SB_LINEUP;
tmrLVScroll.Enabled = true;
}else if (position.Y >= ClientSize.Height - Font.Height / 2)
{
// getting close to bottom, ensure next item is visible
mintScrollDirection = SB_LINEDOWN;
tmrLVScroll.Enabled = true;
}else{
tmrLVScroll.Enabled = false;
}
}
private void tmrLVScroll_Tick(object sender, EventArgs e)
{
SendMessage(Handle, WM_VSCROLL, (IntPtr)mintScrollDirection, IntPtr.Zero);
}
}
Scrolling can be accomplished with the ListViewItem.EnsureVisible method.
You need to determine if the item you are currently dragging over is at the visible boundary of the list view and is not the first/last.
private static void RevealMoreItems(object sender, DragEventArgs e)
{
var listView = (ListView)sender;
var point = listView.PointToClient(new Point(e.X, e.Y));
var item = listView.GetItemAt(point.X, point.Y);
if (item == null)
return;
var index = item.Index;
var maxIndex = listView.Items.Count;
var scrollZoneHeight = listView.Font.Height;
if (index > 0 && point.Y < scrollZoneHeight)
{
listView.Items[index - 1].EnsureVisible();
}
else if (index < maxIndex && point.Y > listView.Height - scrollZoneHeight)
{
listView.Items[index + 1].EnsureVisible();
}
}
Then wire this method to the DragOver event.
targetListView.DragOver += RevealMoreItems;
targetListView.DragOver += (sender, e) =>
{
e.Effect = DragDropEffects.Move;
};
Have a look at ObjectListView. It does this stuff. You can read the code and use that if you don't want to use the ObjectListView itself.
Just a note for future googlers: to get this working on more complex controls (such as DataGridView), see this thread.
I don't want my window to be resized either "only horizontally" or "only vertically." Is there a property I can set on my window that can enforce this, or is there a nifty code-behind trick I can use?
You can always handle the WM_WINDOWPOSCHANGING message, this let's you control the window size and position during the resizing process (as opposed to fixing things after the user finished resizing).
Here is how you do it in WPF, I combined this code from several sources, so there could be some syntax errors in it.
internal enum WM
{
WINDOWPOSCHANGING = 0x0046,
}
[StructLayout(LayoutKind.Sequential)]
internal struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public int flags;
}
private void Window_SourceInitialized(object sender, EventArgs ea)
{
HwndSource hwndSource = (HwndSource)HwndSource.FromVisual((Window)sender);
hwndSource.AddHook(DragHook);
}
private static IntPtr DragHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handeled)
{
switch ((WM)msg)
{
case WM.WINDOWPOSCHANGING:
{
WINDOWPOS pos = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));
if ((pos.flags & (int)SWP.NOMOVE) != 0)
{
return IntPtr.Zero;
}
Window wnd = (Window)HwndSource.FromHwnd(hwnd).RootVisual;
if (wnd == null)
{
return IntPtr.Zero;
}
bool changedPos = false;
// ***********************
// Here you check the values inside the pos structure
// if you want to override tehm just change the pos
// structure and set changedPos to true
// ***********************
if (!changedPos)
{
return IntPtr.Zero;
}
Marshal.StructureToPtr(pos, lParam, true);
handeled = true;
}
break;
}
return IntPtr.Zero;
}
You can reserve aspect ratio of contents using WPF's ViewBox with control with fixed width and height inside.
Let's give this a try. You can change "Stretch" attribute of ViewBox to experience different results.
Here is my screeen shot:
<Window x:Class="TestWPF.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Viewbox Stretch="Uniform">
<StackPanel Background="Azure" Height="400" Width="300" Name="stackPanel1" VerticalAlignment="Top">
<Button Name="testBtn" Width="200" Height="50">
<TextBlock>Test</TextBlock>
</Button>
</StackPanel>
</Viewbox>
</Window>
This is what my solution was.
You will need to add this to your control/window tag:
Loaded="Window_Loaded"
And you will need to place this in your code behind:
private double aspectRatio = 0.0;
private void Window_Loaded(object sender, RoutedEventArgs e)
{
aspectRatio = this.ActualWidth / this.ActualHeight;
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
if (sizeInfo.WidthChanged)
{
this.Width = sizeInfo.NewSize.Height * aspectRatio;
}
else
{
this.Height = sizeInfo.NewSize.Width * aspectRatio;
}
}
I tried the Viewbox trick and I did not like it. I wanted to lock the window border to a specific size. This was tested on a window control but I assume it would work on a border as well.
You could try replicating an effect that I often see on Flash Video websites. They allow you to expand the browser window any way you like, but only stretch the presentation area so that it fits the smallest of the height or width.
For example, if you stretch the window vertically, your application would not resize. It would simple add black bars to the top and bottom of the display area and remain vertically centered.
This may or may not be possible with WPF; I don't know.
This may be bit late but you can simply put it in your code behind....
Private Sub UserControl1_SizeChanged(ByVal sender As Object, ByVal e As System.Windows.SizeChangedEventArgs) Handles Me.SizeChanged
If e.HeightChanged Then
Me.Width = Me.Height
Else
Me.Height = Me.Width
End If
End Sub
I had expected that you could two-way bind the width to the height using a value converter to maintain aspect ratio. Passing the aspect ratio as the converter parameter would make it more general purpose.
So, I tried this - the binding with no converter first:
<Window
...
Title="Window1" Name="Win" Height="500"
Width="{Binding RelativeSource={RelativeSource self},
Path=Height, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<StackPanel>
<TextBlock>Width:</TextBlock>
<TextBlock Text="{Binding ElementName=Win, Path=Width}" />
<TextBlock>Height:</TextBlock>
<TextBlock Text="{Binding ElementName=Win, Path=Height}" />
</StackPanel>
</Window>
Strangely, the binding is behaving as if it is one-way and the reported width of the window (as shown in the TextBlock) is not consistent with it's size on screen!
The idea might be worth pursuing, but this strange behavior would need to be sorted out first.
Hope that helps!
In the code sample:
if (sizeInfo.WidthChanged)
{
this.Width = sizeInfo.NewSize.Height * aspectRatio;
}
else
{
this.Height = sizeInfo.NewSize.Width * aspectRatio;
}
I believe the second computation should be:
this.Height = sizeInfo.NewSize.Width * (1/aspectRatio);
I made a variation of this work in a "SizeChanged" event handler. Since I wanted the width to be the controlling dimension, I simply forced the height to match to it with a computation of the form:
if (aspectRatio > 0)
// enforce aspect ratio by restricting height to stay in sync with width.
this.Height = this.ActualWidth * (1 / aspectRatio);
You may note the check for an aspectRatio > 0, ... I did this because I found that it was tending to call my handlers that did the resizing before the "Load" method had even assigned the aspectRatio.
Maybe too late, but i found a solution from Mike O'Brien blog, and it work really good.
http://www.mikeobrien.net/blog/maintaining-aspect-ratio-when-resizing/
Below is code from his blog:
<Window ... SourceInitialized="Window_SourceInitialized" ... >
...
Window>
public partial class Main : Window
{
private void Window_SourceInitialized(object sender, EventArgs ea)
{
WindowAspectRatio.Register((Window)sender);
}
...
}
internal class WindowAspectRatio
{
private double _ratio;
private WindowAspectRatio(Window window)
{
_ratio = window.Width / window.Height;
((HwndSource)HwndSource.FromVisual(window)).AddHook(DragHook);
}
public static void Register(Window window)
{
new WindowAspectRatio(window);
}
internal enum WM
{
WINDOWPOSCHANGING = 0x0046,
}
[Flags()]
public enum SWP
{
NoMove = 0x2,
}
[StructLayout(LayoutKind.Sequential)]
internal struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public int flags;
}
private IntPtr DragHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handeled)
{
if ((WM)msg == WM.WINDOWPOSCHANGING)
{
WINDOWPOS position = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));
if ((position.flags & (int)SWP.NoMove) != 0 ||
HwndSource.FromHwnd(hwnd).RootVisual == null) return IntPtr.Zero;
position.cx = (int)(position.cy * _ratio);
Marshal.StructureToPtr(position, lParam, true);
handeled = true;
}
return IntPtr.Zero;
}
}
I got a way that doesn't depend on Windows platform-specific API also with acceptable user experience (not shake while dragging the window). It uses a timer to adjust the window size after 0.1 seconds so user won't see it shakes.
public partial class MainWindow : Window
{
private DispatcherTimer resizeTimer;
private double _aspectRatio;
private SizeChangedInfo? _sizeInfo;
public MainWindow()
{
InitializeComponent();
_aspectRatio = Width / Height;
resizeTimer = new DispatcherTimer();
resizeTimer.Interval = new TimeSpan(100*10000); // 0.1 seconds
resizeTimer.Tick += ResizeTimer_Tick;
}
private void ResizeTimer_Tick(object? sender, EventArgs e)
{
resizeTimer.Stop();
if (_sizeInfo == null) return;
var percentWidthChange = Math.Abs(_sizeInfo.NewSize.Width - _sizeInfo.PreviousSize.Width) / _sizeInfo.PreviousSize.Width;
var percentHeightChange = Math.Abs(_sizeInfo.NewSize.Height - _sizeInfo.PreviousSize.Height) / _sizeInfo.PreviousSize.Height;
if (percentWidthChange > percentHeightChange)
this.Height = _sizeInfo.NewSize.Width / _aspectRatio;
else
this.Width = _sizeInfo.NewSize.Height * _aspectRatio;
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
_sizeInfo = sizeInfo;
resizeTimer.Stop();
resizeTimer.Start();
base.OnRenderSizeChanged(sizeInfo);
}
}