WPF Top Level Window and Child Window on different Dispatchers? - wpf

OK, here's what I am trying to do. I have a nasty WPF control (that I am forced to use) that requires a parent with an HWND (because it contains some Windows.Forms stuff) and can be very unresponsive at times. This causes my WPF app to also get bogged down and become unresponsive (less responsive would be more accurate). I would like to move this control onto its own Dispatcher so that my app's Dispatcher isn't tied up when the control becomes unresponsive.
I have tried to create a Window derived class to host the Control. I launch it on an STA thread and do a Dispatcher.Run(). So far all is good. The badness happens when I try to add that Window derived class to the topmost Grid inside my app. I get the "The calling thread cannot access this object because another thread owns it." exception.
XAML for my app:
<Window
x:Class="MyAppWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid
ClipToBounds="True"
x:Name="mainGrid"
Grid.Row="1">
</Grid>
</Window>
myApp.cs
public class MyAppWindow :Window
{
CrapControlWindow crapControlWindow;
Dispatcher crapControlWindowDispatcher;
Dispatcher myAppDispatcher;
public MyAppWindow()
{
myAppDispatcher = Dispatcher.CurrentDispatcher;
Thread launchThread = new Thread(new ThreadStart(crapControlHostLauncher));
launchThread.IsBackground = true;
launchThread.ApartmentState = ApartmentState.STA;
launchThread.Start();
}
public Initialize()
{
mainGrid.Children.Add(crapControlWindow); <--- Fails Here
}
void crapControlHostLauncher()
{
crapControlWindowDispatcher = Dispatcher.CurrentDispatcher;
Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate()
{
crapControlWindow = new crapControlWindow();
});
Dispatcher.Run();
}
}
XAML for child window:
<Window x:Class="CrapControlWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Crappy Control Window" Height="300" Width="300">
<Grid x:Name="mainGrid">
<CrapControl
Grid.Row="1"
Name="crapControl1"
ClipToBounds="True"
Margin="0,4,0,4" />
</Grid>
</Window>
I've tried various permutations of the failing line - crapControlWindowDispatcher.Invoke, myAppDispatcher.Invoke etc. NADA! Same exception...
So lay it on me - am I crazy? other strategies? Can I make this work?
Thanks for your help!

You've inherited from Window but are then attempting to host that Window within a Grid. That makes no sense whether it has the same Dispatcher or not, since a Window is a top-level element. You can have separate windows, each with their own Dispatcher, but they need to be displayed as separate windows. I know of no way to integrate a control with its own Dispatcher into another WPF control with a different Dispatcher.
For an example of separate windows, each with their own dispatcher, see here.

Related

Xaml: design a layout with dynamically visible components

In a mvvm application some areas inside a window (in reality it is a UserControl inside MainWindow) are dynamically displayed according to the user selections.
The changing blocks are inside Stackpanels, I have 4 of them and only one at a time is displayed. This is accomplished binding Visibility to a bool property and using the BooleanToVisibilityConverter.
I put all the alternate StackPanel inside parent control. It works correctly, but during design phase in Visual Studio I see all of them, so I have problems in figuring the final layout.
How can I easily create the layout having more controls which share the same window area and are displayed one at a time ?
Setting A Design Time Only Data Context
Developing XAML in the Studio Designer can be greatly simplified by setting the Design-Time Data Context.
One implementation is based on setting a duplicate DataContext which will be ignored during the final compilation.
To implement the switching, add to the ViewModel, a property that will inform the designer whether it can be used in Development Mode or not.
I use an MVVMLight situation for this example, but for this declared instance property IsInDesignMode and static property ViewModelBase.IsInDesignModeStatic.
Example:
using System.ComponentModel;
namespace DataContextDesignTime.Example
{
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _flag;
public bool Flag
{
get => _flag;
set
{
if (!Equals(_flag, value))
{
_flag = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Flag)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NotFlag)));
}
}
}
public bool NotFlag => !Flag;
}
}
<Window x:Class="DataContextDesignTime.Example.ExamleWindow"
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:DataContextDesignTime.Example"
mc:Ignorable="d"
Title="ExamleWindow" Height="450" Width="800">
<d:Window.DataContext>
<local:MyViewModel Flag="True" NotFlag="True"/>
</d:Window.DataContext>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<StackPanel>
<Border Background="LightBlue" Height="200"
Visibility="{Binding Flag, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Border Background="LightGreen" Height="400"
Visibility="{Binding NotFlag, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</StackPanel>
</Window>
In this example, you can change property values in XAML or in the Property Browser.
And you will immediately see the work of your bindings, triggers, how the display for certain data changes.
Note
This may fail on more complex VMs/packages, but in general by setting the DataContext at design time is not difficult.
I need to recompile the project to see the changes in the properties.
The XAML Designer panel has an «Enable/Disable Project Code» button.
, but during design phase in Visual Studio I see all of them, so I have problems in figuring the final layout.
This problem is easily resolved by bringing up the Document Outline tab in visual studio. Once open, navigate to the visible tree and toggle the eyeball to visibly hide/unhide the control[s] one is not interested in; during design time only.

Keeping page code in separate files

I have a main WPF Window with a Menu Bar and a Status Bar. I want this window to act as MDI Window. I want to define the entire area from below the Menu Bar to just above the Status Bar, where I will be showing other Windows. I want to define a Page or Panel like thing. When I add a new WPF Form, I want it to appear on this location contained within the Main Window. How to do this?
I've used http://wpfmdi.codeplex.com/ in basic prototype apps. Create your content as user controls, which are then wrapped in a window.
I've never used it with the toolbox, though.
Simple version:
<Window x:Class="WpfApplication1.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">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="File"/>
<MenuItem Header="Edit"/>
<MenuItem Header="Help"/>
</Menu>
<StatusBar DockPanel.Dock="Bottom"/>
<ContentControl Name="_content"/>
</DockPanel>
</Window>
And code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public object MainContentGoesHere
{
get { return _content.Content; }
set { _content.Content = value; }
}
}
Then in your code just have a reference to a MainWindow - and use the MainContentGoesHere property to inject the views. Just remember to use UserControls, Panels or ContentControl instead of Window, when you inject them as Window cannot have a parent.
If you want something more fancy - take a look at Prism on CodePlex. It might be overkill for you, but it is actually a well-thought out and lightweight framework.
EDIT: Fixed the order of Children in the DockPanel.

How to display a busy message over a wpf screen

Hey,
I have a WPF application based on Prism4. When performing slow operations, I want to show a busy screen. I will have a large number of screens, so I'm trying to build a single solution into the framework rather than adding the busy indicator to each screen.
These long running operations run in a background thread. This allows the UI to be updated (good) but does not stop the user from using the UI (bad). What I'd like to do is overlay a control with a spinning dial sort of thing and have that control cover the entire screen (the old HTML trick with DIVs). When the app is busy, the control would display thus block any further interaction as well as showing the spinny thing.
To set this up, I thought I could just have my app screen in a canvas along with the spinny thing (with a greater ZIndex) then just make the spinny thing visible as required.
This, however, is getting hard. Canvases do not seem well set up for this and I think I might be barking up the wrong tree.
I would appreciate any help. Thanks.
I have done this with a few programs. Here it is in a nutshell:
(This is easiest with MVVM. It has been so long since I used the codebehid for things like this I can't really say if there is a good way to do it.)
Create a border on your Main Window. I usually make it black with a 50% transparency. Add a grid to it, and put whatever you want inside to tell users it is busy. Size the border and the controls inside it to fill the screen.
Create a property on your main ViewModel for IsBusy as boolean. Initialize it as False. Bind the Visibility property of the Busy Border to that property.
Next, make a converter class for Busy(Boolean) to Visibility. Write the logic into that so that when value is True, Then visibility is Visible, when value is false, visibility is collapsed. ( http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.aspx ).
Back on the border, add your converter to the binding. Add code to the ViewModel for each of your Pages or Views that calls back to that property and sets it to true when your other thread is busy.
Cory
Edit:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MainWindow"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480">
<Grid>
<Border BorderBrush="Black" BorderThickness="1" Background="#80000000" Visibility="Collapsed">
<Grid>
<TextBlock Margin="0" TextWrapping="Wrap" Text="Busy...Please Wait" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="26.667" FontWeight="Bold" Foreground="#7EFFFFFF"/>
</Grid>
</Border>
<DockPanel x:Name="LayoutRoot">
<CheckBox Content="CheckBox" VerticalAlignment="Top"/>
<TextBlock TextWrapping="Wrap"><Run Text="TextBlock"/></TextBlock>
<UserControl x:Name="ViewViewView"/>
</DockPanel>
</Grid>
Look at this WPF toolkit with a busy indicator: https://github.com/xceedsoftware/wpftoolkit/wiki/BusyIndicator
I do this by simply displaying a dialog (so the user cannot interact with anything else and it will be displayed on top) then handle the Closing event (as the user could press Alt-F4) to see if the operation has finished otherwise I cancel the closing event:
myWaitWindow.ShowDialog(); // this can be borderless window with a spinny thing
void Window_Closing(object sender, CancelEventArgs e)
{
if(this.myOperation.IsRunning) // you would have to have some way to see when your operation has finished
e.Cancel = true;
}

How to build a generic/re-usable modal dialog for WPF following MVVM

I would like to build a generic/re-usable modal dialog that I can use in our WPF (MVVM) - WCF LOB application.
I have a Views and associated ViewModels that I would like to display using dialogs. Bindings between Views and ViewModels are done using Type-targeted DataTemplates.
Here are some requirements that I have been able to draft:
I prefer this to be based on a Window instead of using Adorners and controls that act like a modal dialog.
It should get its minimum size from the content.
It should center on the owner window.
The window must not show the Minimize and Maximize buttons.
It should get its title from the content.
What is the best way to do this?
I usually deal with this by injecting this interface into the appropriate ViewModels:
public interface IWindow
{
void Close();
IWindow CreateChild(object viewModel);
void Show();
bool? ShowDialog();
}
This allows the ViewModels to spaw child windows and show them modally on modeless.
A reusable implementation of IWindow is this:
public class WindowAdapter : IWindow
{
private readonly Window wpfWindow;
public WindowAdapter(Window wpfWindow)
{
if (wpfWindow == null)
{
throw new ArgumentNullException("window");
}
this.wpfWindow = wpfWindow;
}
#region IWindow Members
public virtual void Close()
{
this.wpfWindow.Close();
}
public virtual IWindow CreateChild(object viewModel)
{
var cw = new ContentWindow();
cw.Owner = this.wpfWindow;
cw.DataContext = viewModel;
WindowAdapter.ConfigureBehavior(cw);
return new WindowAdapter(cw);
}
public virtual void Show()
{
this.wpfWindow.Show();
}
public virtual bool? ShowDialog()
{
return this.wpfWindow.ShowDialog();
}
#endregion
protected Window WpfWindow
{
get { return this.wpfWindow; }
}
private static void ConfigureBehavior(ContentWindow cw)
{
cw.WindowStartupLocation = WindowStartupLocation.CenterOwner;
cw.CommandBindings.Add(new CommandBinding(PresentationCommands.Accept, (sender, e) => cw.DialogResult = true));
}
}
You can use this Window as a reusable host window. There's no code-behind:
<Window x:Class="Ploeh.Samples.ProductManagement.WpfClient.ContentWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self="clr-namespace:Ploeh.Samples.ProductManagement.WpfClient"
xmlns:pm="clr-namespace:Ploeh.Samples.ProductManagement.PresentationLogic.Wpf;assembly=Ploeh.Samples.ProductManagement.PresentationLogic.Wpf"
Title="{Binding Path=Title}"
Height="300"
Width="300"
MinHeight="300"
MinWidth="300" >
<Window.Resources>
<DataTemplate DataType="{x:Type pm:ProductEditorViewModel}">
<self:ProductEditorControl />
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding}" />
</Window>
You can read more about this (as well as download the full code sample) in my book.
I'm answering my own question to help others find all answers I struggled to find in one place. What above seems like a straight forward problem, actually presents multiple problems that I hope to answer sufficiently below.
Here goes.
Your WPF window that will serve as the generic dialog can look something like this:
<Window x:Class="Example.ModalDialogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ex="clr-namespace:Example"
Title="{Binding Path=mDialogWindowTitle}"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner"
WindowStyle="SingleBorderWindow"
SizeToContent="WidthAndHeight"
ex:WindowCustomizer.CanMaximize="False"
ex:WindowCustomizer.CanMinimize="False"
>
<DockPanel Margin="3">
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" FlowDirection="RightToLeft">
<Button Content="Cancel" IsCancel="True" Margin="3"/>
<Button Content="OK" IsDefault="True" Margin="3" Click="Button_Click" />
</StackPanel>
<ContentPresenter Name="WindowContent" Content="{Binding}"/>
</DockPanel>
</Window>
Following MVVM, the right way to show a dialog is through a mediator. To use a mediator, you typically require some service locator as well. For mediator specific details, look here.
The solution I settled on involved implementing an IDialogService interface that is resolved through a simple static ServiceLocator. This excellent codeproject article has the details on that. Take note of this message in the article forum. This solution also solves the problem of discovering the owner window via the ViewModel instance.
Using this interface, you can call IDialogService.ShowDialog(ownerViewModel, dialogViewModel). For now, I'm calling this from the owner ViewModel, meaning I have hard references between my ViewModels. If you use aggregated events, you will probably call this from a conductor.
Setting the minimum size on the View that will eventually be displayed in the dialog doesn't automatically set the minimum size of the dialog. Also, since the logical tree in the dialog contains the ViewModel, you can't just bind to the WindowContent element's properties. This question has an answer with my solution.
The answer I mention above also includes code that centers the window on the owner.
Finally, disabling the minimize and maximize buttons is something WPF can't natively do. The most elegant solution IMHO is using this.

Handle exceptions with WPF and MVVM

I am attempting to build an application using WPF and the MVVM pattern. I have my Views being populated from my ViewModel purely through databinding. I want to have a central place to handle all exceptions which occur in my application so I can notify the user and log the error appropriately.
I know about Dispatcher.UnhandledException but this does not do the job as exception that occur during databinding are logged to the output windows. Because my View is databound to my ViewModel the entire application is pretty much controlled via databinding so I have no way to log my errors.
Is there a way to generically handle the exceptions raised during databinding, without having to put try blocks around all my ViewModel public's?
Example View:
<Window x:Class="Test.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TestView" Height="600" Width="800"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<StackPanel VerticalAlignment="Center">
<Label Visibility="{Binding DisplayLabel, Converter={StaticResource BooleanToVisibilityConverter}}">My Label</Label>
</StackPanel>
</Window>
The ViewModel:
public class TestViewModel
{
public bool DisplayLabel
{
get { throw new NotImplementedException(); }
}
}
It is an internal application so I do not want to use Wer as I have seen previously recommended.
The Binding implementation is designed to be fault tolerant and so it catches all the exceptions. What you could do is to activate the following properties in your bindings:
ValidatesOnExceptions = true
NotifyOnValidationError = true
See also the MSDN.
This causes to raise the attached Error property on the bound control.
However, this infrastructure is designed for validating the user input and show validation messages. I’m not sure if this is what you are doing.

Resources