简体   繁体   English

WPF - 棱镜区域和祖先绑定的相对来源问题

[英]WPF - Problem with Prism regions and relative source to ancestor binding

I tried to create a simple example of my problem:我试图为我的问题创建一个简单的例子:

Lets say we have the following UserControl with a Label , Image , region and a Button :假设我们有以下UserControlLabelImage 、 region 和Button

<UserControl x:Class="SR.Soykaf.Client.Main.Simple.SimpleTabView"
             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"
             xmlns:core="clr-namespace:SR.Soykaf.Client.Core.Core;assembly=SR.Soykaf.Client.Core"
             xmlns:regions="http://prismlibrary.com/"
             mc:Ignorable="d"
             regions:ViewModelLocator.AutoWireViewModel="True"
             d:DesignHeight="300" d:DesignWidth="300">
   <Grid>
      <Grid.RowDefinitions>
         <RowDefinition Height="*" />
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
         <ColumnDefinition Width="*" />
      </Grid.ColumnDefinitions>

      <Grid Grid.Row="0" Grid.Column="0">
         <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
         </Grid.RowDefinitions>
         <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
         </Grid.ColumnDefinitions>

         <Label Grid.Row="0" HorizontalAlignment="Center" Content="TITLE" />

         <Image Grid.Row="1" Stretch="Uniform" UseLayoutRounding="True" Height="200" Width="153" Source="{Binding PortraitImage}" />

         <ContentControl Grid.Row="2" regions:RegionManager.RegionName="{x:Static core:RegionNames.CombatWizardRegion}" />

         <Button Grid.Row="3" Content="Change view in region" Command="{Binding ChangeViewCommand}" />
      </Grid>
   </Grid>
</UserControl>

With this ViewModel:使用此视图模型:

public class SimpleTabViewModel : BaseViewModel
{
   private int _state;

   private BitmapImage _portraitImage;

   public BitmapImage PortraitImage
   {
      get => _portraitImage;
      set => SetProperty(ref _portraitImage, value);
   }

   public ICommand ChangeViewCommand { get; set; }

   public SimpleTabViewModel()
   {
      SetPortraitImage();

      _state = 0;
      ChangeViewCommand = new DelegateCommand(ChangeView);

   }

   private void ChangeView()
   {
      if (_state == 0)
      {
         RegionManager.RequestNavigate(RegionNames.CombatWizardRegion, new Uri(nameof(WeaponWizardView), UriKind.Relative));
         _state = 1;
      }
      else
      {
         RegionManager.RequestNavigate(RegionNames.CombatWizardRegion, new Uri(nameof(SpellWizardView), UriKind.Relative));
         _state = 0;
      }
   }

   private void SetPortraitImage()
   {
      PortraitImage = new BitmapImage();

      var resource = typeof(Data.Characters.PlayerCharacter).Assembly.GetManifestResourceNames().Single(x => x.ContainsIgnoreCase("PC_Hawk"));

      using (var stream = typeof(Data.Characters.PlayerCharacter).Assembly.GetManifestResourceStream(resource))
      {
         PortraitImage.BeginInit();
         PortraitImage.StreamSource = stream;
         PortraitImage.CacheOption = BitmapCacheOption.OnLoad;
         PortraitImage.EndInit();
      }
   }
}

As you can see, the Button will switch between 2 views for the CombatWizardRegion region.如您所见, Button将在CombatWizardRegion区域的 2 个视图之间切换。 These are the simple views:这些是简单的视图:

View 1:视图 1:

<UserControl x:Class="SR.Soykaf.Client.Main.Simple.SpellWizardView"
             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"
             xmlns:mvvm="http://prismlibrary.com/"
             mc:Ignorable="d"
             mvvm:ViewModelLocator.AutoWireViewModel="True"
             d:DesignHeight="450" d:DesignWidth="800">
   <Grid>
      <Grid.RowDefinitions>
         <RowDefinition Height="Auto" />
         <RowDefinition Height="Auto" />
      </Grid.RowDefinitions>

      <Label Grid.Row="0" HorizontalAlignment="Center" Content="Spell" />

      <Image Grid.Row="1" Stretch="Uniform" UseLayoutRounding="True" Height="200" Width="153" Source="{Binding DataContext.PortraitImage, RelativeSource={RelativeSource AncestorType={x:Type UserControl}, AncestorLevel=2}}" />
   </Grid>
</UserControl>

View 2:视图 2:

<UserControl x:Class="SR.Soykaf.Client.Main.Simple.WeaponWizardView"
             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" 
             xmlns:mvvm="http://prismlibrary.com/"
             mc:Ignorable="d" 
             mvvm:ViewModelLocator.AutoWireViewModel="True"
             d:DesignHeight="450" d:DesignWidth="800">
   <Grid>
      <Grid.RowDefinitions>
         <RowDefinition Height="Auto" />
         <RowDefinition Height="Auto" />
      </Grid.RowDefinitions>

      <Label Grid.Row="0" HorizontalAlignment="Center" Content="Weapon" />

      <Image Grid.Row="1" Stretch="Uniform" UseLayoutRounding="True" Height="200" Width="153" Source="{Binding DataContext.PortraitImage, RelativeSource={RelativeSource AncestorType={x:Type UserControl}, AncestorLevel=2}}" />
   </Grid>
</UserControl>

These two views basically bind their image source to the PortraitImage property of the parent view model.这两个视图基本上将它们的图像源绑定到父视图 model 的PortraitImage属性。 Both views also have a unique label: Spell and Weapon to distinguish between the two of them.这两个视图也都有一个独特的 label: SpellWeapon来区分它们两者。

Then I register the 2 views:然后我注册了 2 个视图:

_regionManager.RegisterViewWithRegion(RegionNames.CombatWizardRegion, typeof(SpellWizardView));
_regionManager.RegisterViewWithRegion(RegionNames.CombatWizardRegion, typeof(WeaponWizardView));

Launching this application works fine - on first sight:启动此应用程序运行良好 - 乍一看:

在此处输入图像描述

But... then we click the button to switch views:但是……然后我们点击按钮切换视图:

在此处输入图像描述

Why is the image not loading in the second view?为什么图像没有在第二个视图中加载? It has the same binding code as the first view.它与第一个视图具有相同的绑定代码。

(Also if I first register the WeaponCombatView to the region, then that view works but the SpellCombatView doesn't work anymore.) (另外,如果我首先将 WeaponCombatView 注册到该区域,则该视图有效,但 SpellCombatView 不再有效。)

I get this error for the view which is registered last:对于最后注册的视图,我收到此错误:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.UserControl', AncestorLevel='2''. BindingExpression:Path=DataContext.PortraitImage; DataItem=null; target element is 'Image' (Name=''); target property is 'Source' (type 'ImageSource')

Also interesting: During debugging if I change the AncesterLevel to 3 and back to 2, the image suddenly appears because the binding seems to get refreshed.也很有趣:在调试过程中,如果我将 AncesterLevel 更改为 3 再更改回 2,图像会突然出现,因为绑定似乎被刷新了。 I also checked the Visual Tree and I don't see any problems.我还检查了可视树,没有发现任何问题。

Thanks in advance!提前致谢!

The RegisterViewWithRegion method enables view discovery, but is intended to construct and show the specified view when the control is loaded . RegisterViewWithRegion方法启用视图发现,但旨在在加载控件时构造和显示指定的视图。 The navigation service allows for changing the view dynamically.导航服务允许动态更改视图。 Typically, you would use RegisterViewWithRegion for static views like a menu, that do not change.通常,您会为static 视图使用RegisterViewWithRegion ,就像菜单一样,不会更改。

[..] if I first register the WeaponCombatView to the region, then that view works but the SpellCombatView doesn't work anymore. [..] 如果我首先将 WeaponCombatView 注册到该区域,那么该视图可以工作,但 SpellCombatView 不再工作。

Your CombatWizardRegion is in a ContentControl which uses a SingleActiveRegion in the default Prism region adapter.您的CombatWizardRegion位于ContentControl中,该控件在默认 Prism 区域适配器中使用SingleActiveRegion This means, that it can only display a single active view at once in it.这意味着,它一次只能显示一个活动视图 When using RegisterViewWithRegion to register multiple views, they will be registered and both will be added to the Views collection of the region, but only the first one will be added to the ActiveViews collection and displayed.当使用RegisterViewWithRegion注册多个视图时,它们都会被注册,并且都会被添加到该区域的Views集合中,但是只有第一个会被添加到ActiveViews集合中并显示出来。 For your registrations below, effectively only the first one will be displayed.对于您在下面的注册,实际上只会显示第一个。

_regionManager.RegisterViewWithRegion(RegionNames.CombatWizardRegion, typeof(SpellWizardView));
_regionManager.RegisterViewWithRegion(RegionNames.CombatWizardRegion, typeof(WeaponWizardView));

After navigation your views do not display, because you did not register them for navigation in the container, so the navigation service will not find them.导航后,您的视图不会显示,因为您没有在容器中注册它们以进行导航,因此导航服务将找不到它们。 The region manager will store them different internally with RegisterViewWithRegion , that is why you cannot just register them that way.区域管理器将在内部使用RegisterViewWithRegion将它们不同地存储,这就是为什么您不能以这种方式注册它们的原因。 Instead register them like this in Prism >=7:而是像这样在 Prism >=7 中注册它们:

containerRegistry.RegisterForNavigation<SpellWizardView>();
containerRegistry.RegisterForNavigation<WeaponWizardView>();

For older versions of Prism <=6 you have to use one of these methods:对于旧版本的 Prism <=6,您必须使用以下方法之一:

containerRegistry.RegisterTypeForNavigation<SpellWizardView>(nameof(SpellWizardView));
containerRegistry.Register<object, SpellWizardView>(nameof(SpellWizardView));
containerRegistry.Register(typeof(object), typeof(SpellWizardView), nameof(SpellWizardView));

I recommend you to use either RegisterViewWithRegion for static regions or the navigation service for dynamic regions, where you need to change views with the navigation service.我建议您对 static 区域使用RegisterViewWithRegion或对需要使用导航服务更改视图的动态区域使用导航服务。 You can navigate to the initial view instead of registering it with the region manager and RegisterViewWithRegion .您可以导航到初始视图,而不是向区域管理器和RegisterViewWithRegion它。

I get this error for the view which is registered last: System.Windows.Data Error: 4: Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.UserControl', AncestorLevel='2''.最后注册的视图出现此错误: System.Windows.Data 错误:4:找不到与引用'RelativeSource FindAncestor,AncestorType ='System.Windows.Controls.UserControl',AncestorLevel ='2''绑定的源.

This binding works fine, it is failing because of the single active region issue above.此绑定工作正常,由于上面的单个活动区域问题而失败。 Both views get added to the Views collection of the CombatWizardRegion , but only the first is added to the ActiveViews collection and displayed and therefore set as Content of the corresponding ContentControl .两个视图都被添加到CombatWizardRegionViews集合中,但只有第一个视图被添加到ActiveViews集合中并显示,因此设置为相应ContentControlContent Consequently, the first view is in the visual tree and receives the data context, while the second view is not in the visual tree, so its data context is null and there is no ancestor, which causes the error.因此,第一个视图在可视树中并接收数据上下文,而第二个视图不在可视树中,因此其数据上下文为null并且没有祖先,从而导致错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM