I'm using the ArcGIS Runtime SDK 100.1.0 for .NET. I have a mobile map package (.mmpk) vector map and want to limit the maximum and minimum zoom in the MapView. I tried to track the MapScale property and set it:
((INotifyPropertyChanged)MyMapView).PropertyChanged += (sender, args) =>
{
args.PropertyName;
var s = MyMapView.MapScale;
if(s < 500)
MyMapView.SetViewpointScaleAsync(700);
if (s > 16500000)
MyMapView.SetViewpointScaleAsync(16500000);
};
This works, but map jerks at low/high zoom levels because it tries to smooth zoom and I can't figure out how to stop the active zooming task. What is the right way to do this?
The easy way
If you just want to set a minimum scale and a maximum scale, the Map class has MinScale and MaxScale properties. Replace your code with the following:
MyMapView.Map.MaxScale = 700;
MyMapView.Map.MinScale = 16500000;
The hard way
You probably don't need this! Use the easy way listed above unless you have a good reason to do something more complicated!
If instead for some reason you really want to track scale changes and then change the scale yourself, you should do it in a different way. Currently you're listening for PropertyChanged, which is way too broad. One effect is that when the scale changes, your event handler runs, which performs an asynchronous zoom, which generates a scale change before it's finished, which calls your event handler, which performs an asynchronous zoom, which generates a scale change before it's finished, which calls your event handler, which...I could go on and on. Literally. And so will your program, unless you make some changes.
Here's one way to do it:
// Save a variable so you can invoke the EventHandler elsewhere
EventHandler navigationCompletedHandler = (sender, args) =>
{
var s = MyMapView.MapScale;
if (s < 500)
MyMapView.SetViewpointScaleAsync(700);
if (s > 16500000)
MyMapView.SetViewpointScaleAsync(16500000);
};
MyMapView.NavigationCompleted += navigationCompletedHandler;
// Invoke the above handler one time when the map first loads
EventHandler firstViewpointChangeHandler = null;
firstViewpointChangeHandler = (sender, args) =>
{
if (!double.IsNaN(MyMapView.MapScale))
{
MyMapView.ViewpointChanged -= firstViewpointChangeHandler;
navigationCompletedHandler.Invoke(null, null);
}
};
MyMapView.ViewpointChanged += firstViewpointChangeHandler;
You can just set the zoom levels to whatever intervals you want in ArcMap for your map file, when you build your map package / publish it to a service ArcMap seems to honour it (does for me at least).
Map Scale Drop Down -> Customize This List -> Standard Scales -> Tick the box "Only display these scales when zooming"
Saves having to write a custom mapscale event handler.
Related
I want to add some Pins that I have stored in a DB to my Maps, the problem is, I want the Pin.MarkerClicked to do something different than the default Event. I cant find a function that lets me handle MarkerClicked Events and writing a specific event for every Pin is not an option since I dont know how many Pins there will be.
pin.MarkerClicked += async (s, args) =>
{
string pinName = ((Pin)s).Label;
args.HideInfoWindow = false;
await DisplayAlert("Pin Clicked", $"{pinName} was clicked.", "Ok");
};
this is the only way I know there is to edit the function but it requires work on each individual pin.
I'm running into an issue that I'm not sure is solvable in the way I want to solve it. I have a problem with a race condition.
I have one project running as a C++ dll (the main engine).
Then I have a second C# process that uses C++/CLI to communicate with the main engine (the editor).
The editor is hosting the engine window as a child window. The result of this is that the child window receives input messages async (see RiProcessMouseMessage()). Normally this only happens when I call window->PollEvents();.
main engine loop {
RiProcessMouseMessage(); // <- Called by the default windows message poll function from the child window
foreach(inputDevice)
inputDevice->UpdateState();
otherCode->UseCurrentInput();
}
The main editor loop is the WPF loop which I don't control. Basically it does this:
main editor loop {
RiProcessMouseMessage(); // <- This one is called by the editor (parent) window, but is using the message loop of the (child) engine window
}
The RawInput processor which is called sync by the engine and async by the editor:
void Win32RawInput::RiProcessMouseMessage(const RAWMOUSE& rmouse, HWND hWnd) {
MouseState& state = Input::mouse._GetGatherState();
// Check Mouse Position Relative Motion
if (rmouse.usFlags == MOUSE_MOVE_RELATIVE) {
vec2f delta((float)rmouse.lLastX, (float)rmouse.lLastY);
delta *= MOUSE_SCALE;
state.movement += delta;
POINT p;
GetCursorPos(&p);
state.cursorPosGlobal = vec2i(p.x, p.y);
ScreenToClient(hWnd, &p);
state.cursorPos = vec2i(p.x, p.y);
}
// Check Mouse Wheel Relative Motion
if (rmouse.usButtonFlags & RI_MOUSE_WHEEL)
state.scrollMovement.y += ((float)(short)rmouse.usButtonData) / WHEEL_DELTA;
if (rmouse.usButtonFlags & RI_MOUSE_HWHEEL)
state.scrollMovement.x += ((float)(short)rmouse.usButtonData) / WHEEL_DELTA;
// Store Mouse Button States
for (int i = 0; i < 5; i++) {
if (rmouse.usButtonFlags & maskDown_[i]) {
state.mouseButtonState[i].pressed = true;
state.mouseButtonState[i].changedThisFrame = true;
} else if (rmouse.usButtonFlags & maskUp_[i]) {
state.mouseButtonState[i].pressed = false;
state.mouseButtonState[i].changedThisFrame = true;
}
}
}
UpdateState() is called only by the engine. It basically swaps the RawInput to the currently used input. This is to prevent input updating in the middle of a frame loop (aka. during otherCode->UseCurrentInput();)
void UpdateState() {
currentState = gatherState; // Copy gather state to current
Reset(gatherState); // Reset the old buffer so the next time the buffer it's used it's all good
// Use current state to check stuff
// For the rest of this frame currentState should be used
}
MouseState& _GetGatherState() { return gatherState; }
void Reset(MouseState& state) { // Might need a lock around gatherState :(
state.movement = vec2f::zero;
state.scrollMovement = vec2f::zero;
for (int i = 0; i < 5; ++i)
state.mouseButtonState[i].changedThisFrame = false;
}
So as you can see the race condition happens when RiProcessMouseMessage() is called while Reset() was called in the main engine loop. If it wasn't clear: The Reset() function is required to reset state back to it's frames default data so that the data is read correctly every frame.
Now I'm very much aware I can fix this easily by adding a mutex around the gatherState updates but I would like to avoid this if possible. Basically I'm asking is it possible to redesign this code to be lock free?
You are asking lock-free which is not quite possible if both ends alter the buffer. But if you ask lock that is optimized and almost instantaneous then you can use FIFO logic. You can use the .net's ConcurrentQueue "https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentqueue-1?view=net-5.0" to write updates and poll updates from this queue.
If you really get rid of the lock then you may check lock-free circular arrays aka lock-free ring-buffer,
If you want to dig deeper into hardware level to understand the logic behind this then you can check https://electronics.stackexchange.com/questions/317415/how-to-allow-thread-and-interrupt-safe-writing-of-incoming-usart-data-on-freerto so you will have an idea about concurrency at the low-level as well; With limitations, a lock-free ring buffer can work when one end only writes and the other end only reads within known intervals/boundaries can check similar questions asked:
Circular lock-free buffer
Boost has well-known implementations for lock-free: https://www.boost.org/doc/libs/1_65_1/doc/html/lockfree.html
I have a control that allows the user to perform some heavy duty image processing on a specific part of an image and they have arrow buttons to move this area around the image.
as the process is very heavy duty (avg 800ms per run) I have used a repeat button which turns this are into a "Ghost" and only executes the process upon the mouse up event.
This works really well and solves most performance issues relating to this function
HOWEVER
A certain group of users are refusing to learn this method of holding and releasing and persist in tapping the button to move it rather than holding and releasing.
This means that the heavy duty method is being called every time the they tap and as it only moves a small increment each time the method fires, so they end up with a application hang whilst it tries to do > 100 of these 800ms + processes
MY QUESTION
How can I handle this tapping behaviour in the same way as holding and releasing?
I thought about a timer but cant work out how I would detect the difference between a normal tap and the last tap.
Quick, dirty solution: use a Timer.
Each time the user taps the button, stop the timer, increase the number of taps, start the timer. If the timer elapses before the user taps again, then it should do your big work method.
Is this prone to threading issues? Probably. I'm not a threading expert. I would love if a threading expert can come comment on it.
Is this the best solution? Hardly. But it will get you by for a while (until the threading issues come up).
private int _totalTaps = 0;
private const int _tapSequenceThreshold = 250; // Milliseconds
private Timer _tapTimer = new Timer(_tapSequenceThreshold);
private void InitializeTimer()
{
_tapTimer.Elapsed += OnTapTimerElapsed;
}
private void OnTapTimerElapsed(object source, System.Timers.ElapsedEventArgs e)
{
_tapTimer.Stop();
// The `DoBigLogic` method should take the number of taps and
// then do *something* based on that number, calculate how far
// to move it, for example.
DoBigLogic(_totalTaps);
_totalTaps = 0;
}
// Call this each time the user taps the button
private void Tap()
{
_tapTimer.Stop();
_totalTaps++;
_tapTimer.Start();
}
Best solution: this method plus moving this work off the GUI thread. Then you don't have to worry about taps or click-and-hold, you won't block the GUI thread.
If you have to do work that doesn't update the UI (redraw the image, for example) then send the image to the UI, you can make a new thread, then you'll hit an error about 'accessing a UI element from a non-UI thread', just drop the UI code in a Marshal for it.
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
Windows.UI.Core.CoreDispatcherPriority.Normal,
() => { UpdateDisplay(); }
);
Consider monitoring mouse activity and start your heavy duty process after a short period of inactivity.
Consider running the process on a separate thread - this might mean cloning (part of) the image in memory.
Consider preventing the process from being ran multiple times concurrently (if that is possible ie. the process is async).
Consider one of these options:
Disable the button until the process completes, causing that 800ms delay. Users will soon learn to use the hold down method. This would involve the smallest amount of code and put the onus on humans. It also ensures you are not holding up the app with clicks in the buffer or over-using resources.
Put a timer in your button click event:
'Ghost area'
Timer Start ( or reset to zero)
Then the code to call your main work is in the timer elapsed event which will be set to whatever pause you wish. (ie If a user has not clicked again within a second or so)
Then stop the timer
Execute code
You could try using reactive extensions which is available on nuget.
using System;
using System.Windows.Controls;
using System.Windows.Input;
using System.Linq;
using System.Reactive.Linq;
namespace SmoothOutButtonTapping
{
public static class Filters
{
// First we need to combine the pressed events and the released
// events into a single unfiltered stream.
private static IObservable<MouseButtonState> GetUnfilteredStream(Button button)
{
var pressedUnfiltered = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
x => button.PreviewMouseLeftButtonDown += x,
x => button.PreviewMouseLeftButtonDown -= x);
var releasedUnfiltered = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
x => button.PreviewMouseLeftButtonUp += x,
x => button.PreviewMouseLeftButtonUp -= x);
return pressedUnfiltered
.Merge(releasedUnfiltered)
.Select(x => x.EventArgs.ButtonState);
}
// Now we need to apply some filters to the stream of events.
public static IObservable<MouseButtonState> FilterMouseStream(
Button button, TimeSpan slidingTimeoutWindow)
{
var unfiltered = GetUnfilteredStream(button);
// Ironically, we have to separate the pressed and released events, even
// though we just combined them.
// This is because we need to apply a filter to throttle the released events,
// but we don't need to apply any filters to the pressed events.
var released = unfiltered
// Here we throttle the events so that we don't get a released event
// unless the button has been released for a bit.
.Throttle(slidingTimeoutWindow)
.Where(x => x == MouseButtonState.Released);
var pressed = unfiltered
.Where(x => x == MouseButtonState.Pressed);
// Now we combine the throttled stream of released events with the unmodified
// stream of pressed events.
return released.Merge(pressed);
}
}
}
Now we have a stream that will respond immediately whenever a user presses, but will not fire a released event unless the button is released for long enough.
Here is an example of how you could consume the above method. This example simply changes the color of the control while the button is in the Pressed state, but you could easily do whatever you wanted.
using System;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Reactive.Linq;
using System.Threading;
namespace SmoothOutButtonTapping
{
public partial class SmoothTapButtonControl : UserControl
{
public SmoothTapButtonControl()
{
InitializeComponent();
_pressed = new SolidColorBrush(Colors.Lime);
_released = Background;
// Don't forget to call ObserveOn() to ensure your UI controls
// only get accessed from the UI thread.
Filters.FilterMouseStream(button, SlidingTimeoutWindow)
.ObserveOn(SynchronizationContext.Current)
.Subscribe(HandleClicked);
}
// This property indicates how long the button must wait before
// responding to being released.
// If the button is pressed again before this timeout window
// expires, it resets.
// This is handled for us automatically by Reactive Extensions.
public TimeSpan SlidingTimeoutWindow { get; set; } = TimeSpan.FromSeconds(.4);
private void HandleClicked(MouseButtonState state)
{
if (state == MouseButtonState.Pressed)
Background = _pressed;
else
Background = _released;
}
private Brush _pressed;
private Brush _released;
}
}
You can find the complete version of the above examples (project files and xaml included) on my github.
You can prevent program from executing your 800 ms function by setting simple flags(Preferably bool type).
You need to create three events. One Mouse Down, one Mouse Up event and other is Mouse Move event of the button. Set your flag false at declaration. When you click the button, make your flag true in Mouse Down event. And when you lift your mouse click i.e., Mouse Up event make your flag false.
Simple illustration of code.
bool click = false,run_process = true;
mouseDown_event()
{
click = true;
}
mouseUp_event()
{
click = false;
}
mouseMove_event()
{
if(click == true && run_process == true)
{
click = false;
run_process = false;
//call your function
}
}
How can I handle this tapping behaviour in the same way as holding and releasing?
Create a formula which calculates a weight value based last tap frequency, current operation and time between last actual operation; with any other factors I may not be aware of. With the right formula it should be representational to the person who uses the system correctly verses someone who sends multiple clicks.
The weighted value should be passed to an alternate thread which is handling the actual operation to the screen and can handle a blizzard of taps or a single tap without missing a beat, per se.
I'm developing an application for a Windows Surface Pro. I need to gather the pen pressure on the screen and i figuret out to do.
After solving some issue due to Event (StylusDown, StylusUp and other Stylus event are never raised, bu only mouse event) i landed in a work around.
I will show you the code (taken from some microsoft guide)
Basically is a filter for raw event
class MyFilterPlugin : StylusPlugIn
{
protected override void OnStylusDown(RawStylusInput rawStylusInput)
{
// Call the base class before modifying the data.
base.OnStylusDown(rawStylusInput);
Console.WriteLine("Here");
// Restrict the stylus input.
Filter(rawStylusInput);
}
private void Filter(RawStylusInput rawStylusInput)
{
// Get the StylusPoints that have come in.
StylusPointCollection stylusPoints = rawStylusInput.GetStylusPoints();
// Modify the (X,Y) data to move the points
// inside the acceptable input area, if necessary.
for (int i = 0; i < stylusPoints.Count; i++)
{
Console.WriteLine("p: " + stylusPoints[i].PressureFactor);
StylusPoint sp = stylusPoints[i];
if (sp.X < 50) sp.X = 50;
if (sp.X > 250) sp.X = 250;
if (sp.Y < 50) sp.Y = 50;
if (sp.Y > 250) sp.Y = 250;
stylusPoints[i] = sp;
}
// Copy the modified StylusPoints back to the RawStylusInput.
rawStylusInput.SetStylusPoints(stylusPoints);
}
Added as Filter in StylusPlugin
StylusPlugIns.Add(new MyFilterPlugin());
While i run this, however, i always get 0.5 as PressureFactor (the default value) and inspecting more deep I still can see the evironment of Stylus is not properly set (his Id is 0 for example).
There is a way to gather StylusEvent correctly?
The main point is: i need to gather the pressure (how much pressure) of the pen on the screen.
Thanks a lot.
Some Info: i'm developing with visuals tudio 2012 Ultimate, on a win 7 Pc. I deploy the application in a Surface Pro with windows 8.1. I installed and configured the Surface SDK 2.0 and the Surface Runtime.
For completition i have solved this.
Stylus event (don't know how) conflicted with the Surface SDK. Once i removed the SDK reference from the project all went smooth and the problem i solved.
Now i can gather correctly the pressure of each pen touch and on pen move.
I post this only for community information.
In my opinion the MS Office Smooth Typing is a very innovative feature in the Office Suite, and I'd like to know if this feature is available for programmers in the .NET Framework, specifically in the C# language.
If so, could you please post in your answer a usage example and link to the documentation?
Thanks.
By "smooth typing" I'm referring to the typing animation, that makes the cursor slide during typing.
I don't own Office, so I can't look at the feature, but I needed to fiddle around with the caret in RichTextBoxes a while ago and decided that it wasn't worth the effort. Basically you are on your own. No helper functions from .NET, but everything is handled by the backing Win32 control. You will have a hard time defeating what already happens under the hood. And probably ending up intercepting window messages and lots of ugly code.
So my basic advice is: Don't do it. At least for basic form controls like the TextBox or RichTextBox. You may have more luck trying to remote access an running office from within .NET, but that is a totally different can of worms.
If you really insist on going the SetCaretPos - route, here is some code to get you up and running with a basic version where you can improve upon:
// import the functions (which are part of Win32 API - not .NET)
[DllImport("user32.dll")] static extern bool SetCaretPos(int x, int y);
[DllImport("user32.dll")] static extern Point GetCaretPos(out Point point);
public Form1()
{
InitializeComponent();
// target position to animate towards
Point targetCaretPos; GetCaretPos(out targetCaretPos);
// richTextBox1 is some RichTextBox that I dragged on the form in the Designer
richTextBox1.TextChanged += (s, e) =>
{
// we need to capture the new position and restore to the old one
Point temp;
GetCaretPos(out temp);
SetCaretPos(targetCaretPos.X, targetCaretPos.Y);
targetCaretPos = temp;
};
// Spawn a new thread that animates toward the new target position.
Thread t = new Thread(() =>
{
Point current = targetCaretPos; // current is the actual position within the current animation
while (true)
{
if (current != targetCaretPos)
{
// The "30" is just some number to have a boundary when not animating
// (e.g. when pressing enter). You can experiment with your own distances..
if (Math.Abs(current.X - targetCaretPos.X) + Math.Abs(current.Y - targetCaretPos.Y) > 30)
current = targetCaretPos; // target too far. Just move there immediately
else
{
current.X += Math.Sign(targetCaretPos.X - current.X);
current.Y += Math.Sign(targetCaretPos.Y - current.Y);
}
// you need to invoke SetCaretPos on the thread which created the control!
richTextBox1.Invoke((Action)(() => SetCaretPos(current.X, current.Y)));
}
// 7 is just some number I liked. The more, the slower.
Thread.Sleep(7);
}
});
t.IsBackground = true; // The animation thread won't prevent the application from exiting.
t.Start();
}
Use SetCaretPos with your own animation timing function. Create a new thread that interpolates the caret's position based on the previous location and the new desired location.