在默认 AppDomain 中的 Winforms 中托管 WPF 用户控件的弹出窗口中的选项卡导航损坏

Posted

技术标签:

【中文标题】在默认 AppDomain 中的 Winforms 中托管 WPF 用户控件的弹出窗口中的选项卡导航损坏【英文标题】:Broken tab navigation in popup with WPF user control hosted inside Winforms in default AppDomain 【发布时间】:2016-12-02 02:15:51 【问题描述】:

我有一个使用弹出窗口的 WPF 用户控件。此控件是一个插件,可以在主 AppDomain 或单独的 AppDomain 中加载,它使用 ElementHost 托管在 Winforms 表单中。当插件在主 AppDomain 中加载并打开弹出窗口时,弹出窗口字段之间的制表符将焦点移动到弹出窗口父级的第一个控件。当它被加载到新的 AppDomain 中时,选项卡行为按预期/期望工作(它在弹出窗口中的控件中循环)。

我已经阅读了许多类似但不完全相同的关于 SO 和其他地方的问题,但没有任何建议有帮助。

似乎选项卡消息正在 AddInHost 中处理(这来自我使用 FrameworkElementAdapters 在域外情况下跨域边界编组 WPF 控件)。我的最终目标是将其实现为托管插件框架插件,但我已经削减了这种方式以简化重现。

如果有更完整的上下文有帮助,我有a git repo of the simplified repro

我可以做些什么来使这种行为保持一致?

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(选定位)

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);

【问题讨论】:

That 似乎描述了您的问题,即使托管是 WPF-WPF。尽管如此,你可以试一试。顺便说一句..DevExpress-Guy 有点有趣,指向 M$。一个附带问题:重写 Host-App 是一种选择吗? 我测试了它,它对我有用。弹出窗口不关闭。它的密码弹出对吗? 我还从 GitHub 测试了您的应用程序,它工作正常。 @AshkanMobayenKhiabani 我在故障排除中混淆了这个问题。发布的示例不会立即关闭弹出窗口,但按 Tab 会移动到父窗口中的第一个控件,而不是弹出窗口中的下一个控件。当控件加载到单独的 AppDomain 中时,按 Tab 键可在弹出窗口中的控件之间移动。我将编辑我的问题。 这与问题无关,但如果您使用 WPF,请尝试实现 MVVM。 【参考方案1】:

我有点受过教育,但绝不是权威,我猜测问题在于 WinForms 和 WPF 期望拥有对顶部窗口的消息泵的独占访问权。在其自己的 AppDomain 中运行每个都可以为每个父窗口及其消息泵提供独占控制。

【讨论】:

以上是关于在默认 AppDomain 中的 Winforms 中托管 WPF 用户控件的弹出窗口中的选项卡导航损坏的主要内容,如果未能解决你的问题,请参考以下文章

AppDomains之间的通信

为啥我不能订阅部分信任 AppDomain 中的事件?

ASP.Net 是不是曾经在默认的 AppDomain 中运行?

默认 AppDomain 与新 AppDomain 中不同的依赖解析行为加载程序集

添加当前 AppDomain 的路径

如何重新加载在浏览器中运行 Silverlight 应用程序的默认 AppDomain?