WPF Thumb DragDelta moving across monitors - wpf

I have a Popup control that I added a thumb to so I can drag it around the screen. The thumb's DragDelta event was overloaded with this:
private static void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
Thumb thumb = (Thumb)sender;
Popup popup = thumb.Tag as Popup;
if (popup != null)
{
popup.HorizontalOffset += e.HorizontalChange;
popup.VerticalOffset += e.VerticalChange;
}
}
The Dragging works perfectly (I used the Dragging example from here: http://www.codeproject.com/Articles/43636/WPF-A-search), except for when the popup reaches the end of the monitor and crosses over to the other (dual monitor setup). For instance if I have the popup open on the Left monitor and start dragging it right, when the right border of it touches the edge of the monitor it's movement is erratic and starts moving all around until I move further right and it displays on the other monitor.
I debugged through this scenario, and this is a numerical example of basically what happens:
At edge of screen:
HorizontalOffset = 600
HorizontalChange = 1
Move Right:
HorizontalOffset = 601
HorizontalChange = -800
HorizontalOffset = -199
HorizontalChange = 401
HorizontalOffset = 200
HorizontalChange = -150
Which gives this weird strobe effect of the popup while it moves to the other monitor; Is there something I need to do to get it to transition smoothly across monitors?

I still haven't figured out how to un-bind pop-ups to a screen, but I was able to accomplish what I needed by using the Window control instead. I made WindowStyle.None so there was no border and overloaded the MouseLeftButtonDown event with a delegate to the DragMove() method so they can be dragged around the screen. This allowed me to have very similar look and feel as the pop-ups, but able to drag it around the screen with no flicker.

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();
}

Making a moveable control in WPF

I have a panel, within that panel are several rectangular controls (the number of controls vaires) I want the user to be able to move the controls around within the panel so that they can arrange the controls in the way that suits them best. does anyone have any resources i could read or simple tips which would get me headed down the right road?
thanks
I figured out a possible, simple method of moving a control in a drag/move style... Here are the steps.
Select an element in your control which you wish to be the movement area. This is the area in which, if me user holds the mouse down, the control will move. In my case it was a rectangular border at the top of the control.
Use the OnMouseDown event to set a boolean (in my case IsMoving) to true and the MouseUp event to set it to false
On the first MouseDown event, set some Point property (InitialPosition) using the following code
if (FirstClick)
{
GeneralTransform transform = this.TransformToAncestor(this.Parent as Visual);
Point StartPoint = transform.Transform(new Point(0, 0));
StartX = StartPoint.X;
StartY = StartPoint.Y;
FirstClick = false;
}
Now that you have the starting position, you need to get the position of the mouse relative to your movement control. This is so you dont end up clicking the middle of your header to move it and it instantly moves the top left of the control to the mouse pointer location. To do this, place this code in the MouseDown event:
Point RelativeMousePoint = Mouse.GetPosition(Header);
RelativeX = RelativeMousePoint.X;
RelativeY = RelativeMousePoint.Y;
Now you have the point the control originated at (startX and STartY), the position of the mouse within your movement control (RelativeX, RelativeY), we just need to move the control to a new location! There are a few steps involved in doing this. Firstly your control needs to have a RenderTransform which is a TranslateTransform. If you dont want to set this in XAML, feel free to set it using this.RenderTransform = new TranslateTransform.
Now we need to set the X and Y coordinates on the RenderTransform so that the control will move to a new location. The following code accomplishes this
private void Header_MouseMove(object sender, MouseEventArgs e)
{
if (IsMoving)
{
//Get the position of the mouse relative to the controls parent
Point MousePoint = Mouse.GetPosition(this.Parent as IInputElement );
//set the distance from the original position
this.DistanceFromStartX= MousePoint.X - StartX - RelativeX ;
this.DistanceFromStartY= MousePoint.Y - StartY - RelativeY;
//Set the X and Y coordinates of the RenderTransform to be the Distance from original position. This will move the control
TranslateTransform MoveTransform = base.RenderTransform as TranslateTransform;
MoveTransform.X = this.DistanceFromStartX;
MoveTransform.Y = this.DistanceFromStartY;
}
}
As you can guess, there is a bit of code left off(variable declarations etc) but this should be all you need to get you started :) happy coding.
EDIT:
One problem you may encounter is that this allows you to move the control out of the area of its parent control. Here is some quick and dirty code to fix that issue...
if ((MousePoint.X + this.Width - RelativeX > Parent.ActualWidth) ||
MousePoint.Y + this.Height - RelativeY > Parent.ActualHeight ||
MousePoint.X - RelativeX < 0 ||
MousePoint.Y - RelativeY < 0)
{
IsMoving = false;
return;
}
Place this code in your MouseMove event before the actual movement takes place. This will check if the control is trying to move outside the bounds of the parent control. The IsMoving = false command will cause the control to exit movement mode. This means that the user will need to click the movement area again to try to move the control as it will have stopped at the boundary. If you want the control to automatically continue movement, just take that line out and the control will jump back onto the cursor as soon as it is back in a legal area.
You can find a lot of inspiration here:
http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part1.aspx
http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part2.aspx
http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part3.aspx
http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part4.aspx

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