I have a wpf application which has a main window and menu. This main window has a panel, and on clicking the menu item i create an instance of the user control and load the panel with the control.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="" MinHeight="750" Height="Auto" MinWidth="1100" Width="Auto" WindowState="Maximized" ScrollViewer.VerticalScrollBarVisibility="Auto"
Loaded ="MainWindow_OnLoaded" Closing="Window_Closing">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility ="Auto" SizeChanged="ScrollViewer_SizeChanged">
<Grid Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="38"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Height="38" Width="Auto" Background="#09527B">
<Grid Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="70"></ColumnDefinition>
</Grid.ColumnDefinitions>
</Grid>
</StackPanel>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="189"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<StackPanel>
<Expander Name="test" Header="Admin" Foreground="White" Margin="0,10,0,0">
<StackPanel Margin="20,0,0,0">
<Expander Header="Data" Foreground="White">
<StackPanel>
<TextBlock Text="Add/Edit UC1" Foreground="White" Margin="30,5,0,0" MouseDown="OpenUC1_MouseDown" MouseEnter="TextBlock_MouseEnter" MouseLeave="TextBlock_MouseLeave"/>
<TextBlock Text="Add/Edit UC2" Name="tbxBuild" Foreground="White" Margin="30,5,0,0" MouseDown="OpenUC2_MouseDown" MouseEnter="TextBlock_MouseEnter" MouseLeave="TextBlock_MouseLeave"/>
</StackPanel>
</Expander>
</StackPanel>
</Grid>
<StackPanel Grid.Column="1">
<Grid Name="pnlMain" Height ="Auto" VerticalAlignment="Top" HorizontalAlignment="Left">
</Grid>
</StackPanel>
</Grid>
</Grid>
</ScrollViewer>
</Window>
MainWindow.cs
private void OpenUC1_MouseDown(object sender, MouseButtonEventArgs e)
{
for (int i = 0; i < pnlMain.Children.Count; i++ )
{
pnlMain.Children.Remove(pnlMain.Children[i]);
}
using (UC2 _uc2= new UC2())
{
pnlMain.Children.Add(_uc2);
}
}
private void OpenUC2_MouseDown(object sender, MouseButtonEventArgs e)
{
for (int i = 0; i < pnlMain.Children.Count; i++ )
{
pnlMain.Children.Remove(pnlMain.Children[i]);
}
using (UC1 _uc1= new UC1())
{
pnlMain.Children.Add(_uc1);
}
}
My question is when I remove the control(UC1) from the main panel, when will that control be disposed?
Both the user control(UC1 and UC2) has the same view model attached to its data context. So i find that some of the methods in the removed user control(UC1) is called even though that is removed from the panel. The reason being, when a new instance of UC2 is created, there are some changes in the data model which in effect calls the dependent methods in UC1.
But if UC1 had been disposed this wouldn't happen. How can I make sure UC1 is disposed before instance of UC2 is created?
public UC1()
{
InitializeComponent();
this.DataContext = App.ViewModel.TestViewModel;
}
private void UC1_Unloaded(object sender, RoutedEventArgs e)
{
this.DataContext = null;
}
public UC2()
{
InitializeComponent();
this.DataContext = App.ViewModel.TestViewModel;
}
private void UC2_Unloaded(object sender, RoutedEventArgs e)
{
this.DataContext = null;
}
The unloaded method is not called immediately when the control is removed from the panel.
When I write and test code to dynamically add and remove a UserControl object from a window's visual tree, I find that the Unloaded event is raised just as expected.
In your own code example, there is at least one serious problem, and two incongruities:
The serious problem is how you are removing children. Your for loop is iterating by index through the children of the pnlMain object (a Grid). But removing any child invalidates the sequence of indexes! That is, the loop will first remove the child at index 0; this causes the child at index 1 to now become the child at index 0. But the loop increments the index before continuing, and will next remove the child at index 1. This child was originally at index 2. The code skips every other child (i.e. the ones originally at odd-numbered indexes), leaving half of them attached as children of the Grid.
Incongruity #1: I would expect a method with the phrase "OpenUC1" in the name to add an instance of UC1. However, your OpenUC1_MouseDown() method seems to be adding an instance of UC2 (and vice a versa for OpenUC2_MouseDown()). At the very least, there should be a comment in the code explaining why the code is different from what one might expect given the name of the method.
Incongruity #2: there is a using statement around the call to Add() when adding the UserControl objects. First, UserControl itself does not implement IDisposable, so unless your types have implemented that interface, that code is not even legal. Second, even if your UserControl subclasses do implement that interface, it does not seem like a very good idea to me to dispose an object that you've just created and which you are retaining in the visual tree (i.e. by adding it to the Grid's children).
Unfortunately, as I mentioned in my comment, without a good, minimal, complete code example that reliably reproduces your problem, it is impossible to say why your code does not behave as one would hope and/or expect it to. It is possible that any of the above points (but especially #1) are the cause of the behavior you're seeing, but I have no way to know for sure.
If after addressing those issues (or determining somehow that they are not problems…though if you can legitimately do that, I would argue that the code is still defective, in the sense that it's poor design), you find that your problem still exists, please edit your question so that it includes a good, minimal, complete code example that reliably reproduces the problem.
In the meantime, here is a simple code example that illustrates the basic behavior of the Unloaded event being raised just as expected when the object is removed from the visual tree. Note that while the correct way to remove all children from the Grid object's Children collection is to simply call the Clear() method (e.g. pnlMain.Children.Clear()), I have included an example of a explicit loop-based approach that does work.
XAML:
UserControl1.xaml
<UserControl x:Class="TestSO33289488UserControlUnloaded.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Unloaded="UserControl_Unloaded"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="UserControl" FontSize="36"/>
</Grid>
</UserControl>
MainWindow.xaml
<Window x:Class="TestSO33289488UserControlUnloaded.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button x:Name="button1" Content="Add UserControl"
HorizontalAlignment="Left" Click="Button_Click"/>
<Grid x:Name="grid1"/>
</StackPanel>
</Window>
C#:
UserControl1.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace TestSO33289488UserControlUnloaded
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
{
MessageBox.Show("UserControl.Unloaded was raised");
}
}
}
MainWindow.xaml.cs
using System.Windows;
namespace TestSO33289488UserControlUnloaded
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private bool _removeUserControl;
private void Button_Click(object sender, RoutedEventArgs e)
{
if (_removeUserControl)
{
//grid1.Children.Clear();
// Calling Clear() is better, but if you really want to loop,
// it is possible to do correctly. For example:
while (grid1.Children.Count > 0)
{
grid1.Children.RemoveAt(grid1.Children.Count - 1);
}
button1.Content = "Add UserControl";
}
else
{
grid1.Children.Add(new UserControl1());
button1.Content = "Remove UserControl";
}
_removeUserControl = !_removeUserControl;
}
}
}
Quote from an MSDN forum entry about Loaded/Unloaded events:
The events are raised asynchronously, so there might be some delay
between the action that causes the event and the event itself. The
events are effectively put into a list and a task is added to the
dispatcher's queue. When that task runs, it raises the events on the
list.
So the answer is you can't predict when exactly these events will raised and you shouldn't expect that they will be called immediately after you removed a control from it's parent.
It's kinda difficult to give you a proper solution without seeing the full project, but here's a quick and dirty solution: rather than making sure that the given user controls' events are fired in time let's check the Parent property of the UC1/UC2 object before running the method. If the property is null then the UC1/UC2 object was removed and you should not execute that method.
But let me point out some problems with this code:
What's the point of the using block in the MouseDown event handlers? You create a user control object, add it to the panel and then immediately after that you call the Dispose method on it? (that's what the using block does in C#)
You don't need a for loop to remove all the children elements from a Panel control like a Grid. You can do that in one line. pnlMain.Children.Clear();
Related
I have spend a little over a Day on this problem and i am absolutely Clueless.
If i click the button to show the Second View it Opens, but without Content.
I even get by a breakpoint in the View Model.
For this i have reduced everything to a Simple Textbox and Textblock that shut display the same Data, but they do not. They show nothing even after Typing into the Box the Block does not update.
But what ever i try the Databinding does not Work. Does anyone has an Idea?
Thanks in Advance
My second View
<Window x:Class="AoE4_BO_Overlay.Views.EditorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:AoE4_BO_Overlay.Views" xmlns:viewmodels="clr-namespace:AoE4_BO_Overlay.ViewModels" d:DataContext="{d:DesignInstance Type=viewmodels:EditorViewModel}"
mc:Ignorable="d"
Title="EditorView" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Path=FirstName}" Grid.Column="0" Grid.Row="2"/>
<TextBox Text="{Binding Path=FirstName , Mode=OneWay}" Grid.Column="0" Grid.Row="1"/>
</Grid>
My ViewModel
internal class EditorViewModel : Conductor<object>
{
private string _firstName = "Tom";
public EditorViewModel()
{
}
public string FirstName
{
get
{
return _firstName;
}
set
{
_firstName = value;
NotifyOfPropertyChange(() => FirstName);
}
}
}
How i call both of them
public void CreateBO_Click(object sender, RoutedEventArgs e)
{
EditorView createBO = new EditorView();
ActivateItemAsync(new EditorViewModel());
createBO.Show();
}
added Information
public partial class EditorView : Window
{
public EditorView()
{
DataContext = new EditorViewModel();
InitializeComponent();
}
}
You have two separate issues:
1. Typing into the textbox doesn't change the bound property:
This is expected, since you use OneWay binding explicitly. OneWay binding means the property updates the user interface, but not the other way around. So changing FirstName should update the TextBox, but changing the TextBox doesn't update FirstName.
Interestingly enough, if you just omit the Mode = OneWay part, it should work - since TextBoxes should use TwoWay binding by default. I recommend you define your TextBox binding explicitly as Mode = TwoWay
2. Your view initializes with an empty TextBlock / TextBox
This one is harder to pin down, since you don't show us where you set your DataContext. This usually happens to me when I set the DataContext AFTER InitializeComponent(), instead of before. You either set the DataContext before the binding is initialized (as part of InizializeComponent()), or you have to raise a NotifyPropertyChanged on your property to update the UI afterwards.
If this is not the cause, you might want to enable WPF binding errors in your output console - that usually gives you a good idea of where your bindings fail. Visual Studio has an option for that. It should be located here:
Tools -> Options -> Debugging -> Output Window -> WPF Trace Settings
-> Data Binding -> All
I believe what you are attempting here is to show your second View (EditorView) within the first one (and not as a pop-up - if you intend to have it as popup, use WindowManager instead of ActivateItemAsync).
One thing you need to change for making this possible is to ensure your second View is a UserControl and not a Window.
// EditorView.xaml.cs
public partial class EditorView : UserControl
// EditView.xaml
<UserControl x:Class="AoE4_BO_Overlay.Views.EditorView"
Also since your using the ActivateItemAsync, you would need to ensure that your FirstView contains a ContendControl with Name "ActiveItem".
// FirstView.xaml
<ContentControl x:Name="ActiveItem"/>
The call to ActivateItemAsync would use this control to load the View of your second ViewModel (EditorViewModel). With this in place, you could now use the ActivateItemAsync method to load the View.
public async Task CreateBO_Click(object sender, RoutedEventArgs e)
{
await ActivateItemAsync(new EditorViewModel());
}
Please note that method ActivateItemAsync supports asynchronous calls and it would be wise to call the method asynchronously.
Another point to note is that you do not need to specify the DataContext explicitly as seen in the OP if you are using Caliburn Micro and the View/ViewModels are stored in the recommended folder/namespaces structures. Caliburn Micro uses naming conventions to associate the appropriate view-viewmodel pairs. More information on the same could be found in the official documentation
I have the following code in a Windows 8.1 Store App. This code runs perfectly fine on Windows 10 but crashes on Windows 8.1. The second named control in MainPage.xaml.cs is null on Win 8.1 but not on Windows 10. It's not a timing issue as the named control still won't be populated in any subsequent event handler following the page load. What on earth is going on here?
To summarize, I have a ContentControl with a ContentPresenter defined in its Template. That ContentControl is then instantiated on a page, with a named child control (using "x:Name") as its Content. On Windows 10, that named control exists in code-behind. On Windows 8.1 it is null
MyUserControl1.xaml
<ContentControl
x:Class="App1.MyUserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<ContentControl.Template>
<ControlTemplate>
<ContentPresenter Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
</ContentControl.Template>
MainPage.xaml
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="TextBlock1"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextAlignment="Center"
FontSize="50"
TextWrapping="Wrap" />
<local:MyUserControl1 Grid.Column="1">
<TextBlock x:Name="TextBlock2"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextAlignment="Center"
FontSize="50"
TextWrapping="Wrap" />
</local:MyUserControl1>
</Grid>
MainPage.xaml.cs
using Windows.UI.Xaml.Controls;
namespace App1
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
TextBlock1.Text = "This works";
TextBlock2.Text = "This does not work because TextBlock2 is null";
}
}
}
Of course you cannot reference this. Your TextBlock2 is explicitly being set BY YOU to be the content of another control fundamentally out of scope. After rendering is complete, your TextBlock2 is no longer a child of your MainPage but instead a child of the ControlTemplate in your UserControl. Windows 10 is behaving EXACTLY how it should, and it appears you have discovered a bug in the Windows 8 rendering engine, if it worked.
One
There are a few workarounds. The first is the textbook approach of adding a property to your UserControl that adds access to this control. Because you are allowing the content to be dynamic, the operation inside that property (or method) would also need to be dynamic. Something like GetControl<TextBlock>("TextBlock1") which could hunt for you.
public bool TryGetControl<T>(string name, out T control)
{
try
{
var children = RecurseChildren(this.MyUserControl);
control = children
.Where(x => Equals(x.Name, name))
.OfType<T>()
.First();
return true;
}
catch
{
control = default(T);
return false;
}
}
public List<Control> RecurseChildren(DependencyObject parent)
{
var list = new List<Control>();
var count = VisualTreeHelper.GetChildrenCount(parent);
var children = Enumerable.Range(0, count - 1)
.Select(x => VisualTreeHelper.GetChild(parent, x));
list.AddRange(children.OfType<Control>());
foreach (var child in children)
{
list.AddRange(RecurseChildren(child));
}
return list;
}
Two
The second thing you could do is simply hunt for the control through the child hierarchy of the UserControl from the page itself. The logic would be the same as number one (above) but it would execute inside the page and not be part of your UserControl logic. You already do this sort of thing when you need to find the ScrollViewer in a ListView or maybe the inner Border in a Button for some reason.
It turns out I have already explained this in a blog article you can use as reference: http://blog.jerrynixon.com/2012/09/how-to-access-named-control-inside-xaml.html
Three
And here's a third, perhaps simplest way to do it. Handle the Loaded event of TextBlock1 in your MainPage and set a field to the value of the sender in the handler method. You can even cast it to TextBlock so everything is typed. This gives you simple access, but has the potential downside of timing. If you try to access the field value before it is set, you might find it is null. But, in most cases, this works the easiest.
So, that's three ways to handle it. I think it is very important that you recognize that this is EXPECTED behavior since a XAML element can have only one parent and you are setting that parent through the Content property of your ContentPresenter. That being said, it might be expected behavior, but it is not obviously intuitive.
Best of luck.
I have a XAML main window that contains a header, a central area and a footer (in a grid). The central area contains a ContentControl which is set throw a binding (using MVVMLight). The header/footer is always the same so no problems there.
The part that goes into the ContentControl is always quite similar, they are WPF usercontrols and have a left part that contains info and a right part with at least an OK and BACK button.
These are viewmodels and their views:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<TextBlock Text="this changes and contains other controls too" />
</Grid>
<Grid Grid.Column="1">
<!-- more buttons and statuses-->
<Button Content="Back" Margin="5" Height="30" />
<Button Content="Ok" Margin="5" Height="30" />
</Grid>
</Grid>
Is there a way i could create a base class/custom control for those views? So that I could write something like this in my xaml:
<basewindow>
<leftpart>
custom XAML for this view
</leftpart>
<rightpart>
custom XAML for this view
</rightpart>
</basewindow>
I could then remove duplicate code that is now in each of those views to the base class while still keeping the ability to write my xaml in the editor. Or is this not feasible?
To clarify are you trying to inherit the visual element that exist in XAML, like you can do in WinForms? If so you cannot do this in WPF. There is no Visual inheritence in WPF.
Now if you aren't trying to inherit visual element it is easy. First create your UserControlBase class and add you event handler. Keep in mind this base class can not have any XAML associated with it. Code only
public class MyUserControlBase : UserControl
{
public MyUserControlBase()
{
}
protected virtual void Button_Click(object sender, RoutedEventArgs e)
{
}
}
Now create another UserControl that does have a xaml counter part. Now you will need to change the root elemtn in the XAML to your base class like this:
<local:MyUserControlBase x:Class="WpfApplication7.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication7">
<Grid>
<Button Click="Button_Click">My Button</Button>
</Grid>
</local:MyUserControlBase>
And don't forget the code behind:
public partial class MyUserControl : MyUserControlBase
{
public MyUserControl()
{
InitializeComponent();
}
}
Notice the button in the derived user control is calling the Button_Click event handler we defined in the base class. That is all you need to do.
I have spent ages experimenting and googling but have thus far come up with no answer
I have a WPF list view that is using a WrapPanel as its items container.
For various reasons I dont want to virtualise the wrap panel.
Is it possible to show the items being added one by as they are completed in the value converter? so that the user is not looking at a blank screen while the items are loading?
This is a very common scenario, so I'm surprised that you didn't find anything during your search. Perhaps if you would have used the word 'Asynchronous', you would have had more luck. Either way, the answer is to use the Dispatcher class.
In short, you can add tasks to the message queue of the UI Dispatcher object, so that it can run them asynchronously. There are many examples of this online, but here is one that I found in the Load the Items in a ListBox Asynchronously page of the Java2s website:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WPF" Height="200" Width="300"
Loaded="Window_Loaded">
<Window.Resources>
<DataTemplate x:Key="ListItemTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ListBox x:Name="listBox" ItemTemplate= "{StaticResource ListItemTemplate}"/>
</StackPanel>
</Window>
//File:Window.xaml.cs
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication1
{
public partial class Window1 : Window
{
private ObservableCollection<string> numberDescriptions;
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
numberDescriptions = new ObservableCollection<string>();
listBox.ItemsSource = numberDescriptions;
this.Dispatcher.BeginInvoke(DispatcherPriority.Background,new LoadNumberDelegate(LoadNumber), 1);
}
private delegate void LoadNumberDelegate(int number);
private void LoadNumber(int number)
{
numberDescriptions.Add("Number " + number.ToString());
this.Dispatcher.BeginInvoke(DispatcherPriority.Background,new LoadNumberDelegate(LoadNumber), ++number);
}
}
}
I apologize if this is a duplicate, but I have not been able to find a question with a similar situation. If this is a duplicate please provide a link.
I would like to show a "Loading..." overlay in my WPF application, when I am dynamically creating a lot of tabs. The overlay visibility is bound to a property called "ShowIsLoadingOverlay". However, the overlay is never shown.
Due to the fact that the tabs are visual elements I can't move the creation into a BackgroundWorker.
I have created a small prototype trying to explain the situation. This is the xaml:
<Window x:Class="WpfApplication5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label HorizontalAlignment="Center" VerticalAlignment="Center"
Visibility="{Binding ShowIsLoadingOverlay, Converter={StaticResource BooleanToVisibilityConverter}}"
Content="Loading..." />
<Button Grid.Row="1" Content="Load" Click="Button_Click" />
</Grid>
</Window>
And this is the code behind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private bool m_ShowIsLoadingOverlay;
public bool ShowIsLoadingOverlay
{
get
{
return m_ShowIsLoadingOverlay;
}
set
{
if ( m_ShowIsLoadingOverlay == value )
{
return;
}
m_ShowIsLoadingOverlay = value;
NotifyPropertyChanged( "ShowIsLoadingOverlay" );
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Button_Click( object sender, RoutedEventArgs e )
{
ShowIsLoadingOverlay = true;
CreateTabs();
ShowIsLoadingOverlay = false;
}
private void CreateTabs()
{
// Simulate long running process to create tabs
Thread.Sleep( 3000 );
}
// Implementation of INotifyPropertyChanged has been left out.
}
The problem is that the overlay is never shown. I know that it has something to do with the UI not updated correctly before and after the ShowIsLoadingOverlay property has changed. And I believe it also has something to do with the lack of using the dispatcher.
I have tried many, many combinations of Dispatcher.Invoke, Dispatcher.BeginInvoke surrounding when changing the property and/or surrounding the CreateTabs call. And I have tried changing the DispatcherPriority to "force" the overlay to show before starting to create the tabs. But I just can't make it work...
Could you please tell me how to accomplish this task? And more importantly; provide an explanation, because I do not get this.
In advance,
thank you.
Best regards,
Casper Korshøj
You cannot manipulate UI controls in a background thread. If you are using the main UI thread to create your TabItems, then you also cannot have a 'Busy' or 'Loading' indicator... this will only work if you are using a alternative thread for your long running process. This is because your 'Busy' indicator will only become updated once the long running process has completed if it runs on the same UI thread.