Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Broken tab navigation in popup with WPF user control hosted inside Winforms in default AppDomain

Tags:

I have a WPF user control that uses a Popup. This control is a plugin and can be loaded in the main AppDomain or in a separate AppDomain, and it is hosted in a Winforms form using ElementHost. When the plugin is loaded in the main AppDomain, and the popup is opened, tabbing between the fields of the popup instead moves focus to the first control of the popup windows parent. When it is loaded in a new AppDomain, the tab behavior works as expected/desired (it cycles through the controls in the popup window).

I have read through many similar, but not quite the same, questions here on SO and elsewhere, but none of the suggestions have helped.

It appears that the tab message is getting handled in the AddInHost (which comes from my use of FrameworkElementAdapters to marshal the WPF control across domain boundaries in out-of-domain case). My ultimate goal is to implement this as a Managed Add-in Framework addin, but I have pared that WAY down to simplify the repro.

In case it helps to have a more complete context, I have a git repo of the simplified repro

What can I do to make this behavior consistent?

WpfUserControl.xaml

<UserControl x:Class="MyPlugin.WpfUserControl"          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" Background="White">     <Grid Margin="5">         <Grid.RowDefinitions>             <RowDefinition Height="28" />             <RowDefinition Height="28" />             <RowDefinition Height="28" />         </Grid.RowDefinitions>          <TextBox Grid.Row="0" Margin="3" />          <Button x:Name="DropDownButton" Grid.Row="1" Margin="3" HorizontalAlignment="Left" MinWidth="100" Content="Drop Down" Click="DropDownButton_OnClick" />         <Popup Grid.Row="1" x:Name="Popup1" Placement="Right" StaysOpen="True" PlacementTarget="{Binding ElementName=DropDownButton}">             <Border BorderBrush="Black" BorderThickness="1">                 <Grid Background="White">                     <Grid.ColumnDefinitions>                         <ColumnDefinition />                         <ColumnDefinition />                     </Grid.ColumnDefinitions>                     <Grid.RowDefinitions>                         <RowDefinition />                         <RowDefinition />                         <RowDefinition />                     </Grid.RowDefinitions>                      <Label Content="Username:" Grid.Row="0" Grid.Column="0" Margin="3" />                     <TextBox Grid.Row="0" Grid.Column="1" Margin="3" MinWidth="150" />                      <Label Content="Password:" Grid.Row="1" Grid.Column="0" Margin="3" />                     <TextBox Grid.Row="1" Grid.Column="1" Margin="3" MinWidth="150" />                      <Button x:Name="SaveButton" Grid.Row="2" Grid.Column="1" Margin="3" HorizontalAlignment="Right"                                             Content="Save" Click="SaveButton_OnClick" />                 </Grid>             </Border>         </Popup>          <Button x:Name="DoSomethingButton" Grid.Row="2" Margin="3" HorizontalAlignment="Left" MinWidth="100" Content="Do Something" />     </Grid> </UserControl> 

Plugin.cs

public class Plugin : MarshalByRefObject {     public INativeHandleContract CreateWpfUserControl()     {         return FrameworkElementAdapters.ViewToContractAdapter(new WpfUserControl());     } } 

MainForm.cs (selected bits)

private void LoadPlugin(bool loadInSameAppDomain) {     AppDomain appDomain;     if (loadInSameAppDomain)     {         appDomain = AppDomain.CurrentDomain;     }     else     {         var appDomainName = Guid.NewGuid().ToString();         _appDomain = AppDomain.CreateDomain(appDomainName, AppDomain.CurrentDomain.Evidence, new AppDomainSetup         {             ApplicationName = appDomainName,             ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,             PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory         });         appDomain = _appDomain;     }      _plugin = (Plugin)appDomain.CreateInstanceAndUnwrap("MyPlugin", "MyPlugin.Plugin"); }  private void loadPluginButton_Click(object sender, EventArgs e) {     LoadPlugin(appDomainCheckBox.Checked);      var pluginControl = FrameworkElementAdapters.ContractToViewAdapter(_plugin.CreateWpfUserControl());     elementHost1.Child = pluginControl;      UpdateUi(true); } 
like image 496
lordjeb Avatar asked Jul 27 '16 21:07

lordjeb


1 Answers

My somewhat educated, though by no means authoritative, guess is that the issue is that WinForms and WPF expect to have exclusive access to the top window's message pump. Running each in its own AppDomain gives each exclusive control of a parent window and its message pump.

like image 152
David A. Gray Avatar answered Sep 23 '22 18:09

David A. Gray