Unable to reset ProgressBar - c#

I am experimenting a behavior which makes me crazy.
I have a ProgressBar which represents the evolution of an import in database (in percents, from 0 to 100).
After the import is done (ProgressBar.Value = 100.0), I open a log window with a code which looks like this :
RadWindow window = new RadWindow()
{
//Set some properties
};
window.Closed += Log_Closed;
window.ShowDialog();
After the RadWindow is closed, I want to reset the ProgressBar. As you can see I use the function Log_Closed whose code is bellow :
private void Log_Closed(object sender, EventArgs e)
{
//pbImport.Value = pbImport.Minimum; (didn't work)
pbImport.Value = 0;
}
Note : pbImport is my progress bar.
The instruction in Log_Closed has no effect.
Before instruction :
After instruction :
Obviously, the progress bar is not updated in UI. I can't understand this. Thank you for your help.

Animations hold onto properties, in order to reset them in code, you have to remove the animation first so that the property is "released".
See https://msdn.microsoft.com/en-us/library/aa970493%28v=vs.110%29.aspx for information on how to set a property after an animation in WPF.

Resetting the progress Bar can be achieved by using an "if" loop and incrementing the progress bar.
You can set a bool value for the database process and then simply:
private void Log_Closed(object sender, EventArgs e)
{
//pbImport.Value = pbImport.Minimum; (didn't work)
pbImport.Value = 0;
if (database)
{
pbImport.Increment(100);
}
}

From Microsoft's documentation -
To remove a specific AnimationClock from a list of clocks, use the Controller property of the AnimationClock to retrieve a ClockController, then call the Remove method of the ClockController. This is typically done in the Completed event handler for a clock. Note that only root clocks can be controlled by a ClockController; the Controller property of a child clock will return null. Note also that the Completed event will not be called if the effective duration of the clock is forever. In that case, the user will need to determine when to call Remove.
In the example below I demonstrate setting up an event handler that runs when the animation is complete and removes the clock controller there, then set the ProgressBar value back to 0.
void RunAnimation()
{
Duration duration = new Duration(TimeSpan.FromSeconds(10));
DoubleAnimation doubleanimation = new DoubleAnimation(100.0, duration);
doubleanimation.Completed += ProgressBarCompleted;
ProgBar.BeginAnimation(ProgressBar.ValueProperty, doubleanimation);
}
private void ProgressBarCompleted(object sender, EventArgs e)
{
var clock = (AnimationClock)sender;
clock.Controller.Remove();
ProgBar.Value = 0;
}
Note: ProgBar is defined in a .xaml file like
<ProgressBar Margin="0,0,0,0"
Padding="0,0,0,0"
x:Name="ProgBar"
Width="800"
HorizontalAlignment="Right"
Foreground="LightGray"/>

Related

(WPF) How to ping from a class in c#?

I'm creating a WPF tool to use on my HelpDesk team using XAML and C#, but I'd like to improve upon what I've done. I've got a textbox to enter a hostname, a button to start a ping, and a textbox that will keep pinging that hostname until I press the button again.
I'd like to keep the functionality the same, but I know there's a better way to write this code.
Currently, I have a class that pings the server and returns a response ONE single time. My main form calls this class, the logic is not written in the main form. When I tried to make this loop, it just kills the function after the first run through rather than looping. The way I got it to work was by using a timer to call the function/ping class every 500 MS or so, and stopping the timer by clicking the button again.
Here's my current way of doing this, t stands for "timer":
private void onTick(object sender, EventArgs e)
{
PingClass pingClass = new PingClass();
_richtext.AppendText(pingClass.PingHost(_textBox.Text));
_richtext.ScrollToEnd();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if ((string)_pingBtn.Content == "Ping")
{
_richtext.Visibility = Visibility.Visible;
_pingBtn.Content = "Stop Ping";
t.Interval = 500;
t.Tick += onTick;
t.Start();
}
else if ((string)_pingBtn.Content == "Stop Ping")
{
_richtext.Visibility = Visibility.Hidden;
_pingBtn.Content = "Ping";
t.Stop();
}
The class itself is pretty simple, it just returns a string of whatever the response from the host is. Unfortunately, it doesn't allow me to loop through inside the class because it stops at the first return. I'd like to gather an "average ping" and some other stats, but can't with the way it's currently set up. I don't want to have a timer, I want to be able to loop in the class itself until the button in the main form is pressed. Is this possible?

Fading effect on Splash sreen

I am trying to make a fading effect on my splash screen, on a WPF application.
The Opacity of the image object is initially 0. This code would modify the Opacity from 0 (min) to 1 (max), but the line img_waves.Opacity just doesn't work. The image opacity remains 0.
private void Splash_ContentRendered(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(3000);
for (double x = 0; x<=1; x+=0.01d)
{
System.Threading.Thread.Sleep(15);
//MessageBox.Show(x.ToString());
img_waves.Opacity = x;
}
this.Close();
}
But, if I activate the line ´MessageBox.Show(x.ToString());´
as you can see on this image:
The code works, but I have to keep clicking on the message boxes.
My ask is: Why? Why doesn't work without the MessageBox.Show?
Because you're blocking the GUI thread. It never gets a chance to redraw the form. When you add the message box, the message queue is pumped, which allows the drawing.
The simplest way to deal with this would be like this:
private async void Splash_ContentRendered(object sender, EventArgs e)
{
await Task.Delay(3000);
for (double x = 0; x<=1; x+=0.01d)
{
await Task.Delay(15);
img_waves.Opacity = x;
}
this.Close();
}
Do note that this means the form can still be interacted with during the animation. This shouldn't be a problem for a splashscreen, but it could cause you trouble in a "real" form. Still, make sure the form can't be closed during the animation - that could cause exceptions :)
There's also other ways to force the message queue to be pumped, but it's usually frowned upon.
All that said, you're using WPF - why are you doing the animation manually like this? Can't you just handle it as an animation effect in WPF, natively? There's a sample on MSDN.
Whilst I agree with #Luaan explanation as to why as an alternative solution to your loop you can use Storyboard with DoubleAnimation on Opacity property
private void Splash_ContentRendered(object sender, EventArgs e)
{
var sb = new Storyboard();
var da = new DoubleAnimation(0, 1, new Duration(TimeSpan.FromSeconds(1.5)));
da.BeginTime = TimeSpan.FromSeconds(3);
Storyboard.SetTargetProperty(da, new PropertyPath("Opacity"));
Storyboard.SetTarget(da, img_waves);
sb.Children.Add(da);
sb.Completed += (s1, e1) => this.Close();
sb.Begin();
}

Mismatch between PointerPressed and PointerReleased events

I was using Monogame and had an issue whereby if I rapidly placed lots of fingers on the screen and then quickly removed them, eventually (after a few repeats) I would end up with some 'Pressed' events that never got a matching 'Released' event.
I don't think the issue is with Monogame though as I can reproduce the issue (on my Nokia Lumia 920) with a tiny 'Windows Phone and Direct3D' App created using vs2012.
On the C++ side I just store a vector inside the generated Direct3DInterop class that records pressed events and released events
void Direct3DInterop::OnPointerPressed(DrawingSurfaceManipulationHost^ sender, PointerEventArgs^ args)
{
uint32 pointerID = args->CurrentPoint->PointerId;
auto i = std::find (_pressed.begin(), _pressed.end(), pointerID);
if (i == _pressed.end())
_pressed.push_back(pointerID);
}
void Direct3DInterop::OnPointerReleased(DrawingSurfaceManipulationHost^ sender, PointerEventArgs^ args)
{
uint32 pointerID = args->CurrentPoint->PointerId;
_pressed.erase(std::remove(_pressed.begin(), _pressed.end(), pointerID), _pressed.end());
}
In my XAML, I just have a textblock and a dispatch timer that periodically checks the count of elements in _pressed (it should always return to 0).
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent"
Margin="0,115,0,0">
<DrawingSurface x:Name="DrawingSurface" Margin="0,-113,0,0" Loaded="DrawingSurface_Loaded"/>
<TextBlock x:Name="LostReleasedCount" Text="Lost released count = 0" HorizontalAlignment="Left" Margin="0,-113,0,0" VerticalAlignment="Top" Height="113" Width="480"/>
</Grid>
and in the code behind file
private Direct3DInterop m_d3dInterop = null;
private DispatcherTimer _timer;
private int _seconds;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Creates a new instance of a timer.
_timer = new DispatcherTimer();
// Tells the _timer to tick every second.
_timer.Interval = TimeSpan.FromSeconds(1);
_timer.Tick += TimerOnTick;
_timer.Start();
_seconds = 0;
}
private void TimerOnTick(object sender, EventArgs eventArgs)
{
Dispatcher.BeginInvoke(() =>
{
_seconds++;
LostReleasedCount.Text = "Lost released count = " + m_d3dInterop.GetPressedCount().ToString();
});
}
Am I missing something obvious here? Should you always expect the pressed events and the released events to match? I cannot reproduce the problem if I use XAML directly with a Touch.FrameReported event so it's presumably some issue with DirectX?
Without a matching pressed/released pair, I'm not sure how to distinguish a genuine problem from someone just holding their finger on the screen and not moving it.
From http://msdn.microsoft.com/library/windows/apps/br208971:
Other events instead of PointerReleased may fire at the end of the action—for example, PointerCanceled or PointerCaptureLost. Don't rely on PointerPressed and PointerReleased events always occurring in pairs. To function properly, your app must listen for and handle all events that represent likely conclusions to the Press action. Some of the reasons why you might not get a PointerReleased occurrence are:
Differences in how specific hardware handles touch actions and Press actions
A programmatic pointer capture from a different pointer
User actions that change the relationship of the display area, such as changing resolution or monitor settings
Input interactions such as a stylus touching the same surface as a previous touch action
There may be another reason. Please see my answer here: How to handle multi touch in windows phone 8 app
Possible reason of this can be race condition.
add this function in Direct3DBackground class
void Direct3DBackground::SetManipulationHost(Windows::Phone::Input::Interop::
DrawingSurfaceManipulationHost^ manipulationHost)
{
manipulationHost->PointerPressed +=
ref new TypedEventHandler<
DrawingSurfaceManipulationHost^, PointerEventArgs^>
(this, &Direct3DBackground::OnPointerPressed);
manipulationHost->PointerMoved +=
ref new TypedEventHandler<DrawingSurfaceManipulationHost^,
PointerEventArgs^>(this, &Direct3DBackground::OnPointerMoved);
manipulationHost->PointerReleased +=
ref new TypedEventHandler<DrawingSurfaceManipulationHost^,
PointerEventArgs^>(this, &Direct3DBackground::OnPointerReleased);
}
and in XAML C# code:
DrawingSurfaceBackground.SetBackgroundManipulationHandler(m_d3dBackGround);

WPF: Detect Animation or Cancel Timeline.Completed Event? How?

I'm moving 3d camera like this:
Point3DAnimation pa;
// Triggered by user click
void MoveCamera(object sender, EventArgs e)
{
pa = new Point3DAnimation(myPoint3D, TimeSpan.FromMilliseconds(2000));
pa.Completed += new EventHandler(pa_Completed);
Camera.BeginAnimation(PerspectiveCamera.PositionProperty, pa); // anim#1
}
// we're in place. do some idle animation
void pa_Completed(object sender, EventArgs e)
{
pa = new Point3DAnimation(myPoint3Ddz, TimeSpan.FromMilliseconds(5000));
Camera.BeginAnimation(PerspectiveCamera.PositionProperty, pa); // anim#2
}
User clicks.
Camera moves to choosen position (anim#1).
When anim#1 ends anim#2 is played.
Everything is ok... until user triggers MoveCamera when previous anim#1 is not finished.
In that case:
New anim#1 is starting.
Completed event of old anim#1 is triggered.
anim#2 is started instatntly (overlapping new anim#1).
2 & 3 are wrong here. How i can avoid that?
I think that pa_Completed() should detect that new anim#1 is already playing, or MoveCamera() should unregister Complete event from old anim#1. But what the right way to do it?
If the goal is to chain two animations together, let WPF do the heavy lifting by using the Point3DAnimationUsingKeyFrames class.
First, build the key frame animation in XAML (it's a bear to do it in code):
<Window.Resources>
<Point3DAnimationUsingKeyFrames x:Key="CameraMoveAnimation" Duration="0:0:7">
<LinearPoint3DKeyFrame KeyTime="28%" />
<LinearPoint3DKeyFrame KeyTime="100%" />
</Point3DAnimationUsingKeyFrames>
</Window.Resources>
Next, consume it and set the actual Point3D values (using your code names):
private void MoveCamera(object sender, EventArgs e) {
Point3DAnimationUsingKeyFrames cameraAnimation =
(Point3DAnimationUsingKeyFrames)Resources["CameraMoveAnimation"];
cameraAnimation.KeyFrames[0].Value = myPoint3D;
cameraAnimation.KeyFrames[1].Value = myPoint3dz;
Camera.BeginAnimation(PerspectiveCamera.PositionProperty, cameraAnimation);
}

How do I safely populate with data and Refresh() a DataGridView in a multi-threaded application?

My app has a DataGridView object and a List of type MousePos. MousePos is a custom class that holds mouse X,Y coordinates (of type "Point") and a running count of this position. I have a thread (System.Timers.Timer) that raises an event once every second, checks the mouse position, adds and/or updates the count of the mouse position on this List.
I would like to have a similar running thread (again, I think System.Timers.Timer is a good choice) which would again raise an event once a second to automatically Refresh() the DataGridView so that the user can see the data on the screen update. (like TaskManager does.)
Unfortunately, calling the DataGridView.Refresh() method results in VS2005 stopping execution and noting that I've run into a cross-threading situation.
If I'm understanding correctly, I have 3 threads now:
Primary UI thread
MousePos List thread (Timer)
DataGridView Refresh thread (Timer)
To see if I could Refresh() the DataGridView on the primary thread, I added a button to the form which called DataGridView.Refresh(), but this (strangely) didn't do anything. I found a topic which seemed to indicate that if I set DataGridView.DataSource = null and back to my List, that it would refresh the datagrid. And indeed this worked, but only thru the button (which gets handled on the primary thread.)
So this question has turned into a two-parter:
Is setting DataGridView.DataSource to null and back to my List an acceptable way to refresh the datagrid? (It seems inefficient to me...)
How do I safely do this in a multi-threaded environment?
Here's the code I've written so far (C#/.Net 2.0)
public partial class Form1 : Form
{
private static List<MousePos> mousePositionList = new List<MousePos>();
private static System.Timers.Timer mouseCheck = new System.Timers.Timer(1000);
private static System.Timers.Timer refreshWindow = new System.Timers.Timer(1000);
public Form1()
{
InitializeComponent();
mousePositionList.Add(new MousePos()); // ANSWER! Must have at least 1 entry before binding to DataSource
dataGridView1.DataSource = mousePositionList;
mouseCheck.Elapsed += new System.Timers.ElapsedEventHandler(mouseCheck_Elapsed);
mouseCheck.Start();
refreshWindow.Elapsed += new System.Timers.ElapsedEventHandler(refreshWindow_Elapsed);
refreshWindow.Start();
}
public void mouseCheck_Elapsed(object source, EventArgs e)
{
Point mPnt = Control.MousePosition;
MousePos mPos = mousePositionList.Find(ByPoint(mPnt));
if (mPos == null) { mousePositionList.Add(new MousePos(mPnt)); }
else { mPos.Count++; }
}
public void refreshWindow_Elapsed(object source, EventArgs e)
{
//dataGridView1.DataSource = null; // Old way
//dataGridView1.DataSource = mousePositionList; // Old way
dataGridView1.Invalidate(); // <= ANSWER!!
}
private static Predicate<MousePos> ByPoint(Point pnt)
{
return delegate(MousePos mPos) { return (mPos.Pnt == pnt); };
}
}
public class MousePos
{
private Point position = new Point();
private int count = 1;
public Point Pnt { get { return position; } }
public int X { get { return position.X; } set { position.X = value; } }
public int Y { get { return position.Y; } set { position.Y = value; } }
public int Count { get { return count; } set { count = value; } }
public MousePos() { }
public MousePos(Point mouse) { position = mouse; }
}
You have to update the grid on the main UI thread, like all the other controls. See control.Invoke or Control.BeginInvoke.
UPDATE! -- I partially figured out the answer to part #1 in the book "Pro .NET 2.0 Windows Forms and Customer Controls in C#"
I had originally thought that Refresh() wasn't doing anything and that I needed to call the Invalidate() method, to tell Windows to repaint my control at it's leisure. (which is usually right away, but if you need a guarantee to repaint it now, then follow up with an immediate call to the Update() method.)
dataGridView1.Invalidate();
But, it turns out that the Refresh() method is merely an alias for:
dataGridView1.Invalidate(true);
dataGridView1.Update(); // <== forces immediate redraw
The only glitch I found with this was that if there was no data in the dataGridView, no amount of invalidating would refresh the control. I had to reassign the datasource. Then it worked fine after that. But only for the amount of rows (or items in my list) -- If new items were added, the dataGridView would be unaware that there were more rows to display.
So it seems that when binding a source of data (List or Table) to the Datasource, the dataGridView counts the items (rows) and then sets this internally and never checks to see if there are new rows/items or rows/items deleted. This is why re-binding the datasource repeatedly was working before.
Now to figure out how to update the number of rows to display in dataGridView without having to re-bind the datasource... fun, fun, fun! :-)
After doing some digging, I think I have my answer to part #2 of my question (aka. safe Multi-threading):
Rather than using System.Timers.Timer, I found that I should be using System.Windows.Forms.Timer instead.
The event occurs such that the method that is used in the Callback automatically happens on the primary thread. No cross-threading issues!
The declaration looks like this:
private static System.Windows.Forms.Timer refreshWindow2;
refreshWindow2 = new Timer();
refreshWindow2.Interval = 1000;
refreshWindow2.Tick += new EventHandler(refreshWindow2_Tick);
refreshWindow2.Start();
And the method is like this:
private void refreshWindow2_Tick(object sender, EventArgs e)
{
dataGridView1.Invalidate();
}
Looks like you have your answer right there!
Just in cawse you're curious about how to do cross thread calls back to ui:
All controls have a Invoke() method (or BEginInvoke()- in case you want to do things asynchronously), this is used to call any method on the control within the context of the main UI thread.
So, if you were going to call your datagridview from another thread you would need to do the following:
public void refreshWindow_Elapsed(object source, EventArgs e)
{
// we use anonymous delgate here as it saves us declaring a named delegate in our class
// however, as c# type inference sometimes need a bit of 'help' we need to cast it
// to an instance of MethodInvoker
dataGridView1.Invoke((MethodInvoker)delegate() { dataGridView1.Invalidate(); });
}

Categories