[英]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
:假设我们有以下
UserControl
和Label
、 Image
、 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:
Spell
和Weapon
来区分它们两者。
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
.两个视图都被添加到
CombatWizardRegion
的Views
集合中,但只有第一个视图被添加到ActiveViews
集合中并显示,因此设置为相应ContentControl
的Content
。 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.