I would like to display a loading gif in WPF while I'm getting datas in database and querying theses.
I Have a MainWindow with 2 usercontrols and my image/loading gif.
I suppose I must use BackgroundWork but I don't manage to make it works.
Thank you.
Assume the following XAML, with one hidden Image and one Button that pretends to load data:
<StackPanel Margin="20">
<Image
x:Name="loadingGif"
Height="30"
Source="Assets/Square.bmp"
Visibility="Hidden"
VerticalAlignment="Top"
HorizontalAlignment="Left" />
<Button
Content="Load"
VerticalAlignment="Top"
Click="OnGoClick" />
</StackPanel>
In the button's Click event handler, you make the Image visible and start a BackgroundWorker. Once the BackgroundWorker is done, hide the Imageagain:
private void OnGoClick(object sender, RoutedEventArgs e)
{
loadingGif.Visibility = Visibility.Visible;
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += OnDoWork;
worker.RunWorkerCompleted += OnRunWorkerCompleted;
worker.RunWorkerAsync();
}
private void OnDoWork(object o, DoWorkEventArgs args)
{
Task.Delay(2000).Wait(); // Pretend to work
}
private void OnRunWorkerCompleted(object o, RunWorkerCompletedEventArgs args)
{
loadingGif.Visibility = Visibility.Hidden;
}
(You need to be careful about where you change the Image.Visibility property; this needs to happen on the UI thread.)
You can use the task for long running function(loading you data)
LoadGif();
await Task.Run(() => LoadYourData());
Related
I will simulate my problem by taking two buttons in a sample application. In my actual case I have a UserControl along with a tabcontrol. When a button is pressed on the UserControl I am trying to get the parent Window and disabling it and perform some work on the UI for 1 minute. After the UI is free I am enabling the parent window.
My problem is when the ParentWindow is disabled and when the user clicks on the tabcontrol, as long as the UI is busy it remains unresponsive. But as soon as the UI does its work, all the user input is being taken into account and it switches tabs by itself. I want to ignore user input when the UI is busy please help. Any suggestions?
In the sample application here is the xaml
<StackPanel Orientation="Vertical">
<Button Height="20" Width="180" Click="Button_Click"
>Hello</Button>
<Button Height="20" Width="180" Click="Button_Click_1">Do Nothing</Button>
</StackPanel>
In the xaml.cs file here is code. See that I am disabling the Window but when the button is pressed i get the hello message automatically after the UI is free.
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Hello");
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Window.GetWindow(this).IsEnabled=false;
Thread.Sleep(5000);
Window.GetWindow(this).IsEnabled=true;
}
just run your long-running-code in new thread
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Task.Run(() =>
{
try
{
this.Dispatcher.Invoke(() =>
{
Window.GetWindow(this).IsEnabled = false;
});
Thread.Sleep(5000);
}
finally
{
this.Dispatcher.Invoke(() =>
{
Window.GetWindow(this).IsEnabled = true;
});
}
});
}
expected result
I've struggled with this setup for couple of days and found a somewhat solution but I think it not how it supposed to work.
Here is my xaml page setup:
<Page
...
<SplitView IsPaneOpen="True" DisplayMode="Inline" OpenPaneLength="300">
<SplitView.Pane>
<Grid>
<ToggleButton x:Name="Edit" IsEnabled="False" Checked="Edit_Checked" Unchecked="Edit_Unchecked"/>
</Grid>
</SplitView.Pane>
<Frame x:Name="RightFrame">
</Frame>
</SplitView>
</Page>
Code for toggle button:
private void Edit_Checked(object sender, RoutedEventArgs e)
{
RightFrame.Navigate(typeof(SubPage1));
}
private void Edit_Unchecked(object sender, RoutedEventArgs e)
{
RightFrame.Navigate(typeof(SubPage2));
}
So basically the toggle button switches the splitview content. The SubPages are just blank pages. The problem is that app crashes when navigation occurs.
I've noticed when I put this before invoking navigation:
RightFrame.Content = null;
Thread.Sleep(1000);
Then everything works fine. So I need to clear frame content and wait for it to finish I guess. But I think it should be done automatically. Could some one explain what am I doing wrong here and how it should be done?
Please verify your code in SubPage1 and SubPage2 which might be creating the crashes, you can clear the resources when the page gets unloaded.
Alternatively, you can try below code:
private void Edit_Checked(object sender, RoutedEventArgs e)
{
RightFrame.Content = new SubPage1();
}
private void Edit_Unchecked(object sender, RoutedEventArgs e)
{
RightFrame.Content = new SubPage2();
}
I have a multi thread application with a background worker which I use to display a splash screen for processing the creation of the main window.
I want to update the progress bar in the thread 'u' in my program, so I will have to invoke the progressbar control each time I want to update it from the thread 'u'.
So that means I don't have to use "backgroundWorker_DoWork" especially.
The problem I have is that I can't display the mainwindow (form2) when "backgroundWorker_RunWorkerCompleted" event is called.
I think the problem is about the dispatcher.
public partial class App : Application
{
public BackgroundWorker backgroundWorker;
private SplashScreenWindow splashScreen;
public static EventWaitHandle initWaitHandle = new AutoResetEvent(false);
public MainWindow Form2 { get; set; }
[DllImport("kernel32.dll")]
private static extern bool AllocConsole();
protected override void OnStartup(StartupEventArgs e)
{
backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
splashScreen = new SplashScreenWindow();
splashScreen.ShowInTaskbar = false;
splashScreen.ResizeMode = ResizeMode.NoResize;
splashScreen.WindowStyle = WindowStyle.None;
splashScreen.Topmost = true;
splashScreen.Width = (SystemParameters.PrimaryScreenWidth) / 2.5;
splashScreen.Height = (SystemParameters.PrimaryScreenHeight) / 2.5;
splashScreen.Show();
base.OnStartup(e);
backgroundWorker.RunWorkerAsync();
Thread u = new Thread(new ThreadStart(interface_process));
u.SetApartmentState(ApartmentState.STA);
u.Start();
}
public void interface_process()
{
MainWindow form2 = new MainWindow();
this.Form2 = form2;
System.Windows.Threading.Dispatcher.Run();
}
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
splashScreen.Close();
this.Form2.Invoke((Action)delegate()
{
this.Form2.Show(); // does not work
System.Windows.Threading.Dispatcher.Run();
});
}
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
splashScreen.ValueProgressBar = e.ProgressPercentage;
}
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= 100; i += 10)
{
backgroundWorker.ReportProgress(i, "Chargement en cours : " + i);
Thread.Sleep(500);
}
}
}
It's not clear to me how the code you posted would even compile, as the WPF Window class does not have an Invoke() method on it. That would cause a compile-time error in the code here:
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
splashScreen.Close();
this.Form2.Invoke((Action)delegate()
{
this.Form2.Show(); // does not work
System.Windows.Threading.Dispatcher.Run();
});
}
If the above code is changed so that the second statement in the method reads this.Form2.Dispatcher.Invoke((Action)delegate() — that is, use the Dispatcher object that owns the Form2 object — not only should the code compile, but the call this.Form2.Show() should also work. Note that the second call to Dispatcher.Run() is not needed and in fact should be avoided.
The "correct" implementation of the method thus would look like this:
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
splashScreen.Close();
this.Form2.Dispatcher.Invoke((Action)delegate()
{
this.Form2.Show(); // works fine
});
}
Now, that said…it seems to me that the whole approach is flawed. You really should just have the one UI thread in your program. If you want something to happen first while a splash screen is shown, then make the splash screen window the first window that's shown, run the background task, and then show the main window in the same thread when it's done.
Here is an example of what you seem to be trying to do, but written to use just the main UI thread and the normal WPF startup mechanisms…
Let's assume we start with a plain WPF project in Visual Studio. This will have in it already an App class and a MainWindow class. We just need to edit it to do what you want.
First, we need a splash screen window. Most of the configuration can be done in XAML; because you want the width and height computed based on the screen size, it's easiest (for me) to just put that in the constructor. This winds up looking like this:
SplashScreenWindow.xaml:
<Window x:Class="TestSingleThreadSplashScreen.SplashScreenWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
ShowInTaskbar="False"
ResizeMode="NoResize"
WindowStyle="None"
Topmost="True"
Title="SplashScreenWindow">
<Grid>
<ProgressBar HorizontalAlignment="Left" Height="10" Margin="10,10,0,0"
VerticalAlignment="Top" Width="100"
Value="{Binding ValueProgressBar}"/>
</Grid>
</Window>
SplashScreenWindow.xaml.cs:
public partial class SplashScreenWindow : Window
{
public readonly static DependencyProperty ValueProgressBarProperty = DependencyProperty.Register(
"ValueProgressBar", typeof(double), typeof(SplashScreenWindow));
public double ValueProgressBar
{
get { return (double)GetValue(ValueProgressBarProperty); }
set { SetValue(ValueProgressBarProperty, value); }
}
public SplashScreenWindow()
{
InitializeComponent();
Width = SystemParameters.PrimaryScreenWidth / 2.5;
Height = SystemParameters.PrimaryScreenHeight / 2.5;
}
}
Now the above class is the one we want shown first. So we edit the App class's XAML to do that, by changing the StartupUri property:
<Application x:Class="TestSingleThreadSplashScreen.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="SplashScreenWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
Finally, we need our App class to run the BackgroundWorker, doing the appropriate things at various times:
public partial class App : Application
{
private SplashScreenWindow SplashScreen { get { return (SplashScreenWindow)this.MainWindow; } }
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
BackgroundWorker backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork += backgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
backgroundWorker.RunWorkerAsync();
}
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
new MainWindow().Show();
SplashScreen.Close();
}
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
SplashScreen.ValueProgressBar = e.ProgressPercentage;
}
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker backgroundWorker = (BackgroundWorker)sender;
for (int i = 0; i <= 100; i += 10)
{
backgroundWorker.ReportProgress(i, "Chargement en cours : " + i);
Thread.Sleep(500);
}
}
}
The only tricky thing in there is that the splash screen window must be closed after you show the main window. Otherwise, WPF will think you've closed the last window (well, technically you would have :) ) and will shut down the program. By showing the main window before closing the splash screen window, the program continues to run.
In my opinion, this is a much better way to do things, as it works with the normal WPF mechanisms, rather than trying to subvert and/or work around them. It takes advantage of the Dispatcher that is created automatically when the program starts, doesn't require an extra UI thread, etc. Oh, and…it works. So there's that. :)
I have roblem with timer and AutocompleteBox toolkit.
I have AutoCompletebox with TextChanged Event. On this event I start timer to make filtering smoother (when user writes, filter don't work).
When I writes everything works ok, but when I choose from DropDown list TextChanged event starts, but I don't have timer Tick event (filter don't starts). What I do wrong?
xaml:
<Controls:AutoCompleteBox Name="acbIdentyfikatorPcS" ValueMemberPath="Identyfikator" FilterMode="Contains" HorizontalAlignment="Left" Margin="100,5,0,0" Grid.Row="1" VerticalAlignment="Top" Width="121" ToolTip="Identyfikator" MinimumPrefixLength="0" TextChanged="acbSerwisant_TextChanged" IsTextCompletionEnabled="True">
<Controls:AutoCompleteBox.ItemTemplate>
<DataTemplate>
<TextBlock Margin="0,0,10,0" FontWeight="Bold" Text="{Binding Identyfikator}"></TextBlock>
</DataTemplate>
</Controls:AutoCompleteBox.ItemTemplate>
</Controls:AutoCompleteBox>
CS:
Declaration:
private DispatcherTimer timerfiltr = new DispatcherTimer();
private DataTable PcS;
public MainWindow()
{
InitializeComponent();
timerfiltr.Tick += new EventHandler(timerfiltr_Tick);
timerfiltr.Interval = new TimeSpan(0, 0, 0, 0, 400);
}
private void acbSerwisant_TextChanged(object sender, RoutedEventArgs e)
{
timerfiltr.Stop();
timerfiltr.IsEnabled = true;
timerfiltr.Start();
}
private void timerfiltr_Tick(object sender, EventArgs e)
{
PcS.DefaultView.RowFilter = "Identyfikator like '%" + acbIdentyfikatorPcS.Text + "%'";
timerfiltr.Stop();
timerfiltr.IsEnabled = false;
}
EDIT:
I think I found my problem:
This AutoCompleteBox is on one of the TabItem, I have also TabControl SelectionChanged event, where is also timer.stop() command
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//rest of code
// timerfiltr.Stop();
}
When I press key down on keyboard (AutoCompleteBox is focused), DropDown opens, and then also TabControl SelectionChanged event also starts and stops timer...
Weird
You can use the IsDropDownOpen property (or DropDownXXX events) of the AutoCompleteBox to figure out whether the DropDown is currently visible and one quick solution is to avoid stopping your timer inside its Tick method for example.
I didn't test your sample code, I think maybe *acbSerwisant_TextChanged* stop the dispatcher thread.
For this scenario, I suggest you do change your code like this, maybe not best, but should work, and there is framework is more suitable for dealing this which is called "RX"(https://rx.codeplex.com), method Throttle is what your want.
**Update: if you worry about perf, add flag as switch,since all code run under ui thread, this guarantee you that last text change will be accepted.But reality is a bit complex, normally you will do you filter in another thread...if that happened,i suggest you use other thread with while statement to check textchange, or you can take look at RX.
private bool enableFilter;
public MainWindow()
{
InitializeComponent();
timerfiltr.Tick += new EventHandler(timerfiltr_Tick);
timerfiltr.Interval = new TimeSpan(0, 0, 0, 0, 400);
acbIdentyfikatorPcS.GotFocus +=(s,e)=>{timerfiltr.Start();};
acbIdentyfikatorPcS.LostFocus +=(s,e)=>{timerfiltr.Stop();};
acbIdentyfikatorPcS.TextChanged +=(s,e)=>{enableFilter= true;};
}
private void timerfiltr_Tick(object sender, EventArgs e)
{
if(enableFilter)
{
enableFilter= false;
//do filter
}
}
I can't get this straight: I have one button that I want one action for the press event and one action for release, I've searched everywhere and can't find an answer.
KeyDown, KeyUp or MouseLeftButtonDown doesn't work with button on windows phone 7.
First I tried combining GotFocus and Click clickmode release like this:
(As you can see I want Image1 to be shown while pressing button, and hidden when releasing the button)
xaml:
Button Click="button1_Click" ClickMode="Release" GotFocus="button1_GotFocus" Content="byt" Height="72" Margin="0,500,6,0" Name="button1" VerticalAlignment="Top"
private void button1_GotFocus(object sender, RoutedEventArgs e)
{
Image1.Visibility = System.Windows.Visibility.Collapsed;
}
private void button1_Click (object sender, RoutedEventArgs e)
{
Image1.Visibility = System.Windows.Visibility.Visible;
}
This works only one time and could work all the time if I could loose focus from the button when releasing it (tried searching for that as well)
The other thing I tried was changing the clickmode while pressing the button, but didn't get that to work either..
something like this:
private void button1_Click (object sender, RoutedEventArgs e)
{
Image1.Visibility = System.Windows.Visibility.Collapsed;
button1.SetValue(Button.ClickModeProperty, ClickMode.Release);
Image1.Visibility = System.Windows.Visibility.Visible;
}
(I know that the syntax is wrong somehow in the second one)
Would be grateful for help!
MouseLeftButtonDown / MouseLeftButtonUp do work on WP7. Obviously not named the best, but they do work on the device.
<TextBlock x:Name="ApplicationTitle" MouseLeftButtonDown="ApplicationTitle_MouseLeftButtonDown" MouseLeftButtonUp="ApplicationTitle_MouseLeftButtonUp"
Style="{StaticResource PhoneTextNormalStyle}"
Text="MY APPLICATION" />
You'll see Down gets fired, and then immediately Up.
private void ApplicationTitle_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
}
private void ApplicationTitle_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
}
Perhaps all you need is MouseLeftButtonDown/Up.
I used
Click="{x:Bind UpArrow_Click}"
ClickMode="Press"
PointerCaptureLost="Button_Release"
on a button. When button is pressed UpArrow_Click event occurs... when button is released Button_Release event occurs