I got a little problem with button events. I programmed one button to decrease specific value by 1 (click), and I want to decrease it over time while holding button pressed. I'm using Silverlight, not XNA.
myTimer.Change(0, 100);
private void OnMyTimerDone(object state)
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
if (rightButton.IsPressed)
{
rightButton_Click(null, null);
}
});
}
this code is correctly working at the beginning, but then I am unable to single tap as it is always calling hold event.
Try to use the RepeatButton silverlight control instead of using a normal Button
Here is an Example of how to use it:
XAML Code:
<RepeatButton x:Name="rbtnDecrease" Content="Decrease" Delay="200" Interval="100" Click="rbtnDecrease_Click" />
Delay: The amount of time, in milliseconds, the RepeatButton waits while it is pressed before it starts repeating.
Interval: The amount of time, in milliseconds, between repeats once repeating starts.
C# Code:
private int tempCount = 100; // A temp Variable used as an Example
private void rbtnDecrease_Click(object sender, RoutedEventArgs e){
// Add your Button Click/Repeat Code Here...
// Example of Decreasing the value of a Variable
tempCount--;
}
Two suggestions, the first being to stop the timer (using a DispatcherTimer) if isPressed is false
void Button_MouseLeftButtonDown(object sender, EventArgs e)
{
myTimer.Start();
}
void OnTimerTick(object s, EventArgs args)
{
if(rightButton.IsPressed == false)
{
myTimer.Stop();
}
else
{
// decrease value
}
}
the second being to stop the timer on the MouseLeftButtonUp event
Related
I have a problem with following, very simplified case being part of my project. Consider we have GUI like below:
I have two background workers:
plot_bgworker - in this example, it increments plot counter,
data_bgworker - in this example, it increments data counter.
I also have label_timer, which updates incremented values diplayed on my form.
To manage both background workers and timer, I wrote two functions:
private: void turnOnAcquisition() {
if (!counting_paused)
return;
if (!plot_bgworker->IsBusy)
plot_bgworker->RunWorkerAsync();
if (!data_bgworker->IsBusy)
data_bgworker->RunWorkerAsync();
label_timer->Enabled = true;
counting_paused = false;
}
private: void turnOffAcquisition() {
if (counting_paused)
return;
if (plot_bgworker->IsBusy)
plot_bgworker->CancelAsync();
if (data_bgworker->IsBusy)
data_bgworker->CancelAsync();
label_timer->Enabled = false;
counting_paused = true;
}
Then, here is what happens when I click each of my buttons:
// Pauses counting on click
private: System::Void stop_btn_Click(System::Object^ sender, System::EventArgs^ e) {
turnOffAcquisition();
}
// Starts counting on click
private: System::Void start_btn_Click(System::Object^ sender, System::EventArgs^ e) {
turnOnAcquisition();
}
// Should restart counting on click, beginning from 0 (no matter what state counting is in right now)
private: System::Void restart_btn_Click(System::Object^ sender, System::EventArgs^ e) {
plot_counter = 0;
data_counter = 0;
turnOffAcquisition();
turnOnAcquisition();
}
Finally, here are my background workers (turned off / on by CancelAsync() / RunWorkerAsync() ) and timer:
// Calculating data counter
private: System::Void data_bgworker_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) {
for (;;) {
data_counter++;
Sleep(50);
if (data_bgworker->CancellationPending) {
e->Cancel = true;
return;
}
}
}
// Calculating plot counter
private: System::Void plot_bgworker_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) {
for (;;) {
plot_counter++;
Sleep(120);
if (plot_bgworker->CancellationPending) {
e->Cancel = true;
return;
}
}
}
// Display counters
private: System::Void label_timer_Tick(System::Object^ sender, System::EventArgs^ e) {
plot_counter_label->Text = numToMStr(plot_counter);
data_counter_label->Text = numToMStr(data_counter);
}
Start button and stop button work both as expected, but now I have a problem with restart button. When I click it in the middle of counting, it seems to reset values and stop background workers, but never start them again (as I would expect after calling turnOnAcquisition). However, when I click it when counting is off, I am able to turn on counting as expected.
My first shot was that cancellation flag is not yet set to another value when I tried to check if my workers were busy, but using Sleep() between calls didn't work. Another guess is that it is due to race condition failure, so I tried using MemoryBarrier(), but I don't know the libraries and I'm not sure if it would work. Also, I tried to use Interlocked class, but couldn't use it properly for void functions.
1. Is this way of thinking correct?
2. If yes, why simple Sleep() doesn't do the trick?
3. How would I use any of mentioned methods in this case and which one would be the best match?
Ok, I found the solution by myself. The problem here was about the race condition - one event tried to stop counting (which meant raising another event) and then starting it again (which was problematic, as my function (I guess) was already cluttered with the first one and probably the second event wasn't even added to the event detected queue). If I am wrong with the explanation, I would appreciate some criticism down there ;)
Here are two modified functions, which solved thread management correctly. The key was to let the other events do their work until I get desired state.
When I want to turn off counting, I let the applications do the events from the queue until both threads will not be busy (the 'while' loop):
private: void turnOffAcquisition() {
if (counting_paused)
return;
if (plot_bgworker->IsBusy)
plot_bgworker->CancelAsync();
if (data_bgworker->IsBusy)
data_bgworker->CancelAsync();
while((plot_bgworker->IsBusy) || (data_bgworker->IsBusy)) // Continue to process events until both workers stop working
Application::DoEvents(); // Then, you can process another thread requests! :)
label_timer->Enabled = false;
counting_paused = true;
}
Similarily, when I want to restart counting, I let the application do the events until I check that both threads are busy (again, the 'while' loop):
private: void turnOnAcquisition() {
if (!counting_paused)
return;
if (!plot_bgworker->IsBusy)
plot_bgworker->RunWorkerAsync();
if (!data_bgworker->IsBusy)
data_bgworker->RunWorkerAsync();
while((!plot_bgworker->IsBusy) || (!data_bgworker->IsBusy)) // Continue to process events until both workers start working
Application::DoEvents(); // Then, you can process another thread requests! :)
label_timer->Enabled = true;
counting_paused = false;
}
I'm in trouble with a Marquee ProgressBar. I need to execute a method (refreshList()) to get a List<string>. Then I assign this List to a ComboBox, so ComboBox refreshes with the new Items. As refreshList() take 3 or 4 sec, I wanted to run a Marquee ProgressBar. But I couldn't. ProgressBar is ok, but ComboBox doesn't load new Items.
My refreshList() method:
private void refreshList(List<string> list)
{
albumList.DataSource = null;
albumList.DataSource = list;
}
I have the following code, it works fine:
private void changeDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
fbd.RootFolder = Environment.SpecialFolder.MyComputer;
folderPath = "";
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
folderPath = fbd.SelectedPath;
refreshList(N.getList(folderPath));
}
}
But I added a ProgressBar and wrote this code:
private void changeDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
fbd.RootFolder = Environment.SpecialFolder.MyComputer;
folderPath = "";
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
folderPath = fbd.SelectedPath;
bgWorker.WorkerReportsProgress = true;
bgWorker.RunWorkerAsync();
}
}
And I placed refreshList() in doWork() method:
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
refreshList(N.getList(folderPath));
}
But unfortunately this isn't working. Can anybody help me solving this problem? Thanks in advance.
You can use the MarqueeAnimationSpeed and Value properties of the ProgressBar control to stop and start the Marquee. There's no need to use WorkerReportsProgress* as you aren't incrementing a normal progress bar - you just want to "spin" the Marquee.
You can do something like the following:
public Form1()
{
InitializeComponent();
//Stop the progress bar to begin with
progressBar1.MarqueeAnimationSpeed = 0;
//If you wire up the event handler in the Designer, then you don't need
//the following line of code (the designer adds it to InitializeComponent)
//backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
}
private void changeDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
fbd.RootFolder = Environment.SpecialFolder.MyComputer;
folderPath = "";
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
folderPath = fbd.SelectedPath;
//This line effectively starts the progress bar
progressBar1.MarqueeAnimationSpeed = 10;
bgWorker.RunWorkerAsync(); //Calls the DoWork event
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
e.Result = N.getList(folderPath); //Technically this is the only work you need to do in the background
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//these two lines effectively stop the progress bar
progressBar1.Value = 0;
progressBar1.MarqueeAnimationSpeed = 0;
//Now update the list with the result from the work done on the background thread
RefreshList(e.Result as List<String>);
}
private void RefreshList(List<String> results)
{
albumList.DataSource = null; //You don't need this line but there is no real harm.
albumList.DataSource = list;
}
Remember to wire up the RunWorkerCompleted event to backgroundWorker1_RunWorkerCompleted via the Properties bar, Events section in the designer.
To begin with, we start the ProgressBar's animation by setting the MarqueeAnimationSpeed property to a non-zero positive number as part of your successful folder selection.
Then, after calling RunWorkerAsync, the code builds your list in the DoWork method, then assigns the result to the DoWorkEventArgs, which get passed to the RunWorkerCompleted event (which fires when DoWork is finished).
In the backgroundWorker1_RunWorkerCompleted method, we stop the progress bar (and set it's value to zero to effectively return it to it's original state), and then we pass the list to the refreshList method to databind it and populate the ComboBox.
Tested using VS2012, Windows Forms, .Net 4.0 (with a Thread.Sleep to emulate the time taken for N.getList)
*WorkerReportsProgress, and the associated ReportProgress method/event are used when you want to increment the progress bar - you can tell the GUI that you are 10% done, 20% done, 50% done etc etc.
I've been searching and found that a good way to perform background work and update the GUI is using background workers. However, doing this (stupid) little task (counting from 1 to 10000) it doesn't update the label content but prints to the debug! (This is just a spike solution for another project of course...)
Here's the code:
public partial class MainWindow : Window
{
BackgroundWorker bw = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.WorkerReportsProgress = true;
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync();
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("DONE");
}
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Content = "going here: "+e.ProgressPercentage;
Debug.WriteLine(e.ProgressPercentage);
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
for (int i=0; i < 10000; i++)
{
bw.ReportProgress((i*100)/10000);
}
}
}
The ProgressChanged event is raised on the UI thread, not the worker thread. In your code, the worker thread is doing almost nothing (just loop from 0 to 10000 and call ReportProgress), most of the work is done on the UI thread. Basically, you're sending too many progress notifications. Because of this, the UI thread is almost always busy and has no time to render the new content of the label.
Rendering in WPF is not performed immediately when you change a property of a control, it is done on a separate dispatcher frame, which is processed when the dispatcher has nothing more urgent to do, based on the priority of the task. The priority used for rendering has a value of 7 (DispatcherPriority.Render); the ProgressChanged event is marshalled to the UI thread with a priority of 9 (DispatcherPriority.Normal), as specified on MSDN. So the ProgressChanged notifications always have a higher priority than rendering, and since they keep coming, the dispatcher never has time to process the rendering tasks.
If you just decrease the frequency of the notifications, your app should work fine (currently you're sending 100 notifications for each percentage value, which is useless):
void bw_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 10000; i++)
{
if (i % 100 == 0)
bw.ReportProgress(i / 100);
}
}
this.Dispatcher.BeginInvoke( (Action) delegate(){
label1.Content = "going here: "+e.ProgressPercentage;
});
Try to change the label using womething like this:
string Text = "going here: " + e.ProgressPercentage;
this.Invoke((MethodInvoker)delegate {
label1.Content = newText;
});
Note that i'm not sure it will work. I can not test it now. If it does not work, let me know and I will delete the answer.
If you need the a canonical way to do exactly what you want, look at the Hath answer in this post: How do I update the GUI from another thread?
I have a grid in my application. After user selects some files in ofdialog application processes some calculations. While app is making calculations it looks like it is not responding. How to display some picture and make main window in black&white while calculating? Maybe make some dp in MainWindow a la "IsBusy" and bind a popup with picture to it?
How you implement this logic in yours apps?
One easy way is to use the busy indicator from Extended WPF Toolkit:
Dowload the binaries and add project reference to WPFToolkit.Extended.dll.
Next add following namespace in your 'main window':
xmlns:ext="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit.Extended"
Then add the busy indicator in the view (place it so that when shown, it will occupy the whole screen) Here my main window has two rows and I want the control to span on both rows. The control's IsBusy property is bound to a bool property in the view's data context.
<ext:BusyIndicator Grid.RowSpan="2" x:Name="busyIndicator" IsBusy="{Binding IsBusy}" />
The long lasting calculation should be processed in another thread so that it won't block the user interface. For threading you can use BackgroundWorker class.
You should have the long running tasks in a seperate thread to avoid UI blocking.
Here's one way you could achieve that:
Define background thread as below:
//Delegate that you could pass into the worker thread
public delegate void ProgressMonitor(string s);
//Call this to start background work
void StartLongRunningWork(ProgressMonitor mon)
{
using (BackgroundWorker bgw = new BackgroundWorker())
{
bgw.DoWork += WorkerThread;
bgw.RunWorkerCompleted += WorkerThreadCompleted;
bgw.RunWorkerAsync(mon);
}
}
void WorkerThread(object sender, DoWorkEventArgs e)
{
ProgressMonitor pm = (ProgressMonitor)e.Argument;
WorkerActual(pm, <any other parameters>);
}
void WorkerActual(ProgressMonitor pm,<any other parameters>)
{
...
pm("Doing x");
Do long running task
pm("Doing y");
...
}
//This function is called in case of Exception, Cancellation or successful completion
//of the background worker. Handle each event appropriately
void WorkerThreadCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
//Long running task threw an exception
}
else
if (e.Cancelled)
{
//Long running task was cancelled
}
else
{
//Long running task was successfuly completed
}
}
And Call it as below:
private void UpDateProgressLabel(string s)
{
this.Dispatcher.BeginInvoke((Action)delegate
{
NotificationLabel.Content = s;
});
}
private void Button_Click(object sender, RoutedEventArgs e)
{
StartLongRunningWork(UpDateProgressLabel);
}
I have an Accordion and the height of its content can be dynamically resized. I would like to see the Accordion dynamically respond to the child item's height, but I'm having trouble doing this.
<lt:Accordion Name="MyAccordion"
SelectionMode="ZeroOrOne"
HorizontalAlignment="Stretch">
<lt:AccordionItem Name="MyAccordionItem"
Header="MyAccordion"
IsSelected="True"
HorizontalContentAlignment="Stretch"
VerticalAlignment="Stretch">
<StackPanel>
<Button Content="Grow" Click="Grow"/>
<Button Content="Shrink" Click="Shrink"/>
<TextBox Name="GrowTextBox"
Text="GrowTextBox"
Height="400"
Background="Green"
SizeChanged="GrowTextBox_SizeChanged"/>
</StackPanel>
</lt:AccordionItem>
</lt:Accordion>
private void Grow(object sender, System.Windows.RoutedEventArgs e)
{
GrowTextBox.Height += 100;
}
private void Shrink(object sender, System.Windows.RoutedEventArgs e)
{
GrowTextBox.Height -= 100;
}
private void GrowTextBox_SizeChanged(object sender, System.Windows.SizeChangedEventArgs e)
{
MyAccordion.UpdateLayout();
MyAccordionItem.UpdateLayout();
}
Mind you, if I collapse and then re-open the accordion, it takes shape just the way I want, but I'd like this resizing to occur immediately when the child resizes.
I feebly attempted to fix this by adding a SizeChanged event handler that calls UpdateLayout() on the Accordion and AccordionItem, but this doesn't have any visual effect. I can't figure out where proper resizing takes place inside the Accordion control. Does anyone have an idea?
Try this one
//here i am creating a size object depending on child items height and width
// and 25 for accordian item header...
// if it works you can easily update the following code to avoid exceptional behaviour
Size size = new Size();
size.Width = GrowTextBox.ActualWidth;
size.Height = grow.ActualHeight + shrink.ActualHeight + GrowTextBox.ActualHeight + 25;
MyAccordion.Arrange(new Rect(size));
In the above code i am just rearranging accordion depending on child item size.
I have a similar problem, my simple hack is as follows:
private void GrowTextBox_SizeChanged(object sender, System.Windows.SizeChangedEventArgs e)
{
MyAccordionItem.Measure(new Size());
MyAccordionItem.UpdateLayout();
}
Hope it works for you too..
Cheers
I had a slightly different problem - resizing my window sometimes didn't correctly adjust the Accordion item size, so the header of the next item would be stuck below the window or in the middle of it.
I solved this by creating a timer that is started in SizeChanged, and that deselects and immediately reselects the current item, after which the layout seems to be readjusted and turns up correct. Might help you as well. You could dispense with the timer, I introduced it to prevent continuous calls when the user drag resizes the window, it also gives a kind of feathery effect because of the delay.
public partial class MyAccordion : System.Windows.Controls.Accordion
{
private Timer _layoutUpdateTimer = new Timer(100);
public MyAccordion
{
this.SizeChanged += (s, e) =>
{
_layoutUpdateTimer.Stop(); // prevents continuous calls
_layoutUpdateTimer.Start();
};
_layoutUpdateTimer.Elapsed += (s, e) => ReselectItem();
}
private void ReselectItem()
{
Application.Current.Dispatcher.BeginInvoke((Action)(() =>
{
// backup values
int selectedIndex = this.SelectedIndex;
AccordionSelectionMode mode = this.SelectionMode;
// deselect
this.SelectionMode = AccordionSelectionMode.ZeroOrOne; // allow null selection
this.SelectedItem = null;
// restore values (reselect)
this.SelectionMode = mode;
this.SelectedIndex = selectedIndex;
}));
_layoutUpdateTimer.Stop();
}
}