The Slider control in WPF doesn't work properly for what I'm looking for.
I need to slide 2 different controls (Slider) at the same time (with one finger each).
When I touch the first Slider, it gets all the focus and I cannot touch anything else with my second touch device.
So I need to create my own Slider (MySlider) that inherit from Slider.
I've made 4 methods:
protected override void OnTouchDown(TouchEventArgs e)
protected override void OnTouchUp(TouchEventArgs e)
protected override void OnTouchLeave(TouchEventArgs e)
protected override void OnTouchMove(TouchEventArgs e)
But is there a way to move the Slider exactly like with the mouse? Or I need to calcule each time my touch device moved something like:
protected override void OnTouchMove(TouchEventArgs e)
{
base.OnTouchMove(e);
if (this.Value <= this.Maximum && this.Value >= this.Minimum)
{
Point newPoint = e.GetTouchPoint(this).Position;
this.Value += (this.lastPoint.Y - newPoint.Y);
lastPoint = newPoint;
}
e.Handled = true;
}
And in this case the movement doesn't move at the same speed as the finger...
You might want to check out the Surface 2.0 SDK as it contains a class called SurfaceSlider, which I believe will allow for two or more sliders to be updated simultaneously. This SDK can be used to target applications built for Windows 7.
I'm not familiar with multi-touch events in WPF so will not be able to help you with that. However, for moving the mouse to the same location as your touching then you can look at this answer here.
Your problem that you're assuming that the width of the control is equivalent to the maximum value. You need to take out the factor the actual width relative to the difference between the max and min values.
This can only be done via events since no routed event or DPs for mouse position.
Related
I've written a Windows Forms app where I do custom drawing on a Panel using Control.CreateGraphics(). Here's what my Form looks like at startup:
The custom drawing is performed on the top panel in the Click event handler of the "Draw!" button. Here's my button click handler:
private void drawButton_Click(object sender, EventArgs e)
{
using (Graphics g = drawPanel.CreateGraphics())
{
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.Clear(Color.White);
Size size = drawPanel.ClientSize;
Rectangle bounds = drawPanel.ClientRectangle;
bounds.Inflate(-10, -10);
g.FillEllipse(Brushes.LightGreen, bounds);
g.DrawEllipse(Pens.Black, bounds);
}
}
After a click on drawButton, the form looks like this:
Success!
But when I shrink the form by dragging a corner...
...and expand it back to its original size,
part of what I drew is gone!
This also happens when I drag part of the window offscreen...
...and drag it back onscreen:
If I minimize the window and restore it, the whole image is erased:
What is causing this? How can I make it so the graphics I draw are persistent?
Note: I've created this self-answered question so I have a canonical Q/A to direct users to, as this is a common scenario that's hard to search for if you don't already know the cause of the problem.
TL;DR:
Don't do your drawing in response to a one-time UI event with Control.CreateGraphics. Instead, register a Paint event handler for the control on which you want to paint, and do your drawing with the Graphics object passed via the PaintEventArgs.
If you want to paint only after a button click (for example), in your Click handler, set a boolean flag indicating that the button has been clicked and then call Control.Invalidate(). Then do your rendering conditionally in the Paint handler.
Finally, if your control's contents should change with the size of the control, register a Resize event handler and call Invalidate() there too.
Example code:
private bool _doCustomDrawing = false;
private void drawPanel_Paint(object sender, PaintEventArgs e)
{
if (_doCustomDrawing)
{
Graphics g = e.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.Clear(Color.White);
Size size = drawPanel.ClientSize;
Rectangle bounds = drawPanel.ClientRectangle;
bounds.Inflate(-10, -10);
g.FillEllipse(Brushes.LightGreen, bounds);
g.DrawEllipse(Pens.Black, bounds);
}
}
private void drawButton_Click(object sender, EventArgs e)
{
_doCustomDrawing = true;
drawPanel.Invalidate();
}
private void drawPanel_Resize(object sender, EventArgs e)
{
drawPanel.Invalidate();
}
But why? What was I doing wrong, and how does this fix it?
Take a look at the documentation for Control.CreateGraphics:
The Graphics object that you retrieve through the CreateGraphics method should not normally be retained after the current Windows message has been processed, because anything painted with that object will be erased with the next WM_PAINT message.
Windows doesn't take responsibility for retaining the graphics you draw to your Control. Rather, it identifies situations in which your control will require a repaint and informs it with a WM_PAINT message. Then it's up to your control to repaint itself. This happens in the OnPaint method, which you can override if you subclass Control or one of its subclasses. If you're not subclassing, you can still do custom drawing by handling the public Paint event, which a control will fire near the end of its OnPaint method. This is where you want to hook in, to make sure your graphics get redrawn every time the Control is told to repaint. Otherwise, part or all of your control will be painted over to the control's default appearance.
Repainting happens when all or part of a control is invalidated. You can invalidate the entire control, requesting a full repaint, by calling Control.Invalidate(). Other situations may require only a partial repaint. If Windows determines that only part of a Control needs to be repainted, the PaintEventArgs you receive will have a non-empty ClipRegion. In this situation, your drawing will only affect the area in the ClipRegion, even if you try to draw to areas outside that region. This is why the call to drawPanel.Invalidate() was required in the above example. Because the appearance of drawPanel needs to change with the size of the control and only the new parts of the control are invalidated when the window is expanded, it's necessary to request a full repaint with each resize.
Is there a way to distinguish whether a button was clicked as in with a mouse or touched using a touchscreen in WPF?
You can subscribe to PreviewMouseDown and PreviewTouchDown.
Page.xaml
<Button PreviewMouseDown="Button_PreviewMouseDown"
PreviewTouchDown="Button_PreviewTouchDown" />
Page.xaml.cs
private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Mouse was used.");
}
private void Button_PreviewTouchDown(object sender, TouchEventArgs e)
{
MessageBox.Show("Touchscreen was used.");
}
I don't believe you'll be able to access the eventargs of either in the actual click event.
If you need to perform work there as opposed to the preview events I would recommend setting an instance variable in the preview events so when you get to the click event you know where you came from.
You have to set up an event handler. In the designer, double click on the button and that will set on up for you.
Then in the code behind add what ever code you want.
private void Button_Click(object sender, RoutedEventArgs e)
{
this.Title = "Clicked";
}
You can add Touch events as well TouchDown, TouchUp, etc.
Windows 7 and its higher versions have the ability to receive input from multiple touch-sensitive devices. WPF applications can also handle touch input as other input, such as the mouse or keyboard, by raising events when a touch occurs.
WPF exposes two types of events when a touch occurs − touch events and manipulation events. Touch events provide raw data about each finger on a touchscreen and its movement. Manipulation events interpret the input as certain actions. Both types of events are discussed in this section.
WPF enables applications to respond to touch. For example, you can interact with an application by using one or more fingers on a touch-sensitive device, such as a touchscreen This walkthrough creates an application that enables the user to move, resize, or rotate a single object by using touch.
Source MSDN : https://msdn.microsoft.com/en-us/library/ee649090.aspx
Also read this codeproject article - http://www.codeproject.com/Articles/692286/WPF-and-multi-touch
I have an OpenTK GLControl embedden in a WindowsFormsHost in my WPF application.
I want to continuously update and render it.
In Winforms a solution would be to attach the UpdateAndRender method to the Application.Idle event, but there is no such thing in WPF.
So what would be the best way to do (60FPS) updating of my scene and GLControl ?
You can use a System.Timers.Timer to control how often your render code is called. In your window containing the GLControl-in-WindowsFormsHost, declare a private System.Timers.Timer _timer;, then when you're ready to start the rendering loop, set the timer interval and it's event handler, then start it up, as in the following example:
private void btnLoadModel_Click(object sender, RoutedEventArgs e)
{
LoadModel(); // do whatever you need to do to prepare your scene for rendering
_timer = new System.Timers.Timer(10.0); // in milliseconds - you might have to play with this value to throttle your framerate depending on how involved your update and render code is
_timer.Elapsed += TimerElapsed;
_timer.Start();
}
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
UpdateModel(); // this is where you'd do whatever you need to do to update your model per frame
// Invalidate will cause the Paint event on your GLControl to fire
_glControl.Invalidate(); // _glControl is obviously a private reference to the GLControl
}
You'll clearly need to add using System.Timers to your usings.
You can use Invalidate() for it. This causes the GLControl to redraw it's content.
If you call it at the end of Paint() you may blocking some UI rendering of the other WPF controls.
WPF provides a per frame render event: CompositionTarget.Rendering. This event is called before WPF wants to render the content. Subscribe from it and call Invalidate:
public YourConstructor()
{
//...
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
_yourControl.Invalidate();
}
You need to unsubscribe if you don't use it anymore (to avoid memory leaks).
Here is a How to: Render on a Per Frame Interval Using CompositionTarget from MSDN.
I use GLControl with that method and it works fine. I did not checked how much FPS I have but it feels smooth.
You may also have a look on this: Why is Frame Rate in WPF Irregular and Not Limited To Monitor Refresh?
I'm working on a WinForms app and need to record the location of MouseDown and MouseUp events. My problem is that the events happen on different controls so their coordinate systems don't match (all I need is the amount of drag). I tried adding in the location of the sending control but it still doesn't work right.
Is there a simple solution to this?
You may use PointToScreen method for the purpose. Your mouse handler code could then look like this:
private void Form1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
Control control = (Control) sender;
Point pointOnScreen = control.PointToScreen(new Point(e.X, e.Y));
...
}
I am new to silverlight - I've seen the tutorials and all, what I am looking for are high-level steps required to implement drag&drop and maybe a bit of (pseudo)code just to get an idea.
Any help appreciated!
Could you explain exactly what you would like to achieve with drag and drop in Silverlight? The top answer in the question that you linked to links to this page:
http://www.adefwebserver.com/DotNetNukeHELP/Misc/Silverlight/DragAndDropTest/
This contains a sample project with source that implements drag and drop (coincidentally, based on a sample I made for beta 1 of Silverlight 2 :-) ). What about this code is not suitable for your needs?
EDIT:
The basic skeleton of a drag and drop implementation would look like:
bool isDragging = false;
void elementMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (element.CaptureMouse())
{
isDragging = true;
element.MouseMove += elementMouseMove;
//start drag code goes here
}
}
void elementMouseMove(object sender, MouseEventArgs e)
{
//Drag code goes here
}
void element_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (isDragging)
{
element.ReleaseMouseCapture();
isDragging = false;
element.MouseMove -= elementMouseMove;
//Drop code goes here
}
}
Add the MouseLeftButtonUp/Down handlers to the element that you want to drag.
In the MouseMove event handler, add the code that you want to execute during the drag: e.g. change Canvas.Top and Canvas.Left to match the mouse position. You can get the mouse position from the event args. You would probably want to get the position relative to the elements container.
In the MouseLeftButtonUp event handler, add the code that would execute when the "drop" occurs. For example, you might want to implement a "recycle bin" that elements can be dragged to. In that case, you would want to know what elements are underneath the mouse at the point of the drop. You can use VisualTreeHelper.FindElementsAtHostCoordinates, passing the mouse position relative to the application's root (use e.GetPosition(null)). Then if your "recycle bin" element is returned by FindElementsInHostCoordinates you know to execute the appropriate action.
Does this help answer you question?
isn't MouseLeftButtonDown where you are supposed to be starting the capture, not on mouseleftbuttonup?