I need to be able to programmatically scroll a window up and down given only a point on a screen. I've managed to retrieve a handle using Windows API, but I am unable to get it to scroll up or down.
Assume the following code:
//retrieves the correct window.
IntPtr hWnd = Win32.WindowFromPoint(new Point(xPos, yPos));
Win32.Rect rect = default(Win32.Rect);
//retrieves a rectangle with the desired windows dimensions
Win32.GetWindowRect(hWnd, ref rect);
//Insert scroll code here...
to scroll a window you need to send it a windows message by calling SendMessage with the appropriate parameters - for full details regarding scrolling and associated messages etc. see MSDN.
UPDATE - as per comments:
Another option might be to call ScrollWindowEx on the hWnd - as per comments calling ScrollwindowEx should NOT be used since it would create an inconsistency between the displayed state and the internal state of the respective window!
Have you tried using SendMessage() function with WM_VSCROLL and WM_HSCROLL messages?
Also check SetScrollInfo (pInvoked version here). Even this post may be helpful to you.
Related
The background: Most of us know the SysListView32 common control and the equivalent wrapper ListView class provided by the .NET Framework. A little depth into its internals show that the scroll bars it provides for scrolling its contents are NOT controls themselves, but are managed by the SysListView32 control.
The goal: Always draw scroll bars even if it has no ListViewItems to display or has very few such that no scroll bars are needed anyway; sort of like mimicking the RichTextBox class with its ScrollBars property set to ForcedBoth. Or kinda like this ListBox:
The problem(s):
.NET has NO sugar at all for scroll bars within a ListView.
Win32 documentation does not state when to show/hide and/or enable/disable scrollbars.
My workaround(s):
override the WndProc in a derived class and handle its WM_HSCROLL and WM_VSCROLL messages as per steps 2 and 3.
Call base.WndProc to do the actually required processing of the scroll functionality.
Create a method like WmScroll and do my processing on it immediately after base.WndProc has returned.
This consists of a p/invoke call to GetScrollInfo. Determine if a scroll bar is actually needed. If it's not then call ShowScrollBar and EnableScrollBar with required values to draw visibly disabled scroll bars.
Problems with the workaround:
It barely works. The scroll bars are displayed and disabled but are like the ones under Windows Classic Theme.
It hides the collapse buttons of each ListViewGroup, rendering them useless!
The descriptive image:
The long awaited actual question:
How do I force scroll bars to always be Visible within a ListView irrespective of the number of ListViewItems and disable them if they are unnecessary, at the same time avoiding size miscalculation (to display collapse buttons of the ListViewGroups) and theme deterioration?
Answers without code, and answers with code in C#, VB.NET and C++/CLR are welcome. If you post code in any other language supported by .NET, please also leave a link to a code conversion website I may use if the code seems, uh, incomprehensible.
Information:
Firstly, I have to admit this is an okay answer and not the best/most efficient one. If you have a different answer from mine, please post it.
Secondly, this answer owes some credit to Plutonix's answer, experimenting with which I learned that by default ListView does not have WS_HSCROLL | WS_VSCROLL flags set in its styles.
This is why my previous workaround had problem with themes.
These Classic scroll bars are ones Windows provides to Controls that do not have these flags set.
Changing the CreateParams does not work either. You have to set it manually in the OnHandleCreated method using SetWindowLong.
The solution I am posting does not use the above technique. Apparently, calling ShowScrollBar for each window message forces these flags to be set.
The Solution:
Define your WndProc like the following:
protected override void WndPoc(ref Message m)
{
//custom code before calling base.WndProc
base.WndProc(ref m);
//custom after base.WndProc returns
WmScroll(); //VERY INEFFICIENT, called for each message :(
}
Define WmScroll() as follows:
protected virtual void WmScroll()
{
NativeMethods.ShowScrollBar(Handle, SB_BOTH, true);
//si.fMask = SIF_PAGE | SIF_RANGE <- initialized in .ctor
NativeMethods.GetScrollInfo(Handle, SB_HORZ, ref si);
if(si.nMax < si.nPage)
NativeMethods.EnableScrollBar(Handle, SB_HORZ, ESB_DISABLE_BOTH);
else
NativeMethods.EnableScrollBar(Handle, SB_HORZ, ESB_ENABLE_BOTH);
NativeMethods.GetScrollInfo(Handle, SB_VERT, ref si);
if(si.nMax < si.nPage)
NativeMethods.EnableScrollBar(Handle, SB_VERT, ESB_DISABLE_BOTH);
else
NativeMethods.EnableScrollBar(Handle, SB_VERT, ESB_ENABLE_BOTH);
}
Output:
It now, looks like:
These are with another item added featuring the horizontal scroll and working ListViewGroup collapse button:
Imperfection, yes there is:
A call to AutoResizeColumns is required if group collapse changes effective text width, otherwise the vertical scroll bar hides the collapse buttons.
The background: Most of us know the SysListView32 common control and the equivalent wrapper ListView class provided by the .NET Framework. A little depth into its internals show that the scroll bars it provides for scrolling its contents are NOT controls themselves, but are managed by the SysListView32 control.
The goal: Always draw scroll bars even if it has no ListViewItems to display or has very few such that no scroll bars are needed anyway; sort of like mimicking the RichTextBox class with its ScrollBars property set to ForcedBoth. Or kinda like this ListBox:
The problem(s):
.NET has NO sugar at all for scroll bars within a ListView.
Win32 documentation does not state when to show/hide and/or enable/disable scrollbars.
My workaround(s):
override the WndProc in a derived class and handle its WM_HSCROLL and WM_VSCROLL messages as per steps 2 and 3.
Call base.WndProc to do the actually required processing of the scroll functionality.
Create a method like WmScroll and do my processing on it immediately after base.WndProc has returned.
This consists of a p/invoke call to GetScrollInfo. Determine if a scroll bar is actually needed. If it's not then call ShowScrollBar and EnableScrollBar with required values to draw visibly disabled scroll bars.
Problems with the workaround:
It barely works. The scroll bars are displayed and disabled but are like the ones under Windows Classic Theme.
It hides the collapse buttons of each ListViewGroup, rendering them useless!
The descriptive image:
The long awaited actual question:
How do I force scroll bars to always be Visible within a ListView irrespective of the number of ListViewItems and disable them if they are unnecessary, at the same time avoiding size miscalculation (to display collapse buttons of the ListViewGroups) and theme deterioration?
Answers without code, and answers with code in C#, VB.NET and C++/CLR are welcome. If you post code in any other language supported by .NET, please also leave a link to a code conversion website I may use if the code seems, uh, incomprehensible.
Information:
Firstly, I have to admit this is an okay answer and not the best/most efficient one. If you have a different answer from mine, please post it.
Secondly, this answer owes some credit to Plutonix's answer, experimenting with which I learned that by default ListView does not have WS_HSCROLL | WS_VSCROLL flags set in its styles.
This is why my previous workaround had problem with themes.
These Classic scroll bars are ones Windows provides to Controls that do not have these flags set.
Changing the CreateParams does not work either. You have to set it manually in the OnHandleCreated method using SetWindowLong.
The solution I am posting does not use the above technique. Apparently, calling ShowScrollBar for each window message forces these flags to be set.
The Solution:
Define your WndProc like the following:
protected override void WndPoc(ref Message m)
{
//custom code before calling base.WndProc
base.WndProc(ref m);
//custom after base.WndProc returns
WmScroll(); //VERY INEFFICIENT, called for each message :(
}
Define WmScroll() as follows:
protected virtual void WmScroll()
{
NativeMethods.ShowScrollBar(Handle, SB_BOTH, true);
//si.fMask = SIF_PAGE | SIF_RANGE <- initialized in .ctor
NativeMethods.GetScrollInfo(Handle, SB_HORZ, ref si);
if(si.nMax < si.nPage)
NativeMethods.EnableScrollBar(Handle, SB_HORZ, ESB_DISABLE_BOTH);
else
NativeMethods.EnableScrollBar(Handle, SB_HORZ, ESB_ENABLE_BOTH);
NativeMethods.GetScrollInfo(Handle, SB_VERT, ref si);
if(si.nMax < si.nPage)
NativeMethods.EnableScrollBar(Handle, SB_VERT, ESB_DISABLE_BOTH);
else
NativeMethods.EnableScrollBar(Handle, SB_VERT, ESB_ENABLE_BOTH);
}
Output:
It now, looks like:
These are with another item added featuring the horizontal scroll and working ListViewGroup collapse button:
Imperfection, yes there is:
A call to AutoResizeColumns is required if group collapse changes effective text width, otherwise the vertical scroll bar hides the collapse buttons.
If i create a C# console application which sets the console Buffer/Window Width and Height (using Console.*-Methods) to 80x25 (or any other specific size) the console-window has no scrollbars.
When i use GetConsoleScreenBufferInfoEx to read the consolebuffer-settings it reports a window.Right and window.Bottom of 79x24. If i write the read CONSOLE_SCREEN_BUFFER_INFO_EX back the window will get scrollbars.
Question: Are there any C#/.NET-framework methods interfering or is this standard behavior?
I've seen examplecode for C on the internet which is always doing window.Right++ and window.Bottom++.
For the first question, I think you're getting scroll bars because your window is actually getting smaller. It's a bug.
See 35901572 and note, for reference that the examples on PInvoke
always fix the size.
Try adding a couple lines after you call GetConsoleScreenBufferInfoEx:
GetConsoleScreenBufferInfoEx(handle, ref currentScreen);
++currentScreen.srWindowBottom;
++currentScreen.srWindowRight;
I have a toolbar application which changes the work area of the desktop and positions itself in the gap (either at the top or bottom of the screen). I need a function which will resize the other windows so that they are not left behind the toolbar after the resizing (the toolbar is always on top). I'm using the function below to change the work area:
private bool SetWorkspace(RECT rect)
{
bool result = SystemParametersInfo(SPI_SETWORKAREA,
0,
ref rect,
SPIF_change);
if (!result)
{
MessageBox.Show("The last error was: " +
Marshal.GetLastWin32Error().ToString());
}
this.minAll();
return result;
}
and the minAll() function is how I'm resizing the other windows:
public void minAll(){
IntPtr lHwnd = FindWindow("Shell_TrayWnd", null);
SendMessage(lHwnd, WM_COMMAND, (IntPtr)MIN_ALL, IntPtr.Zero);
System.Threading.Thread.Sleep(10);
SendMessage(lHwnd, WM_COMMAND, (IntPtr)MIN_ALL_UNDO, IntPtr.Zero);
System.Threading.Thread.Sleep(10);
}
This method works, in that the workarea does get resized and no window keeps its old position outside it, but there are a few problems:
Normal (ie. not maximised or minimized) windows are sometimes made so
small that they aren't displayed, and have to be maximised from the
taskbar (Vista) to be visible
Sometimes the method makes other
windows 'always on top' when they shouldn't be
It changes the z-order of the windows, seemingly at random
The first two of these problems are fixed if you close and reopen the affected windows, but it's hardly an ideal solution and I can't even manually correct the z-order (I tried saving the z-order before the method and then restoring it afterwards, but no luck).
So, can anyone see what's wrong with how I'm doing this, or does anyone know how it's supposed to be done please?
The correct way to do this is to create an AppBar. See SHAppBarMessage. There is a C++ sample in the Knowledge Base. There is a C# sample on CodeProject.
I have a form, which sets these styles in constructor:
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
And I draw some rectangles in Paint event. There are no controls on the form. Hovewer, when I resize the form, there are black strips at right and bottom of the form. Is there any way to get rid of them? I've tried everything, listening for WM_ERASEBKGND in WndProc, manually drawing the form on WM_PAINT, implementing custom double buffer, etc. Is there anything else I could try?
I've found this:
https://connect.microsoft.com/VisualStudio/feedback/details/522441/custom-resizing-of-system-windows-window-flickers
and it looks like it is a bug in DWM, but I just hope I can do some workaround.
Please note that I must use double buffering, since I want to draw pretty intense graphic presentation in the Paint event. I develop in C# .NET 2.0, Win7.
Status Update 1
I've managed to get rid of most of the black stripes by implementing the resize functionality by myself. Hovewer there are still some minor glitches. Is there any way to do resize and paint operation at once? Here is a pseudo-code of what I need to do:
IntPtr hDC;
var size = new Size(250, 200);
IntPtr handle = API.PaintAndResizeBegin(this.Handle /* Form.Handle */,
size.Width, size.Height, out hDC);
using (var g = Graphics.FromHdc(hDC)) {
this.backBuffer.Render(g, size);
}
API.PaintAndResizeCommit(handle);
Is there any way to implement the above code?
The second solution could be to back-buffer whole form, including non-client area. But how to do that? I don't want to paint the non-client area by myself, as I want to keep the nice aero effect on Vista/7. Any help will be deeply appreciated.
Status Update 2
It looks like this problem is unsolvable, since it is omnipresent on Windows, in every application. We can just hope that MS will take some inspiration in Mac OS X and will provide appropriate APIs in new Windows.
I've found the function which can paint and resize window at the same time - UpdateLayeredWindow.
So now it should be possible to create resizable windows, which do not have any strips while being resized. However, you need to paint the window content yourself, so it is a little inconvenient. But I think that using WPF and UpdateLayeredWindow, there shouldn't be any problem.
Update
Found problems. :-) When using UpdateLayeredWindow, you must paint the window's border yourself. So, if you want standard window painted using UpdateLayeredWindow with nice glass effect in win7, you are screwed.
On Microsft Connect is even a thread about this problem, where Microsoft says it is a bug by design, and if it ever gets fixed, then probably in Win8 or some newer system. So there isn't much we could do about this.
I found that it is best not to do any custom rendering directly on the Form surface. Instead, put a docked PictureBox on the form, create Bitmap object that will be displayed in the PictureBox, draw everything onto that using the System.Drawing.Graphics.FromImage(Image) method.
I used that method with a game loop to make a simple shooter game (Crimsonland-style) and got pretty good performance (with anti-aliased lines), above 100 FPS.