How to make a more precise Slider Control? - c#

I'd like to make a slider control that when I press a key (lets say shift), its thumb (and its value) will drag slower than the mouse and so become more precise.
I'm still new to WPF and I don't really where to start.
Any hints?
EDIT---
Here is what I'm trying so far with horizontal slider :
Point pStart;
Point pCurrent;
private void CMiXSlider_DragDelta(object sender, DragDeltaEventArgs e)
{
if (Keyboard.IsKeyDown(Key.LeftShift))
{
pCurrent = Mouse.GetPosition(CMiXSlider);
double center = Math.Abs(pCurrent.X - pStart.X);
Value = (1.0 / CMiXSlider.ActualWidth) * center;
}
}
private void CMiXSlider_DragStarted(object sender, DragStartedEventArgs e)
{
pStart = Mouse.GetPosition(CMiXSlider);
}
it behaves almost as expected but the thumb is always at the center position between 0 and pCurrent. Looks like pStart doesn't keep its value when DragDelta is triggered.

This is primarily a UX question, and only secondarily a WPF question. I cannot help you with WPF, but I can give you some input from a UX point of view.
Don't.
Just don't do this. It is a bad idea, for a number of reasons:
a) It is highly non-standard. No application that I ever heard of does something like that. Therefore, nobody will know that your slider behaves differently when some modifier key is pressed, and nobody will ever try to drag your slider with a modifier key down.
b) You cannot teach users to do it. Cluttering your user interface with additional text labels containing instructions on how to use it is clunky and abandoned as a practice decades ago.
c) Even if you do somehow manage to teach users to do it, the mental effort of learning some new way to use an existing and familiar control is not worth the added benefit of simply having better control over the precision of the slider.
d) This could either work by moving the slider by a fraction of the distance by which the pointer moves, or by slowing down the pointer.
moving the slider by a fraction of the distance by which the pointer moves would look ugly and clunky. It would work, but it would look broken.
slowing down the pointer is a tricky proposition, and it would only work with a mouse. What about a touch interface where the user uses their finger? You can't slow that down.
Solutions:
Implement movement by arrow keys. That's the standard way of allowing fine control over the value of a slider. When the slider has focus, then the arrow keys should change the value by one unit.
If the nature of your application is such that high precision is of great importance, then consider replacing the slider with a numeric control. Make it a spinner if need be. (A numeric control with an up and down button.) Or perhaps make it a numeric control with a drop-down slider, so the primary means of operation is numeric, and operation by slider is secondary and optional. (I have no idea how nor if you can do that with WPF.)

Sometimes this functionality is useful, for example a lighting/audio control or fine positioning of robotic arms. In these UX situations users will probably find out by RTFM.
I'm not in front of a PC, here is some simple psuedo code to illustrate how to do it with a Custom Slider Control that ignores every second value change when the shift key is down.
public class CustomSlider : Slider
{
private int precision = 2;
private int movement = 0;
protected override void OnValueChanged(double oldValue, double newValue)
{
bool keyShift = (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
if (keyShift && movement % precision == 0)
{
base.OnValueChanged(oldValue, newValue);
}
else if (!keyShift)
{
base.OnValueChanged(oldValue, newValue);
}
movement++;
if (movement == int.MaxValue) movement = 0;
}
}
You could apply this solution to Numeric Up/Down, Scroll Bars and various other controls.
In addition to the code above, for even more precise interaction you can detect for Ctrl key in the OnValueChanged event and increase precision, eg
precision = Keyboard.IsKeyDown(Key.Control) ? 3 : 2;

Related

How can i calculate the right coordinates?

I need help. I have a Picturebox and would now like to calculate given coordinates on the picture and then play them in the label. How can I do that best?
As seen in the picture.
If you then click on the image on it, then the data is entered in a list box.
Thank you for your help.
My Picture here: https://prnt.sc/puxyu6
In WPF this particular might for once be the hardest. WPF/UWP is designed for the MVVM, and I do not know anyone but beginners that programm outside of MVVM. And I can not think of way to do this with MVVM.
PictureBox is also the WinForms Element. The WPF equivalent is called Image.
Navigation aids like this are not a trivial thing. One reason there are so few of it. But it comes down to few step process:
Get the x and y pixel coordinates that was clicked, also in relation to that the overall display size of the Image. Usually the MouseClick Event would be the tool for that, but I can not find it. MouseDown or LeftMouseDown are the closest events.
If the entire image was shown with no zooming or cropping, it is now just simple math. If it was 20% of the X axis and 21% of the Y axis, it is pretty easy to figure out where 20% of X and 21 of Y is on the SourceImage.
If there was any zoom or crop, this has to be taken into account, otherwise it is 2.
Equate the Image Pixel position with the coordinates you know for it.
Part 1 would look like this and needs to be registered with the MouseDown or LeftMouseDown Event of the Image:
private void ContentControl_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
//Unpack the sender
Image source = (Image)Sender;
//Get the click point
Point clickPoint = e.GetPosition(source);
//There is more then 1 height Property in WPF.
//The Actuall one is what you are looking for
//Unfortunately you get doubles for this and int for the other. Consider general advise regarding double math
double ElementHeight = source.ActualHeight;
double ElementWidth = source.ActualWidth;
//Do what you need to find the relative position
//Part 2
}
Hopefully someone else can give you a better answer.

The reason behind slow performance in WPF

I'm creating a large number of texts in WPF using DrawText and then adding them to a single Canvas.
I need to redraw the screen in each MouseWheel event and I realized that the performance is a bit slow, so I measured the time the objects are created and it was less than 1 milliseconds!
So what could be the problem? A long time ago I guess I read somewhere that it actually is the Rendering that takes the time, not creating and adding the visuals.
Here is the code I'm using to create the text objects, I've only included the essential parts:
public class ColumnIdsInPlan : UIElement
{
private readonly VisualCollection _visuals;
public ColumnIdsInPlan(BaseWorkspace space)
{
_visuals = new VisualCollection(this);
foreach (var column in Building.ModelColumnsInTheElevation)
{
var drawingVisual = new DrawingVisual();
using (var dc = drawingVisual.RenderOpen())
{
var text = "C" + Convert.ToString(column.GroupId);
var ft = new FormattedText(text, cultureinfo, flowdirection,
typeface, columntextsize, columntextcolor,
null, TextFormattingMode.Display)
{
TextAlignment = TextAlignment.Left
};
// Apply Transforms
var st = new ScaleTransform(1 / scale, 1 / scale, x, space.FlipYAxis(y));
dc.PushTransform(st);
// Draw Text
dc.DrawText(ft, space.FlipYAxis(x, y));
}
_visuals.Add(drawingVisual);
}
}
protected override Visual GetVisualChild(int index)
{
return _visuals[index];
}
protected override int VisualChildrenCount
{
get
{
return _visuals.Count;
}
}
}
And this code is run each time the MouseWheel event is fired:
var columnsGroupIds = new ColumnIdsInPlan(this);
MyCanvas.Children.Clear();
FixedLayer.Children.Add(columnsGroupIds);
What could be the culprit?
I'm also having trouble while panning:
private void Workspace_MouseMove(object sender, MouseEventArgs e)
{
MousePos.Current = e.GetPosition(Window);
if (!Window.IsMouseCaptured) return;
var tt = GetTranslateTransform(Window);
var v = Start - e.GetPosition(this);
tt.X = Origin.X - v.X;
tt.Y = Origin.Y - v.Y;
}
I'm currently dealing with what is likely the same issue and I've discovered something quite unexpected. I'm rendering to a WriteableBitmap and allowing the user to scroll (zoom) and pan to change what is rendered. The movement seemed choppy for both the zooming and panning, so I naturally figured the rendering was taking too long. After some instrumentation, I verified that I'm rendering at 30-60 fps. There is no increase in render time regardless of how the user is zooming or panning, so the choppiness must be coming from somewhere else.
I looked instead at the OnMouseMove event handler. While the WriteableBitmap updates 30-60 times per second, the MouseMove event is only fired 1-2 times per second. If I decrease the size of the WriteableBitmap, the MouseMove event fires more often and the pan operation appears smoother. So the choppiness is actually a result of the MouseMove event being choppy, not the rendering (e.g. the WriteableBitmap is rendering 7-10 frames that look the same, a MouseMove event fires, then the WriteableBitmap renders 7-10 frames of the newly panned image, etc).
I tried keeping track of the pan operation by polling the mouse position every time the WriteableBitmap updates using Mouse.GetPosition(this). That had the same result, however, because the returned mouse position would be the same for 7-10 frames before changing to a new value.
I then tried polling the mouse position using the PInvoke service GetCursorPos like in this SO answer eg:
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(out POINT lpPoint);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
and this actually did the trick. GetCursorPos returns a new position each time it is called (when the mouse is moving), so each frame is rendered at a slightly different position while the user is panning. The same sort of choppiness seems to be affecting the MouseWheel event, and I have no idea how to work around that one.
So, while all of the above advice about efficiently maintaining your visual tree is good practice, I suspect that your performance issues may be a result of something interfering with the mouse event frequency. In my case, it appears that for some reason the rendering is causing the Mouse events to update and fire much slower than usual. I'll update this if I find a true solution rather than this partial work-around.
Edit: Ok, I dug into this a little more and I think I now understand what is going on. I'll explain with more detailed code samples:
I am rendering to my bitmap on a per-frame basis by registering to handle the CompositionTarget.Rendering event as described in this MSDN article. Basically, it means that every time the UI is rendered my code will be called so I can update my bitmap. This is essentially equivalent to the rendering that you are doing, it's just that your rendering code gets called behind the scenes depending on how you've set up your visual elements and my rendering code is where I can see it. I override the OnMouseMove event to update some variable depending on the position of the mouse.
public class MainWindow : Window
{
private System.Windows.Point _mousePos;
public Window()
{
InitializeComponent();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
// Update my WriteableBitmap here using the _mousePos variable
}
protected override void OnMouseMove(MouseEventArgs e)
{
_mousePos = e.GetPosition(this);
base.OnMouseMove(e);
}
}
The problem is that, as the rendering takes more time, the MouseMove event (and all mouse events, really) gets called much less frequently. When the rendering code takes 15ms, the MouseMove event gets called every few ms. When the rendering code takes 30ms, the MouseMove event gets called every few hundred milliseconds. My theory on why this happens is that the rendering is happening on the same thread where the WPF mouse system updates its values and fires mouse events. The WPF loop on this thread must have some conditional logic where if the rendering takes too long during one frame it skips doing the mouse updates. The problem arises when my rendering code takes "too long" on every single frame. Then, instead of the interface appearing to slow down a little bit because the rendering is taking 15 extra ms per frame, the interface stutters greatly because that extra 15ms of render time introduces hundreds of milliseconds of lag between mouse updates.
The PInvoke workaround I mentioned before essentially bypasses the WPF mouse input system. Every time the rendering happens it goes straight to the source, so starving the WPF mouse input system no longer prevents my bitmap from updating correctly.
public class MainWindow : Window
{
private System.Windows.Point _mousePos;
public Window()
{
InitializeComponent();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
POINT screenSpacePoint;
GetCursorPos(out screenSpacePoint);
// note that screenSpacePoint is in screen-space pixel coordinates,
// not the same WPF Units you get from the MouseMove event.
// You may want to convert to WPF units when using GetCursorPos.
_mousePos = new System.Windows.Point(screenSpacePoint.X,
screenSpacePoint.Y);
// Update my WriteableBitmap here using the _mousePos variable
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(out POINT lpPoint);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
}
This approach didn't fix the rest of my mouse events (MouseDown, MouseWheel, etc), however, and I wasn't keen on taking this PInvoke approach for all of my mouse input, so I decided I better just stop starving the WPF mouse input system. What I ended up doing was only updating the WriteableBitmap when it really needed to be updated. It only needs to be updated when some mouse input has affected it. So the result is that I receive mouse input one frame, update the bitmap on the next frame but do not receive more mouse input on the same frame because the update takes a few milliseconds too long, and then the next frame I'll receive more mouse input because the bitmap didn't need to be updated again. This produces a much more linear (and reasonable) performance degradation as my rendering time increases because the variable length frame times just sort of average out.
public class MainWindow : Window
{
private System.Windows.Point _mousePos;
private bool _bitmapNeedsUpdate;
public Window()
{
InitializeComponent();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
if (!_bitmapNeedsUpdate) return;
_bitmapNeedsUpdate = false;
// Update my WriteableBitmap here using the _mousePos variable
}
protected override void OnMouseMove(MouseEventArgs e)
{
_mousePos = e.GetPosition(this);
_bitmapNeedsUpdate = true;
base.OnMouseMove(e);
}
}
Translating this same knowledge to your own particular situation: for your complex geometries that lead to performance issues I would try some type of caching. For example, if the geometries themselves never change or if they don't change often, try rendering them to a RenderTargetBitmap and then add the RenderTargetBitmap to your visual tree instead of adding the geometries themselves. That way, when WPF is performing it's rendering path, all it needs to do is blit those bitmaps rather than reconstruct the pixel data from the raw geometric data.
#Vahid: the WPF system is using [retained graphics]. What you eventually should do, is devise a system where you only send "what has changed compared to previous frame" - nothing more, nothing less, you should not be creating new objects at all. It's not about "creating objects takes zero seconds", it's about how it affects rendering and the time. It's about letting the WPF do it's job using caching.
Sending new objects to the GPU for rendering=slow. Sending only updates to the GPU which tells what objects moved=fast.
Also, it's possible to create Visuals in an arbitrary thread to improve the performance (Multithreaded UI: HostVisual - Dwayne Need). That all said, if your project is pretty complex in 3D wise - there's good chance that WPF won't just cut it. Using DirectX.. directly, is much, much, more performant!
Some of the articles I suggest you to read & understand:
[Writing More Efficient ItemsControls -
Charles Petzold] - understand the process how one achieves better drawing rate in WPF.
As for why your UI is lagging, Dan answer seems to be spot on. If you are trying to render more than WPF can handle, the input system will suffer.
The likely culprit is the fact that you are clearing out and rebuilding your visual tree on each wheel event. According to your own post, that tree includes a "large number" of text elements. For each event that comes in, each of those text elements must be recreated, reformatted, measured, and eventually rendered. That is not the way to accomplish simple text scaling.
Rather than setting a ScaleTransform on each FormattedText element, set one on the element containing the text. Depending on your needs, you can set a RenderTransform or LayoutTransform. Then, when you receive wheel events, adjust the Scale property accordingly. Don't rebuild the text on each event.
I would also do what other have recommended and bind an ItemsControl to the list of columns and generate the text that way. There is no reason you should need to do this by hand.

Handling Swipe Guesture in Windows 8 Grid

I am trying to implement a custom control which consists of a grid with some canvas elements as children , When a swipe action is made on the grid , I am intended to preform some operation with the canvas elements .
I am unable to handle the swipe for the grid , i have posted the same in the
msdn - win8 Dev forum
I was in the same boat as you guys, since there was no samples out there on how this was done, but after perusing and scrutinizing the MSDN documentation on how a swipe gesture is implemented on a Windows 8 Store app using C#, this is what i came up with (and it works for my app which requires swiping up / down / left / right):
First of all, instead of the GestureRecognizer, the Manipulation events need to be used, so on the grid that you want to handle the swiping (lets' say you make it so that it takes the whole screen so it interprets the gestures) do the following:
I called my grid swipingSurface and i'm handling manipulation modes for both the Y-axis and X-axis:
swipingSurface.ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY;
Then wire up the manipulation events that you want to be notified, in my case i just want to know then the manipulation started and when it ended:
swipingSurface.ManipulationStarted += OnManipulationStarted;
swipingSurface.ManipulationCompleted += OnManipulationCompleted;
Do whatever you want on your manipulation started, such as getting the initial point if you want. But the actual trick is on the ManipulationCompleted event, in which you need to get the Velocities resulting from your gesture, as follows:
public void OnManipulationCompleted(object sender, ManipulationCompletedEventArgs e) {
var velocities = e.Velocities;
}
The ManipulationCompletedEventArgs Velocities property will bring back a struct of type ManipulationVelocities, which contains other properties inside:
-Angular: The rotational velocity in degrees per millisecond.
-Expansion: The expansion, or scaling, velocity in DIPs per millisecond.
-Linear: The straight line velocity in DIPs per millisecond.
I'm actually looking at the Linear velocity, which is a Point that contains X and Y values indicating the direction in which the gesture was performed; for example, if the swipe was upward, you will notice that the Y value is positive, and if its down the Y value is negative; the same goes for the X value, if the swipe is left, the X values are negative and if the swipe is right, the X values are positive, so you can play around with those values and check your swiping direction, final points, etc.
Hope this helps.
You could try setting ManipulationMode on your swipe-able control and handling the Manipulation~ events. Note that some controls might stop bubbling of UI events, so if you say put your control inside of a Button or a ScrollViewer - the events might not work.
You could check out SwipeHintThemeAnimation that uses GestureRecognizer to hook up to a Rectangle control or modify it to use your Grid control, see the documentation.

How to create a slider replicate, wp7, c#, silverlight

I don't necessarily want to use the slider control but rather make the whole screen an interactive slider. For example, I'm practicing on a nightstand app and I'd like it so they can slide their finger down to lower the brightness or up to increase the brightness. I know this has been done in other apps but I'm not sure where to start. If someone could give me a starting point on what technique would work or any ideas in accomplishing this it would be great!
Thank you
You can use GestureListener from Silverlight Toolkit. Take a look at sample application provided with SL Toolkit. There is a sample page GestureSample.xaml. You will find there how to detect gestures.
One way could be to use Manipulation events. So, for example, you get the starting position in the ManipulationStarted event's ManipulationOrigin property . You can then get the final position in the ManipulationCompleted event's Manipulation property. Substract the two Y components and check if it's greater than or less than zero. If it's greater than zero, the user has moved downwards otherwise they've moved upwards.
private void LayoutRoot_ManipulationStarted(object sender, System.Windows.Input.ManipulationStartedEventArgs e)
{
startY = e.ManipulationOrigin.Y;
}
private void LayoutRoot_ManipulationCompleted(object sender, System.Windows.Input.ManipulationCompletedEventArgs e)
{
endY = e.ManipulationOrigin.Y;
if(endY - startY > 0)
MessageBox.Text("Down")
else
MessageBox.Text("Up");
//add check to see if it equals zero in which case the user didn't swipe
}
Alternatively, you could perform similar actions with the Silverlight Toolkit or the XNA Gestures. (They both differ slightly).

How to implement smooth scrolling in .NET

I want to implement a smooth/animated scrolling for a custom control in C#. I want something similar to the following javascript example:
http://www.kryogenix.org/code/browser/smoothscroll/#p0
My first idea is moving the scrollbars to the target point but stopping in intermediate points. For example, this is a very simplified idea:
public void SetSrollValue(int x)
{
// assume right scrolling
float step = x / 10;
while (scroll.Value < x)
{
scroll.Value += step;
}
}
My questions are:
Should I implement this in a thread?
Will this be painted smoothly (I suppose that yes if I have double buffer activated in my control)
So, if you know any good example, article, guide or similar, please could you provider a link here?
Thanks in advance.
To make the content of the control scroll, you pass the value of the AutoScrollPosition to e.Graphics.TranslateTransform(). That's your angle, alter the value you pass.
Write a little helper class that observes the value of the control's AutoScrollPosition with a method that you call in your OnPaint method, passing e.Graphics so you can call its TranslateTransform method. When you see it change, record Environment.TickCount, set an internal 'scrollBusy' flag and start a 15 msec timer. On each timer tick, call the control's Invalidate() method so that you'll calculate a new value for TranslateTransform when your method is called again. Calculate the increment from the original to the target scroll position so it takes, say, 250 msec.

Categories