I'm writing a graphing program, and I want to be able to hover my mouse over a point on the graph to reveal details about the point. I'm aware of MouseHover(), but that only works when hovering over the form, not specific points on the form. I've written a function that works, but is incredibly resource intensive and not very reliable using MouseHover:
List<List<Point>> Points { get; set; } = new();
private async void GraphPanel_MouseHover(object sender, EventArgs e)
{
await Task.Run(() => {
do
{
foreach(var pointList in Points)
{
foreach (Point point in pointList)
{
if (Control.MousePosition == point)
{
throw new Exception();
}
}
}
}
while (true);
});
}
Is there a more practical way of doing this?
MouseHover is called a lot of times. You can:
Save mouse position into a variable in MouseHover. Save the currentTime (DateTime.UtcNow) in other variable.
Use Task.Delay to wait, for example, 100 ms. In the code executed after this delay, check if current mouse position is the same (o closest if you want work with some tolerance). Then, the mouse was 100 ms in the same position: do your work. If not, do nothing.
Avoid create a new Task if (DateTime.UtcNow-YourPreviouslySavedDate).TotalMilliSeconds is less than 100 ms
Another option is using Timer component. Set the interval and attach a method for the event (double click in the component). On the event, check mouse position and do your work.
Related
I'm creating a WinUI 3 User Control that -for sake of simplicity- can be described as glorified slider:
Whenever user presses their left click (PointerPressed event), Pointer movement starts being tracked.
Whenever uses releases their left click (PointerReleased event), this tracking stops.
Sample XAML
<UserControl [...] >
<Border Name="MainArea"
PointerPressed="StartCapturing"
PointerReleased="StopCapturing"
PointerMoved="CaptureCoords"
[...] >
<!-- [...] -->
</Border>
</UserControl>
Sample C#
bool _isCapturing = false;
private void CalculateCoords(PointerRoutedEventArgs args){
// max distance is [w,h]. min distance is [0,0]
var h = MainArea.ActualHeight;
var w = MainArea.ActualWidth;
// Coordinates relative to the MainArea element;
var pos = args.GetCurrentPoint(MainArea).Position;
var (x, y) = (pos.X, pos.Y);
// clip 0% - 100%
x = x < 0 ? 0 : (x > w ? w : x);
y = y < 0 ? 0 : (y > h ? h : y);
// convert to percentages
var (x_perc, y_perc) = (x/w, y/h);
// Do stuff with the two percentages
}
public void StartCapturing(object? sender, PointerRoutedEventArgs args){
_isCapturing = true;
CalculateCoords(args);
}
public void CaptureCoords(object? sender, PointerRoutedEventArgs args){
if (!_isCapturing) return;
CalculateCoords(args);
}
public void StopCapturing(object? sender, PointerRoutedEventArgs args){
_isCapturing = false;
CalculateCoords(args);
}
This methodology works fine while the cursor is inside the control. The problem comes when the cursor leaves the element: Movement tracking stops and, if click is released outside of the bounding box, the capturing will continue once the mouse re-enters the bounding box, even without click pressed.
These problems where to be expected. To eliminate them, the only possible solutions I have thought of but don't know how to implement them, are:
Force-Capture Mouse events, even when pointer exits the control
Restrain mouse movement within control's bounding box
Is there a way to do any of these two things?
You can use CapturePointer in StartCapturing.
You typically capture the pointer because you want the current pointer
action to initiate a behavior in your app. In this case you typically
don't want other elements to handle any other events that come from
that pointer's actions, until your behavior is either completed or is
canceled by releasing the pointer capture. If a pointer is captured,
only the element that has capture gets the pointer's input events, and
other elements don't fire events even if the pointer moves into their
bounds. For example, consider a UI that has two adjacent elements.
Normally, if you moved the pointer from one element to the other,
you'd first get PointerMoved events from the first element, and then
from the second element. But if the first element has captured the
pointer, then the first element continues to receive PointerMoved
events even if the captured pointer leaves its bounds. Also, the
second element doesn't fire PointerEntered events for a captured
pointer when the captured pointer enters it.
public void StartCapturing(object sender, PointerRoutedEventArgs args)
{
_isCapturing = true;
CalculateCoords(args);
MainArea.CapturePointer(args.Pointer);
}
I'm using OxyPlot to show a data chart and to allow the user to select an interval of data he wants to do calculation on.
It looks like this:
Now, I would like the user to be able to set the data interval used for calculation himself by resizing the chart. For example, if he resized the chart on this particular interval it would only take the points located between the furthest left and the furthest right on screen.
I already found the event that triggers whenever the chart is resized :
plotModel.Updated += (s, e) =>
{
//reset interval used for calculation
};
What I couldn't find in OxyPlot documentation is a way to retrieve a certain set of points currently shown. It doesn't need to be points in particular, I could also use only the x components of each extremum.
You could use Series.GetScreenRectangle().Contains() method after transforming your points using the Transform() method to detect if the point is currently in display.
For example,
model.Updated += (s,e)=>
{
if(s is PlotModel plotModel)
{
var series = plotModel.Series.OfType<OxyPlot.Series.LineSeries>().Single();
var pointCurrentlyInDisplay = new List<DataPoint>();
foreach (var point in series.ItemsSource.OfType<DataPoint>())
{
if (series.GetScreenRectangle().Contains(series.Transform(point)))
{
pointCurrentlyInDisplay.Add(point);
}
}
}
};
You are iterating over the point collection and verifying if the Transformed point (Transform method transform DataPoint to screen point) falls within the Screen Rectangle used by the series.
Update
If you have used Series.Points.AddRange()/Add() for adding points instead of Series.ItemSource, use the following for retrieving points.
foreach (var point in series.Points.OfType<DataPoint>())
{
if (series.GetScreenRectangle().Contains(series.Transform(point)))
{
pointCurrentlyInDisplay.Add(point);
}
}
I'm a beginner in Kinect Programming. Yet I read different tutorials and texts about it. Furthermore I was working on the code examples ("Controls Basis-WPF").
Unfortunately none of the sources teach me how to code grabbing and releasing in a way that I understand.
I would like to create dynamically KinectTileButtons. While the program is running the user should be able to place the buttons to the positions he prefers. The player uses his hand as the cursor.
For example:
There is a button b1 on the top left corner.
While the hand of the user is not on b1, the button doesn't change his position.
If the user's hand is on the button AND he makes the "GRAB"-gesture, the position of the button is like the position of his hand (variable position). When he RELEASES, the position of button is constant and will not change until the user is grabbing for the button the next time.
I'm very thankful for any advices, suggestions or code examples, because I really don't know how I should continue to work on this problem.
Thanks in advance
derpate
What you should be going for is to change the location of the buttons on the window. I would have some global bools to signify is they are grabbing and holding. Then I would put a conditional in the update to move the label to the position of the hand. Here's some pseudo-code:
bool grabbing = false;
bool selected = false;
var button;
var handElement;
Start()
{
button = window.Button;
handElement = window.handElement;
...
sensor.Start();
}
Update() //all frames ready
{
using (SkeletonFrame sf = e.OpenSkeletonFrame())
{
... //get skeleton and position
... //tell if grabbing
if (/*however you do the grab gesture*/)
grabbing = true;
if (selected && grabbing)
{
button.X = handElement.X;
button.Y = handElement.Y;
}
}
}
Button_OnClick()//or whatever it is called for the Kinect UI control
{
selected = true;
}
Sorry for the pseudo-code, I just haven't worked with the Kinect UI before, and I didn't want to have half pseudo-code with the elements I don't know, however I know that it has events like OnClick() for when it is held, and I don't know what you mean by "GRAB" gesture but I assume you know how to implement it if you know what it is. Good luck!
Know also this only works for one button. If you were doing this for many, I would suggest a class... something like:
class MoveableButton : KinectButton//again, don't know the exact name of this
{
private int X, Y;
private bool selected = false, grabbing = false;
public void Selected()
{
selected = true;
}
public void Grabbed()
{
grabbing = true;
}
public void LetGo()
{
grabbing = false;
selected = false;
}
public void Update(UIElement hand)
{
if (selected && grabbing)
{
this.X = hand.X;
this.Y = hand.Y;
}
}
}
This you would then call the appropriate methods during certain frames, I would have an array of them to loop through for Update.
I am really not sure if it is an issue with my inexperience with C#, VS 2010, debug, .net, or events in general, so please be bear with me. I have a project drawing an Alphabet Aquarium. Adding letters in different colors to a panel and then animate them. It is a simple windows form project that includes two classes, Fishtank and Fish. A paint event is uses to draw the colored letters and it is our task to animate them. In order to understand how the paint event is using the classes with initial load and controls, I set a breakpoint. With the breakpoint, I cannot step through or over the paint event. Without the breakpoint, the program loads?? Is it an issue with my ineperience, the code, the debugging or what??
private void fishTankPanel_Paint(object sender, PaintEventArgs e)
{
// Loop through each fish in our fish tank, and draw them.
for (int i = 0; i < _fishTank.CountFish(); i++)
{
Fish fish = _fishTank.GetFish(i);
e.Graphics.DrawString(fish.FishLetter, new Font("Arial", 10),
new SolidBrush(fish.FishColor), new Point(fish.XPosition, fish.YPosition));
}
fishCountLabel.Text = _fishTank.CountFish().ToString();
}
class Fish
{
private Color _fishColor;
public Color FishColor
{
get { return _fishColor; }
set { _fishColor = value; }
}
private int _xPosition;
public int XPosition
{
get { return _xPosition; }
set { _xPosition = value; }
}
private int _yPosition;
public int YPosition
{
get { return _yPosition; }
set { _yPosition = value; }
}
private string _fishLetter;
public string FishLetter
{
get { return _fishLetter; }
set { _fishLetter = value; }
}
private string _direction;
public string Direction
{
get { return _direction; }
set { _direction = value; }
}
public Fish(string fishLetter, int xPosition, int yPosition, Color fishColor, string fishDirection)
{
// If no letter specified, use "X."
if (fishLetter.Length == 0)
fishLetter = "X";
_fishLetter = fishLetter;
// Ensure the position is >= 0.
if (xPosition < 0)
xPosition = 0;
_xPosition = xPosition;
if (yPosition < 0)
yPosition = 0;
_yPosition = yPosition;
// Set the fish color.
_fishColor = fishColor;
// Set fish direction
}
}class FishTank
{
// Use a List collection to hold the fish.
private List<Fish> _fishTank = new List<Fish>();
public int CountFish()
{
return _fishTank.Count;
}
public Fish GetFish(int position)
{
return _fishTank[position];
}
public void AddFish(Fish fish)
{
_fishTank.Add(fish);
}
public void ClearFish()
{
_fishTank.Clear();
}
}
Feedback would be appreciated and thanks in advance.
While single-step debugging a paint method, being 'caught' in a seemingly endless loop of paint events is a common problem if your software's window (that which is being debugged) is at least partially overlapping with one of Visual Studio's windows - an overlap of even one pixel might be enough to trigger this symptom.
What happens is that whenever you do a step in the debugger, the focus switches from the debugger window to your program. Since the program window, which was at least partially covered by one of the Visual Studio windows, is now brought to the foreground again, it will trigger a Paint event. Then the focus will switch back to the debugger, which again will (partially) cover the program window. Executing the next step, the whole procedure repeats, and you are confronted with a seemingly never-ending sequence of paint events.
To avoid this issue, make sure that the Visual Studio window(s) and your program window do not touch.
If the paint events still continue to happen although debugger and program windows are spatially separated, your code triggers paint event under other conditions as well. Examples of typical candidates which might trigger paint events would be handlers for events telling a window it has become the active window or handlers for focus events.
However, figuring this out might become somewhat cumbersome if the focus frequently switches between the debugger window and your program. If such a problem is suspected, the recommended approach would be to use remote debugging. Remote debugging means that the program to debug runs on a different machine/environment than the debugger, and thus is totally undisturbed from the debugger UI. For the debug environment you might use a second physical computer, but often using a virtual machine is a reasonable option as well.
I have added the scroll bar to the x-axis of my mschart control using this link Adding a scroll bar to MS Chart control C# and it worked as expected. But now my requirement is, I need zooming for both the axis. But since I removed Zoom reset button for x-axis, I have used the following to reset it by forcing.
private void chart1_AxisScrollBarClicked(object sender, ScrollBarEventArgs e)
{
// Handle zoom reset button
if(e.ButtonType == ScrollBarButtonType.ZoomReset)
{
// Event is handled, no more processing required
e.IsHandled = true;
// Reset zoom on X and Y axis
chart1.ChartAreas[0].AxisX.ScaleView.ZoomReset();
chart1.ChartAreas[0].AxisY.ScaleView.ZoomReset();
}
}
But it is not working properly. Please help me in fixing this in c#..
Try using ZoomReset(0).
private void zeroZoom_Click(object sender, EventArgs e)
{
chart1.ChartAreas[0].AxisX.ScaleView.ZoomReset(0);
chart1.ChartAreas[0].AxisY.ScaleView.ZoomReset(0);
}
The first thing coming in mind, is that your problem is related to multiple zooming.
As you had noticed, by default the zoom-reset button (exactly like the ZoomReset method) doesn't reset the zoom completely, but restore the previous view-status, i.e. if you have zoomed more than one time, it returns just to the previous zoomed view.
To completely reset the zoom, you can use this code:
while (chart1.ChartAreas[0].AxisX.ScaleView.IsZoomed)
chart1.ChartAreas[0].AxisX.ScaleView.ZoomReset();
while (chart1.ChartAreas[0].AxisY.ScaleView.IsZoomed)
chart1.ChartAreas[0].AxisY.ScaleView.ZoomReset();
Conversely, if you like the default zoom-reset behaviour, you should have two buttons for the two axis because it's possible to have different number of view-statex for the different axis.
Another possibility, is that you are zooming a secondary axis, like AxisX2 or AxisY2 (not sure, but I think that is depending on the chart type), so you should reset those (or, to be safe, just reset all axis...).
I tried with the below code today and it seems like working fine. Here the for loop handles the X axis with scroll and the next if block handles the ordinary X-axis. Could you please have a glance at it and let me know your views about it?
private void chart1_AxisScrollBarClicked(object sender, ScrollBarEventArgs e)
{
Boolean blnIsXaxisReset = false;
try
{
// Handle zoom reset button
if(e.ButtonType == ScrollBarButtonType.ZoomReset)
{
// Event is handled, no more processing required
e.IsHandled = true;
// Reset zoom on Y axis
while (chart1.ChartAreas[0].AxisY.ScaleView.IsZoomed)
chart1.ChartAreas[0].AxisY.ScaleView.ZoomReset();
//Handles Zoom reset on X axis with scroll bar
foreach (Series series in chart1.Series)
{
if (series.YAxisType == AxisType.Secondary)
{
chart1.ChartAreas[0].AxisX.ScaleView.Zoom(-10, 10);
blnIsXaxisReset = true;
break;
}
}
//Handles Zoom reset on ordinary X axis
if (blnIsXaxisReset == false)
{
while (chart1.ChartAreas[0].AxisX.ScaleView.IsZoomed)
chart1.ChartAreas[0].AxisX.ScaleView.ZoomReset();
}
}
}
catch (Exception ex)
{
BuildException buildException = new BuildException();
buildException.SystemException = ex;
buildException.CustomMessage = "Error in zooming the Chart";
ExceptionHandler.HandleException(buildException);
}
}
Thanks for your effort!!