绑定 ElementName return 来自先前实例化的 DataTemplate 的实例
Binding ElementName return an instance from a previously instancied DataTemplate
我有以下数据模板
<DataTemplate x:Key="ArchiveModeContentTemplate">
<Button Style="{x:Static ui:ButtonStyles.DrawingButtonLabel}" Grid.Row="1" Grid.Column="0" Foreground="{x:Static ui:UbiBrushes.UbiDarkBlue}"
Content="{StaticResource ValidateIcon48}" ui:StyleProperties.Label="{DynamicResource Archive}"
Command="{Binding ElementName=factory,Path=BuildPopup}">
<i:Interaction.Behaviors>
<pop:PopupFactory x:Name="factory" Factory="{Binding ConfirmArchivingFactory}" />
</i:Interaction.Behaviors>
</Button>
</DataTemplate>
PopupFactory
有一个命令 BuildPopup
。此命令被提供给具有 ElementName
.
绑定的按钮
第一次显示此数据模板时,它工作正常。按钮获取命令。但是,如果此 dataTemplate 被卸载然后再次显示,则绑定将 PopupFactory
的前一个实例而不是新创建的实例的命令提供给按钮。
我传入 PopupFactory
的构造函数并将其附加到新按钮。所以这不是 PopupFactory
在模板之间共享的问题。
为什么会这样?这是 xaml 缓存的错误吗?
编辑
我现在有一个更奇怪的错误。
我将语法更改为以下内容,以便在 Xaml 中的名称声明之后具有绑定 elementName。现在该命令可以正常工作,但是使用绑定 RelativeSource
查找名为 GoBack
的命令的第二个按钮不再起作用。我使用 snoop 检查绑定,它抱怨找不到命令 BuildPopup
。 WPF 越来越疯狂了!
<Button Style="{x:Static ui:ButtonStyles.DrawingButtonLabel}" Grid.Row="1" Grid.Column="0" Foreground="{x:Static ui:UbiBrushes.UbiDarkBlue}"
Content="{StaticResource ValidateIcon48}" ui:StyleProperties.Label="{DynamicResource Archive}">
<i:Interaction.Behaviors>
<pop:PopupFactory x:Name="Archivefactory" Factory="{Binding ConfirmArchivingFactory}" IsSingleInstance="False" />
</i:Interaction.Behaviors>
<Button.Command>
<Binding ElementName="Archivefactory" Path="BuildPopup" />
</Button.Command>
</Button>
<Button Grid.Row="1" Grid.Column="1"
Style="{x:Static ui:ButtonStyles.DrawingButtonLabel}"
Content="{StaticResource CrossIcon48}"
Foreground="Green"
ui:StyleProperties.Label="{DynamicResource Cancel}"
Command="{Binding Path=GoBack, RelativeSource={RelativeSource AncestorType={x:Type ui:DrillDown}}}" />
编辑
这里是PopupFactory的代码
public class PopupFactory : Behavior<UIElement>
{
public ICommand BuildPopup { get; private set; }
private bool _canExecute;
private IDisposable _canexecuteSubscription = null;
public IObservable<bool> CanExecuteSource
{
get { return (IObservable<bool>)GetValue(CanExecuteSourceProperty); }
set { SetValue(CanExecuteSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for CanExecute. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CanExecuteSourceProperty =
DependencyProperty.Register("CanExecute", typeof(IObservable<bool>), typeof(PopupFactory), new PropertyMetadata(null));
private static void OnCanExecuteSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs arg)
{
var factory = obj as PopupFactory;
factory._canexecuteSubscription?.Dispose();
if (arg.NewValue != null)
{
factory._canexecuteSubscription = ((IObservable<bool>)arg.NewValue)
.ObserveOnDispatcher()
.Subscribe(factory.UpdateCanExecute);
}
}
private void UpdateCanExecute(bool value)
{
_canExecute = value;
((RelayCommand<object>)BuildPopup).RaiseCanExecuteChanged();
}
public IFactory Factory
{
get { return (IFactory)GetValue(FactoryProperty); }
set { SetValue(FactoryProperty, value); }
}
// Using a DependencyProperty as the backing store for Factory. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FactoryProperty =
DependencyProperty.Register("Factory", typeof(IFactory), typeof(PopupFactory), new PropertyMetadata(null, OnFactoryChanged));
private static void OnFactoryChanged(DependencyObject obj, DependencyPropertyChangedEventArgs arg)
{
var factory = obj as PopupFactory;
((RelayCommand<object>)factory.BuildPopup).RaiseCanExecuteChanged();
}
public UIElement PlacementTarget
{
get { return (UIElement)GetValue(PlacementTargetProperty); }
set { SetValue(PlacementTargetProperty, value); }
}
// Using a DependencyProperty as the backing store for PlacementTarget. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PlacementTargetProperty =
DependencyProperty.Register("PlacementTarget", typeof(UIElement), typeof(PopupFactory), new PropertyMetadata(null));
public PlacementMode Placement
{
get { return (PlacementMode)GetValue(PlacementProperty); }
set { SetValue(PlacementProperty, value); }
}
// Using a DependencyProperty as the backing store for Placement. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PlacementProperty =
DependencyProperty.Register("Placement", typeof(PlacementMode), typeof(PopupFactory), new PropertyMetadata(PlacementMode.Center));
public bool IsSingleInstance
{
get { return (bool)GetValue(IsSingleInstanceProperty); }
set { SetValue(IsSingleInstanceProperty, value); }
}
// Using a DependencyProperty as the backing store for IsSingleInsance. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsSingleInstanceProperty =
DependencyProperty.Register("IsSingleInstance", typeof(bool), typeof(PopupFactory), new PropertyMetadata(false));
private bool _singleInstanceShowed = false;
public PopupFactory()
{
BuildPopup = new RelayCommand<object>((f) =>
{
ShowPopup(f);
}, (p) =>
{
return _canExecute && Factory != null && !_singleInstanceShowed;
});
UpdateCanExecute(true);
}
public IOverlayContainer ShowPopup(object parameter)
{
var param = new PopupParameter() { Owner = AssociatedObject };
UIElement target = PlacementTarget != null ? PlacementTarget : AssociatedObject;
var item = Factory.Build(parameter);
param.Content = item.Item;
param.Owner = AssociatedObject;
param.RemoveCondition = item.DisposeStream;
var container = OverlayManager.ShowPopup(param);
var placement = new PopupRelativePlacement(container as FrameworkElement, target,
Placement, false);
item.PostFactory?.Invoke();
if (IsSingleInstance)
{
_singleInstanceShowed = true;
OverlayManager.PopupOperations.Where((op) => op.Id == container.Id && op.Operationtype == OverlayOperation.OpType.PopupRemoved)
.Once((_) =>
{
_singleInstanceShowed = false;
((RelayCommand<object>)BuildPopup).RaiseCanExecuteChanged();
});
}
return container;
}
}
问题已解决。
我将 PopupFactory 行为移至按钮的可视父级。这样,行为是在按钮之前创建的,并且 WPF 不会在绑定期间搞乱名称解析。
我有以下数据模板
<DataTemplate x:Key="ArchiveModeContentTemplate">
<Button Style="{x:Static ui:ButtonStyles.DrawingButtonLabel}" Grid.Row="1" Grid.Column="0" Foreground="{x:Static ui:UbiBrushes.UbiDarkBlue}"
Content="{StaticResource ValidateIcon48}" ui:StyleProperties.Label="{DynamicResource Archive}"
Command="{Binding ElementName=factory,Path=BuildPopup}">
<i:Interaction.Behaviors>
<pop:PopupFactory x:Name="factory" Factory="{Binding ConfirmArchivingFactory}" />
</i:Interaction.Behaviors>
</Button>
</DataTemplate>
PopupFactory
有一个命令 BuildPopup
。此命令被提供给具有 ElementName
.
第一次显示此数据模板时,它工作正常。按钮获取命令。但是,如果此 dataTemplate 被卸载然后再次显示,则绑定将 PopupFactory
的前一个实例而不是新创建的实例的命令提供给按钮。
我传入 PopupFactory
的构造函数并将其附加到新按钮。所以这不是 PopupFactory
在模板之间共享的问题。
为什么会这样?这是 xaml 缓存的错误吗?
编辑
我现在有一个更奇怪的错误。
我将语法更改为以下内容,以便在 Xaml 中的名称声明之后具有绑定 elementName。现在该命令可以正常工作,但是使用绑定 RelativeSource
查找名为 GoBack
的命令的第二个按钮不再起作用。我使用 snoop 检查绑定,它抱怨找不到命令 BuildPopup
。 WPF 越来越疯狂了!
<Button Style="{x:Static ui:ButtonStyles.DrawingButtonLabel}" Grid.Row="1" Grid.Column="0" Foreground="{x:Static ui:UbiBrushes.UbiDarkBlue}"
Content="{StaticResource ValidateIcon48}" ui:StyleProperties.Label="{DynamicResource Archive}">
<i:Interaction.Behaviors>
<pop:PopupFactory x:Name="Archivefactory" Factory="{Binding ConfirmArchivingFactory}" IsSingleInstance="False" />
</i:Interaction.Behaviors>
<Button.Command>
<Binding ElementName="Archivefactory" Path="BuildPopup" />
</Button.Command>
</Button>
<Button Grid.Row="1" Grid.Column="1"
Style="{x:Static ui:ButtonStyles.DrawingButtonLabel}"
Content="{StaticResource CrossIcon48}"
Foreground="Green"
ui:StyleProperties.Label="{DynamicResource Cancel}"
Command="{Binding Path=GoBack, RelativeSource={RelativeSource AncestorType={x:Type ui:DrillDown}}}" />
编辑
这里是PopupFactory的代码
public class PopupFactory : Behavior<UIElement>
{
public ICommand BuildPopup { get; private set; }
private bool _canExecute;
private IDisposable _canexecuteSubscription = null;
public IObservable<bool> CanExecuteSource
{
get { return (IObservable<bool>)GetValue(CanExecuteSourceProperty); }
set { SetValue(CanExecuteSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for CanExecute. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CanExecuteSourceProperty =
DependencyProperty.Register("CanExecute", typeof(IObservable<bool>), typeof(PopupFactory), new PropertyMetadata(null));
private static void OnCanExecuteSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs arg)
{
var factory = obj as PopupFactory;
factory._canexecuteSubscription?.Dispose();
if (arg.NewValue != null)
{
factory._canexecuteSubscription = ((IObservable<bool>)arg.NewValue)
.ObserveOnDispatcher()
.Subscribe(factory.UpdateCanExecute);
}
}
private void UpdateCanExecute(bool value)
{
_canExecute = value;
((RelayCommand<object>)BuildPopup).RaiseCanExecuteChanged();
}
public IFactory Factory
{
get { return (IFactory)GetValue(FactoryProperty); }
set { SetValue(FactoryProperty, value); }
}
// Using a DependencyProperty as the backing store for Factory. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FactoryProperty =
DependencyProperty.Register("Factory", typeof(IFactory), typeof(PopupFactory), new PropertyMetadata(null, OnFactoryChanged));
private static void OnFactoryChanged(DependencyObject obj, DependencyPropertyChangedEventArgs arg)
{
var factory = obj as PopupFactory;
((RelayCommand<object>)factory.BuildPopup).RaiseCanExecuteChanged();
}
public UIElement PlacementTarget
{
get { return (UIElement)GetValue(PlacementTargetProperty); }
set { SetValue(PlacementTargetProperty, value); }
}
// Using a DependencyProperty as the backing store for PlacementTarget. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PlacementTargetProperty =
DependencyProperty.Register("PlacementTarget", typeof(UIElement), typeof(PopupFactory), new PropertyMetadata(null));
public PlacementMode Placement
{
get { return (PlacementMode)GetValue(PlacementProperty); }
set { SetValue(PlacementProperty, value); }
}
// Using a DependencyProperty as the backing store for Placement. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PlacementProperty =
DependencyProperty.Register("Placement", typeof(PlacementMode), typeof(PopupFactory), new PropertyMetadata(PlacementMode.Center));
public bool IsSingleInstance
{
get { return (bool)GetValue(IsSingleInstanceProperty); }
set { SetValue(IsSingleInstanceProperty, value); }
}
// Using a DependencyProperty as the backing store for IsSingleInsance. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsSingleInstanceProperty =
DependencyProperty.Register("IsSingleInstance", typeof(bool), typeof(PopupFactory), new PropertyMetadata(false));
private bool _singleInstanceShowed = false;
public PopupFactory()
{
BuildPopup = new RelayCommand<object>((f) =>
{
ShowPopup(f);
}, (p) =>
{
return _canExecute && Factory != null && !_singleInstanceShowed;
});
UpdateCanExecute(true);
}
public IOverlayContainer ShowPopup(object parameter)
{
var param = new PopupParameter() { Owner = AssociatedObject };
UIElement target = PlacementTarget != null ? PlacementTarget : AssociatedObject;
var item = Factory.Build(parameter);
param.Content = item.Item;
param.Owner = AssociatedObject;
param.RemoveCondition = item.DisposeStream;
var container = OverlayManager.ShowPopup(param);
var placement = new PopupRelativePlacement(container as FrameworkElement, target,
Placement, false);
item.PostFactory?.Invoke();
if (IsSingleInstance)
{
_singleInstanceShowed = true;
OverlayManager.PopupOperations.Where((op) => op.Id == container.Id && op.Operationtype == OverlayOperation.OpType.PopupRemoved)
.Once((_) =>
{
_singleInstanceShowed = false;
((RelayCommand<object>)BuildPopup).RaiseCanExecuteChanged();
});
}
return container;
}
}
问题已解决。
我将 PopupFactory 行为移至按钮的可视父级。这样,行为是在按钮之前创建的,并且 WPF 不会在绑定期间搞乱名称解析。