How to implement my own scrolling in windows form - winforms

I have an MdiClient derived from Form and I use the surface of this control for GDI+ drawing. I run into troubles implementing my own scrolling for this control. I set both AutoScroll and AutoSize properties to false and try to use form's own horizontal/vertical scrollbars instead of placing my own. Observed form's behavior is quite confusing. To begin with there are two properties (A) HScroll and (B) HorizontalScroll that also allows access to Visible attribute.
I ended up setting HorizontalScroll.Visible = true and leaving HScroll = false (same for vertical) but am curious why there are two of them. Documentation implies that both control visibility of horizontal scroll bar but they do not appear to access the same data. Besides, it looks like HScroll is being reset on every paint. At the moment I ignore existence of HScroll/VScroll. Is it OK for my application?
What is more critical for me is the ability control placement of the thumb on scroll bars. I set VerticalScroll attributes Minimum = 0, Maximum = 100, and Value = 50 but when form is displayed thumb is positioned at the start of scrollbar not in the middle. Why? Also when user clicks on horizontal scrollbar an event handler for horizontal scrolling is invoked but meanwhile form has already reset VerticalScroll.Value to 0 (without raising vertical scroll event). What is going on?
I probably don't understand how framework expects me to implement what I need. Can someone shed some light.

Credit goes to LarsTech who pointed me towards good solution. Setting large AutoScrollMinSize automatically does whatever needs to be done enabling and controlling form scrollbars. There is one potential trap to watch for. Be aware that programmatic attempts to set AutoScrollPosition will be ignored until form is shown. So if you want your form to open with scrollbars not in the default (0,0) position then place your code inside form_shown event handler.

Just set the AutoScrollMinSize to your desired canvas.
Quick example:
using System.Drawing;
using System.Drawing.Drawing2D;
private void Form1_Load(object sender, EventArgs e)
{
this.AutoScrollMinSize = new Size(1200, 1200);
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(SystemColors.Window);
using (Matrix mx = new Matrix(1, 0, 0, 1, this.AutoScrollPosition.X, this.AutoScrollPosition.Y))
{
e.Graphics.Transform = mx;
e.Graphics.FillEllipse(Brushes.Red, new Rectangle(250, 250, 100, 100));
}
}
See these links: Understanding Windows Forms AutoScroll and How to back-track the mouse to the virtual page.

Related

c# MouseWheel Scroll and RTB VScroll Event

I am a bit confused:
We have a chat application that has a requirement to NOT scroll chat, if the user is scrolled up; and we have a scrollInfo class that provides us with the ability to check if the scroll bar thumb is at the bottom of the scrollbar, so that we can identify if we should scroll or not.
Unfortunately, there are many different ways to scroll chat. One could use the scrollbars up/down boxes, or the scrollbar thumb, or just scroll using the mouse wheel.
However, if the up/down arrows are used, we have no issue.
And, if the scrollbar thumb is used, the VScroll event does fire, but we have no way of identifying which DIRECTION the thumb is moved, or how to check if it's being held down, without using a timer to constantly query the left mouse button status.
And, lastly, if mousewheel is scrolled, while we can tell which direction it's going by looking at the Delta property, the VScroll event still fires after the Mouse Wheel event is handled. So, we essentially have a duplicate scroll happen.
I need some help with this. Our chat application is as follows:
.NET 4.0 Windows Forms C# Application
Built in Visual Studio 2010
*Chat Output: * RichTextBox that auto formats text entering it.
Here's an example of our logic, that just isn't working:
User Scrolls Up using Mouse wheel: When new messages come in, they should NOT scroll the chat output box back down. Instead, when a scroll event happens, it should be able to detect where the scroll thumb is, and make that determination.
User Scrolls up using the ScrollBar thumb: VScroll event is fired, which checks if the scrollbar is at the 'bottom' of the scroll box. And, if it is, it does the full scroll event, to ensure that the caret is always placed at the end of the scroll box, and is hidden (so that the READ ONLY chat ouput, has no actual blinking I beam).
User scrolls using the scrollbar up/down arrows: works just fine. (no need to explain this one, as it's already working).
I need some clarification here, how can i properly check these events?
-------- EDIT FOR CLARIFICATION ---------
Contents of scrollInfo class:
internal class Scrollinfo
{
internal const uint ObjidVscroll = 0xFFFFFFFB;
[DllImport("user32.dll", SetLastError = true, EntryPoint = "GetScrollBarInfo")]
private static extern int GetScrollBarInfo(IntPtr hWnd,
uint idObject,
ref Scrollbarinfo psbi);
internal static bool CheckBottom(Control rtb, int postion)
{
var info = new Scrollbarinfo();
info.CbSize = Marshal.SizeOf(info);
var chk = GetScrollBarInfo(rtb.Handle,
ObjidVscroll,
ref info);
if (chk == 0)
GetScrollBarInfo(rtb.Handle,
ObjidVscroll,
ref info);
bool isbottom = info.XyThumbBottom
>= (info.RcScrollBar.Bottom - info.RcScrollBar.Top - (info.DxyLineButton + 1));
if (info.DxyLineButton <= 0) isbottom = true;
if (info.XyThumbBottom <= 0) isbottom = true;
return isbottom;
}
}
internal struct Scrollbarinfo
{
internal int CbSize;
internal Rect RcScrollBar;
internal int DxyLineButton;
internal int XyThumbTop;
internal int XyThumbBottom;
internal int Reserved;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
internal int[] Rgstate;
}
internal struct Rect
{
internal int Left;
internal int Top;
internal int Right;
internal int Bottom;
}}
You could try something like this:
Update the box with the new text to be displayed and then find the last visible line using the following code:
rtb.GetLineFromCharIndex(rtb.GetCharIndexFromPosition(new Point(rtb.Width, rtb.Height)));
Now next time that you need to put text into the RichTextBox, just check for the last visible line using the same code as above.
If the new last visible line is less than the older one, it means the user has scrolled upwards (or the RichTextBox has been resized). In either case, you'll just have to move the caret to the end of the text and then call the RichTextBox.ScrollToCaret() method to restore the position.
I also recommend you take a look at the following on the MSDN site:
RichTextBox Methods
RichTextBox.GetCharIndexFromPosition Method
RichTextBox.GetLineFromCharIndex Method
RichTextBox.Select Method¹
RichTextBox.ScrollToCaret Method
¹ This can be used to set the caret position.

How to make label transparent without any flickering at load time

I have a panel and on that I've a picturebox. There are around 20 labels that I've to show in the panel. I want the background of Label to be transparent ie the image in picturebox is shown and the label displays only the text.
Now since labels do not exhibit true transparency I made the labels child of picturebox
this.lbl1.Parent = pictureBox1;
This has solved my immediate problem but now when the form loads, all the labels take a while to become visible and do so one at a time. I'd appreciate if you guys can give some solution for this.
Thanks in advance
The standard cure for flicker is double-buffering. But that cannot solve this kind of flicker. It is a different kind, caused by having multiple windows overlapping each other. Each label is its own window. When the form needs to paint itself, it draws its background leaving holes for the child windows. Each child window then takes a turn drawing itself. And their child windows draw themselves next. Etcetera.
This becomes noticeable when one control takes a while to draw, no doubt your picture box. Especially when it displays a large image that needs to be resized. The holes for the child windows stay unpainted while the picture box draws. They have a white background, black when you use the form's TransparencyKey or Opacity property. This can contrast badly with the image in your picture box, that effect is perceived by the user as flicker.
One immediate cure is to not use controls so you don't pay for their window. A Label is very convenient but it is a massive waste of system resources to burn up a window just to display a string. You can simply implement the picture box' Paint event and draw the strings with TextRenderer.DrawText(). PictureBox has double-buffering turned on by default so the image as well as the text is drawn completely smoothly, no more flicker. The obvious disadvantage is that you lose the convenience of point-and-click, you have to write code.
There are other fixes possible. One of them is to prevent the picture box from leaving holes for the child windows. It will draw the entire image, the labels pop on top of them. That's still flicker but not nearly as noticeable. Add a new class to your project and paste this code:
using System;
using System.Windows.Forms;
internal class MyPictureBox : PictureBox {
protected override CreateParams CreateParams {
get {
var parms = base.CreateParams;
parms.Style &= ~0x02000000; // Turn off WS_CLIPCHILDREN
return parms;
}
}
}
Compile and drop the new picture box control from the top of the toolbox onto your form.
Yet another possible workaround is to make the form and all of its children double-buffered. This doesn't speed up the painting at all but all of the windows get rendered into a memory buffer, the result is blitted to the screen. You'll notice a delay but the window suddenly pops on the screen. This is called compositing. Winforms doesn't support this directly since it can have side-effects but it is easy to enable. Paste this code into your form class:
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED
return cp;
}
}
Supported by XP and later. Watch out for painting artifacts.
or you can ditch the labels and draw the text yourself:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
TextRenderer.DrawText(e.Graphics, "Label1", SystemFonts.DefaultFont,
new Point(10, 10), Color.Black, Color.Empty);
}
The label does not support transparency, you must create your own unique custom control, you can see these code examples.
http://www.codeproject.com/KB/dotnet/transparent_controls_net.aspx http://www.codeproject.com/KB/vb/uLabelX.aspx
Bye

Form height problem when FormBorderStyle is NONE

I have a borderless form (FormBorderStyle = None) with the height of 23 pixels (set in the designer)
When .NET draws my form at runtime - it draws it 38 pixels high (it adds the height of a title-bar for some reason).
MessageBox.Show(this.Height.ToString()); //this shows 38!! why?
To work it around I have to set "Height = 23;" in the Form_Load event.
private void MyForm_Load(object sender, EventArgs e)
{
this.Height = 23; //workaround. wtf??
}
You can try this yourself in Visual Studio 2010 (Winforms App, target Framework - 2.0).
Wtf?
Yeah, it is a bug, of sorts. Note how in the designer you set the size of the form with the Width and Height properties. Those properties include the size of the borders and the title bar. That's a problem however, your form may run on a machine where the user has increased, say, the title bar font size. That then would reduce the size of the window's client area. Or in other words, the form's ClientSize property would change on that machine. Leaving less room for the controls and messing up the design of your form pretty badly.
There's code inside the Form class that runs after the Handle is created, right before the Load event runs. It recalculates the Size of the form, using the same ClientSize you had on your machine. Now everything is good, the Height of the form won't match the one you set in the designer but the form otherwise looks the same and the layout of the controls is identical.
That same code also ensures that the window doesn't get too small. And that's where it falls over, it doesn't pay enough attention to the FormBorderStyle property. Clipping the height to the title bar size plus the client area height, as you found out. It also prevents the form getting too narrow, trying to make sure that the icon and min/max/close buttons are always visible. Even if you don't have any.
The workaround is to change the ClientSize after this code runs, the OnLoad override or Load event handler is the right place for that. Beware that if you hard-code the form size like this then you should also set the AutoScaleMode property to None. Make sure that this doesn't cause trouble on a machine that has a different DPI setting.

WPF - Expand Window to the Left

I have a WPF window with expandable panel (via Expander). The panel is on the left side of the window, and when expanded the window grows to fit the content.
By default, windows are anchored to the top-left, so my window grows to the right. I'd like the window to grow to the left.
I tried to do the following in the Window.SizeChanged event:
private void onWindowSizeChanged(object sender, SizeChangedEventArgs e)
{
Left -= (e.NewSize.Width - e.PreviousSize.Width)
}
and it works, but the growth is jerky, and I'd like to find a smoother solution.
I managed to overcome this using a simple solution: Hide & Show.
Here's the code:
protected override void OnRenderSizeChanged(SizeChangeInfo sizeInfo)
{
if (!sizeInfo.WidthChanged)
{
base.OnRenderSizeChanged(sizeInfo);
return;
}
Hide();
base.OnRenderSizeChanged(sizeInfo);
Left -= (sizeInfo.NewSize.Width - sizeInfo.PreviousSize.Width);
Show();
}
I replaced the event handler for Window.SizeChanged with this override of FrameworkElement.OnRenderSizeChanged.
I haven't tried to make a Window grow to the left like what you're requesting, but if all else fails, I would consider templating a button to look like the expander button. Then instead of trying to make your Window grow to the left, make a new Window grow to the left of your primary Window using Transforms.
UPDATE
Well, the poor rendering performance could be video card related, layout (overly complex) related, or both. I've got an idea that might do the trick for you. Jeff Prosise blogged about a magnifying glass in Silverlight that uses a WriteableBitmap to achieve the desired effect. I thought, "why not use a WriteableBitmap to create a screenshot of your layout to the right of the Expander, and cover up the other elements with it?". I think that if you do this and hide the underlying elements (so they don't get adjusted), rendering performance will be much improved.
I got Jeff's code to work in WPF with little modification.
http://www.wintellect.com/CS/blogs/jprosise/archive/2009/10/29/more-fun-with-silverlight-3-s-writeablebitmap.aspx
Solution 1
Try to use Window property: SizeToContent="width" this will scale your window to the size of your content and you can scale your content using animation and easing, this will make scaling of the window nice and smooth.
Solution 2
You could create a window which is bigger than it's content and make your background transparent. You still have to add background to some element.
Here is an example of how it may look like:
You may put your expander in a grid (where the column size can change) and then set the ExpandDirection property of your expander to left ?

wpf: capturing mouse does not work

I am developing an kind of outlook calendar application where I need to make the appointment resizable from mouse.
My first try with a thumb did not work properly so I tried another way.
What I did is that:
1) on the botton of the appointmennt panel I added a rectangle to figure out the resize zone (the thumb). The appointment panel is put on a grid panel.
2) I intercept down event on the rectangle and send event to this code:
private Point startPoint;
private void OnResizeElementMouseDown(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
this.MouseMove += new MouseEventHandler(ResizeEndElement_MouseMove);
this.MouseLeftButtonUp += new MouseButtonEventHandler(OnResizeElementMouseUp);
// some code to perform new height computation
Mouse.Capture(this);
}
where this is the appointment panel that own the thumb.
Decreasing height works well.
But increasing is more difficult. If I move the mouse very very slowly it's OK, if I speed it up a little bit it tends to leave out the appointment panel and then all MouseMove event are lost.
I thought Mouse.Capture() was propose to solve this kind of problem, but in fact not.
Does anybody know what is wrong in my code?
You should be using an actual Thumb control. Check out MSDN for help:
How to: Use a Thumb to Enable Dragging
you should use a thumb, but to play with mouse capture, override the protected override void OnLostMouseCapture(MouseEventArgs e) method, then you will know if you have lost the capture.

Resources