c# MouseWheel Scroll and RTB VScroll Event - winforms

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.

Related

How to work around a Windows ListView issue?

If the listview (in Details mode) is scrolled down and then you resize the listview, and if you resize columns in the Resize event or even Layout event, the contents get corrupted badly.
To reproduce, make a new C# project and put this code in there:
private void Form1_Load(object sender, EventArgs e)
{
ListView lv = new ListView();
lv.Size = ClientSize;
lv.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top;
lv.Columns.Add(new ColumnHeader());
lv.Columns.Add(new ColumnHeader());
lv.View = View.Details;
lv.FullRowSelect = true;
lv.Resize += lv_Resize;
Controls.Add(lv);
string[] s = new string[2];
s[0] = "hrd";
s[1] = "igwegmg";
for (int i = 0; i < 20; i++)
{
lv.Items.Add(new ListViewItem(s));
}
lv_Resize(lv, null);
}
private void lv_Resize(object sender, EventArgs e)
{
ListView lv = (ListView)sender;
lv.Columns[1].Width = lv.ClientSize.Width - lv.Columns[0].Width;
}
Run it, scroll the list down a bit, resize the form to show all items by dragging the bottom of the form downwards.
Resize it smaller to where the scroll bar shows up, then it gets worse.
Or instead of resizing just maximize and restore the form.
Notice also there's a bunch (depending on how much you scrolled) of empty items at the top of the listview, you cant click on them. To restore it to normal you have to do one of two things.
Repopulate the items (not good - I have too many items and I lose my scroll position).
manually if you resize the form to where the scrollbar pops up, then you scroll the scroll bar to the bottom, then move your mouse off the scroll bar, and then back on top of the scrollbar you will see the scrollbar resize itself, at which point you can drag the scrollbar to the top, and then its back to normal.
If you RedrawItems, then at least you can see all the items again, but you still get the blank items at the top. Doing a begininvoke to a function that calls RedrawItems after a resize/layout event doesn't always work.
Any ideas on this bug? I really don't want to use any other controls or third party software.
Noticed other ListView's in my app not exhibiting the bug. Found out its because they were resizing columns inside the Form's Resize event.
Workaround: Dont Resize columns in the ListView.Resize() but do it in the Form.Resize() event.
I'll leave this open a littelwhile incase some of you already been researching for a workaround to use ListView.Resize()

OpenTK makes touching button raise click event differently than mouse

What I am trying to do: I am currently hosting an OpenTK glControl (through a WindowsFormsHost) in my WPF application. The application has many buttons, but we will focus on the pause and play buttons. I use VBO's and this is how I animate GL.DrawArrays(PrimitiveType.LineStrip, 0, frameCount );. It is drawing the range of vertices from 0 to framecount, so when I hit play, it just starts incrementing framecount, which starts animating. When just using a mouse, everything works perfectly.
The Problem: My app needs to work with a touch screen also (FWIW, when I say touch, WPF is seeing it as a stylus, not touch). When the app is NOT animating, touch works like it should, I don't handle touch events so touching buttons just raised the Click event. If we aren't animating, I don't have any problems. So when I use my mouse Click the play button, the UI still responds to my Mouse (clicks work, hovering changes colors as it should, etc), but then touches seemingly get ignored for some period of time. I have to touch a button 3 to 5 times before it does what it is supposed to (like pause). Lets go back and now we aren't animating, this time, if I TOUCH the play button to raise the Click event, it will start animating, but my touches now have the same problem of having to touch multiple times to raise Click event, and on top of that, the UI now does not respond to the mouse (the WindowsFormsHost still reacts to mouse events properly). Clicking on buttons or hovering over buttons doesn't do anything. It is not until I can get the animation to pause again that the UI starts responding to Mouse again.
What I have tried and what (I think) I know: When the UI stops responding to mouse input and touch input is messsed up, if I use MOUSE or TOUCH to click anywhere outside of my app, it works as expected. This leads me to believe it is not a touch screen driver problem or anything. I also used Snoop to see what events were being raised (or not) to see the stylus and the mouse being captured and released and what has focus. I could not find an instance where one was not released. If anyone would like, I can post Snoop results showing differences between mouse and touch clicking. I tried putting a preview mouse down on the MainWindow, but after TOUCHING play, no Mouse clicks raise this event, and the first touch after touching the play button touches randomly will fire this event. I also tried hiding the WindowsFormsHost, and mouse and touch clicks all work like they should minus the glControl. Because I hide the WindowsFormsHost, the paint event never gets fired, leading me to believe there might a problem with the paint function. I am aware of this bug WPF Touch Bug, however I don't really think that this is my problem. Something tells me the interop is what is giving me problems but I am not sure.
My Code:
private void playFwdFunc()
{
//disable undrawing and enable drawing
undraw = false;
draw = true;
//unpause animation
paused = false;
//enable/disable appropriate buttons
pauseBtn.IsEnabled = true;
stepBackBtn.IsEnabled = false;
stepFwdBtn.IsEnabled = false;
clearStart.IsEnabled = true;
clearCurrent.IsEnabled = true;
//refresh control
glControl1.Invalidate();
}
private void playFwdClick(object sender, RoutedEventArgs e)
{
playFwdFunc();
}
In my glPaint event:
if (frameCount < vertices.Length && !paused)
{
//draw more vertices
if (draw)
{
if (0 < (int)(vertices.Length / (25000 / speedTrack.Value)))
frameCount += (int)(vertices.Length / (25000 / speedTrack.Value));
else
frameCount++;
}
//draw less vertices
else if (undraw)
{
if (0 < (int)(vertices.Length / (25000 / speedTrack.Value)))
frameCount -= (int)(vertices.Length / (25000 / speedTrack.Value));
else
frameCount--;
}
//make sure we dont have a negative framecount (null pointer)
if (frameCount < 0)
{
paused = true;
frameCount = 0;
// manageCodeBox();
}
//make sure we dont exceed # of vertices (null pointer)
else if (frameCount > vertices.Length)
{
frameCount = vertices.Length;
paused = true;
// manageCodeBox();
}
}
My Question: Why is clicking a button with mouse making the UI react differently than touching a button, even though they raise the same event? Any input is appreciated.
So I found what was causing the behavior, in my paint function I was constantly raising an event that had a glControl1.Invalidate().
My Best Guess: My only real guess is that certain events like touches which occur on the stylus thread some how gets into a live-lock with the UI thread when calling glControl1.Invalidate(). I think that that is why some touches got accepted, but the UI thread never reached mouse event listeners. As I said: best guess.
My Workaround: I originally made a DispatcherTimer to call glControl1.Invalidate(). Mouse events worked fine, but because touches got promoted to mouse clicks, there was a delay unless I handled the touch event (which I did not want to do for each button on my UI). My next solution seems to be working so far.
In my windows constructor: CompositionTarget.Rendering += invalidateProcessor;.
The function:
private void invalidateProcessor(object sender, EventArgs e)
{
Dispatcher.BeginInvoke(new Action(() => { if (!paused) glControl1.Invalidate(); }), DispatcherPriority.Background);
}
I chose DispatcherPriority.Background because the higher priorities cause a delay in the touches again.

WPF TouchDevice get absolute coordinates

I try to create a window, that can be moves or resized via multi-touch gestures. I tried it this way. I captures the TouchDown-Event of the window and saved all active TouchDevices in a List, to know which TouchDevices are active. I catch updated and deactivated event of the TouchDevices to know when they are moved and when they are deactivated. I save the Left and Top Property of the Window and the position where the TouchDevice started and everytime the Updated event is called I move the Window to the new Position relative to the new position of the TouchDevice. This works if I move the finger. But if I don't move the finger (or just very little) the window suddenly began shaking (moving chaotically) and then soon disappears to a position outside of the screen.
I think the problem here is, that the function "GetTouchPoint" of the TouchDevice only give relative coordinates related to the window (even if I set the parameter null instead if the window reference). And because the Window moves the relative position of the TouchDevice (that doesn't move) changes too. So I did a research but wasn't able to find a way to determine the screen coordinates of the touch device.
So I hope anyone can help me how to get the absolute coordinates of the TouchDevice. Or can help me find another way to "DragMove" the window with touch.(I tried DragMove, but that only works for mouse clicks, not TouchDowns) Also I like to resize the window when two Touch Devices are active and therefor I also need absolute coordinates because otherwise same effect happens.
I ran into this issue because my taskbar is on the right edge of the screen, effectively pushing the maximized window to the right. This issue also arises when the application's window is not maximized, and floating somewhere on the screen.
Here is an extension method that fixes the coordinates based on the application window's position.
public static Point FixCoordinates(this Point point)
{
var left = Application.Current.MainWindow.Left;
var top = Application.Current.MainWindow.Top;
return new Point(point.X + left, point.Y + top);
}
You may want to pass in a window that hosts your touched control as a parameter. In my case it is application's main window.
Also, since you tagged the question with "multi-touch", here is a method which averages multiple touch coordinates:
public static Point GetAverage(this IEnumerable<Point> points)
{
var averageX = points.Average(p => p.X);
var averageY = points.Average(p => p.Y);
return new Point(averageX, averageY);
}
And I use it in the code like this:
private void TouchAdornment_TouchDown(object sender, TouchEventArgs e)
{
var touchPosition = (sender as UIElement).TouchesOver.Select(t => t.GetTouchPoint(null).Position).GetAverage().FixCoordinates();
}

Is there a way to ensure that a Silverlight ScrollViewer keeps the top item completely visible

In Silverlight 4, is there a way that whenever you page down a ScrollViewer (i.e click the scroll bar in the area adjacent to the thumb) whatever item is at the top is completely visible. I still need it to scroll smoothly when the thumb is dragged or the mouse wheel is used.
My client doesn't like that an item is cut in half when he pages down the list because it is cut in half both when its at the top and when its at the bottom. I suggested some sort of integral scroll, and he didn't like it. He wants it to still scroll smoothly unless paging up or down.
Edits
Here's the catch. The items are not the same size. So I have to detect the item that is at the top of the scroll viewer and Scroll it into view. Is there an easy way to do this?
The first thing you need to do is dig out the vertical scroll bar from the internals of the ScrollViewer. You can do this with the help of VisualTreeHelper. There are a number of little chunks of code in various blogs the make its use even easier. I recommend this VisualTreeEnumeration (but I would wouldn't I). With that extensions class in place you can get the vertical scroll bar with:
ScrolBar vertSB = someScrollViewer.Descendents()
.OfType<ScrollBar>()
.FirstOrDefault(sb => sb.Name = "VerticalScrollBar");
Now you can attach to is Scroll event and determine the type of scroll that occured like this:
vertSB.Scroll += (s, args) =>
{
if (args.ScrollEventType == ScrollEventType.LargeDecrement
|| args.ScrollEventType == ScrollEventType.LargeIncrement)
{
// using args.NewValue determine the correct Integral value and assign
// using someScrollViewer.ScrollToVerticalOffset
}
};

How to implement my own scrolling in windows form

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.

Resources