ContentControls 上的 WPF DataTriggers 在其绑定依赖项 属性 的 属性ChangedCallback 之后触发
WPF DataTriggers on ContentControls are firing after their bound Dependency Property's PropertyChangedCallback
我正在编写 WPF 输入对话框 window,它将根据名为 InputType 的依赖项 属性 显示不同的控件。
我使用的语言是 Visual COBOL .NET,但问题与语言无关,而与 WPF 本身有关,VB 和 C# 程序员很容易理解该语言。
这是我的对话框 window
的 XAML 代码
<Window x:Name="wndDialog"
x:Class="ClassLibraryNew.AGInputBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lib="clr-namespace:ClassLibraryNew"
xmlns:ctrl="clr-namespace:ClassLibraryNew.Controls"
Width="400"
MinHeight="200"
WindowStyle="None"
WindowStartupLocation="CenterOwner"
ResizeMode="NoResize"
Background="#FFEEEEEE"
SizeToContent="Height"
MouseDown="OnMouseDown"
Loaded="OnLoaded">
<Window.CommandBindings>
<CommandBinding Command="{x:Static lib:DialogCommands.OkCommand}" CanExecute="OnCommandCanExecute" Executed="OnCommandExecuted"/>
<CommandBinding Command="{x:Static lib:DialogCommands.CancelCommand}" CanExecute="OnCommandCanExecute" Executed="OnCommandExecuted"/>
</Window.CommandBindings>
<Border Style="{StaticResource AGTWindowBorder}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox Grid.Row="0" Header="{Binding Caption}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="{Binding Text}"
FontSize="{Binding Converter={StaticResource FontSizeConverter}, ConverterParameter='16'}"
VerticalAlignment="Center"
TextWrapping="Wrap"/>
<ContentControl Grid.Row="1" x:Name="contentControl" >
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Foreground="Red" Text="Errore: input type non valido. Contattare l'assistenza tecnica."/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding InputType}" Value="Text">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ctrl:TextField Text="{Binding Value, ElementName=wndDialog}"
ctrl:WatermarkService.Watermark="{Binding WatermarkText, ElementName=wndDialog}"
ctrl:WatermarkService.HideWhenFocused="False"
MaxLength="{Binding MaxLength, ElementName=wndDialog}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding InputType}" Value="Integer">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ctrl:IntegerField Value="{Binding Value, ElementName=wndDialog}"
ZeroFill="{Binding ZeroFill, ElementName=wndDialog}"
MaxLength="{Binding MaxLength, ElementName=wndDialog}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding InputType}" Value="Decimal">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ctrl:DecimalField Value="{Binding Value, ElementName=wndDialog}"
DecimalDigits="{Binding DecimalDigits, ElementName=wndDialog}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
</GroupBox>
<StackPanel Grid.Row="1"
Orientation="Horizontal"
HorizontalAlignment="Right"
Margin="8">
<StackPanel.Resources>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource EuroButton}">
<Setter Property="Width" Value="80"/>
<Setter Property="Margin" Value="8,0,0,0"/>
</Style>
</StackPanel.Resources>
<Button IsDefault="True"
Command="{x:Static lib:DialogCommands.OkCommand}">
<AccessText Text="_Ok"/>
</Button>
<Button IsCancel="True"
Command="{x:Static lib:DialogCommands.CancelCommand}">
<AccessText Text="_Annulla"/>
</Button>
</StackPanel>
</Grid>
</Border>
我想要实现的是当 InputType 为 Text 时向用户显示 'TextField'(自定义 TextBox),当 InputType 为 Integer 时向用户显示 'IntegerField',等等。
InputType 的类型是一个枚举名称 DialogInputType,它包含三个值(Text、Integer、Decimal)。
这工作正常,但是我需要一种方法来将事件处理程序附加到 ContentControl 内的字段,当它的内容已正确设置并且不为空时。
我希望 DataTriggers 在 InputType 更改时重新评估,但失败了:
(可视化 COBOL .NET)
01 InputTypeProperty type DependencyProperty public static initialize only
value type DependencyProperty::Register(
"InputType",
type of DialogInputType,
type of AGInputBox,
new FrameworkPropertyMetadata(
type DialogInputType::Text,
new PropertyChangedCallback(method OnInputTypeChanged)
)
).
*> property definition omitted...
method-id OnInputTypeChanged private static.
procedure division using by value sender as type DependencyObject, by value e as type DependencyPropertyChangedEventArgs.
if sender not instance of type AGInputBox
goback
end-if
declare wnd as type AGInputBox = sender as type AGInputBox
if wnd::contentControl::Content instance of type FieldBase *> debugger arrives here
declare textField as type FieldBase
set textField = wnd::contentControl::Content as type FieldBase
attach method wnd::OnFieldTemplateApplied to textField::TemplateApplied
end-if
end method.
VS 调试器显示 ContentControl 的 Content 为 null,但随后 window 被正确可视化,也许它的内容是稍后设置的...
它在以下情况下也是空的:
- Window 的加载事件
- Window 的 ContentRendered 事件
而且我无法在 DataTemplate 控件中设置 Loaded RoutedEventHandler,无论是 Loaded="OnFieldLoaded" 还是 Style + EventSetter,因为它被禁止并且不会编译(即使编译器错误建议使用 EventSetter:/ ).
编辑:我尝试了 l33t 的解决方案,但不幸的是,OnContentChanges 从未被调用,即使内容设置正确。
我创建了这个 class:
class-id ClassLibraryNew.Controls.NotifyingContentControl public
inherits type ContentControl.
01 ContentChanged type EventHandler event public.
method-id new public.
procedure division.
invoke super::new()
end method.
method-id OnContentChanged protected override.
procedure division using by value oldContent as object, by value newContent as object.
invoke super::OnContentChanged(oldContent, newContent) *> I put a debugger breakpoint here but it's not getting hit
invoke RaiseContentChanged()
end method.
method-id RaiseContentChanged private.
procedure division.
declare handler as type EventHandler = ContentChanged
declare e as type EventArgs = new EventArgs()
if handler not = null
invoke run handler(by value self, e)
end-if
end method.
end class.
即使您设法确定内容的值,也不能保证这就是您在 UI 中看到的值。
你可以试试这个:
public class ContentControlEx : ContentControl
{
protected override void OnContentChanged(object oldContent, object newContent)
{
// Do stuff...
base.OnContentChanged(oldContent, newContent);
}
}
然后用ContentControlEx
代替常规的
将 DataTemplates
定义为资源并处理 TextField
、IntegerField
和 DecimalField
根元素的 Loaded
事件:
<ContentControl Grid.Row="1" x:Name="contentControl" >
<ContentControl.Resources>
<DataTemplate x:Key="tfTemplate">
<ctrl:TextField ... Loaded="LoadedHandler"/>
</DataTemplate>
<!-- + DataTemplates for IntegerField and DecimalField -->
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Foreground="Red" Text="Errore: input type non valido. Contattare l'assistenza tecnica."/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding InputType}" Value="Text">
<Setter Property="ContentTemplate" Value="{StaticResource tfTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding InputType}" Value="Integer">
<Setter Property="ContentTemplate" Value="{StaticResource ifTemplate}" />
</DataTrigger>
...
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
我正在编写 WPF 输入对话框 window,它将根据名为 InputType 的依赖项 属性 显示不同的控件。 我使用的语言是 Visual COBOL .NET,但问题与语言无关,而与 WPF 本身有关,VB 和 C# 程序员很容易理解该语言。 这是我的对话框 window
的 XAML 代码<Window x:Name="wndDialog"
x:Class="ClassLibraryNew.AGInputBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lib="clr-namespace:ClassLibraryNew"
xmlns:ctrl="clr-namespace:ClassLibraryNew.Controls"
Width="400"
MinHeight="200"
WindowStyle="None"
WindowStartupLocation="CenterOwner"
ResizeMode="NoResize"
Background="#FFEEEEEE"
SizeToContent="Height"
MouseDown="OnMouseDown"
Loaded="OnLoaded">
<Window.CommandBindings>
<CommandBinding Command="{x:Static lib:DialogCommands.OkCommand}" CanExecute="OnCommandCanExecute" Executed="OnCommandExecuted"/>
<CommandBinding Command="{x:Static lib:DialogCommands.CancelCommand}" CanExecute="OnCommandCanExecute" Executed="OnCommandExecuted"/>
</Window.CommandBindings>
<Border Style="{StaticResource AGTWindowBorder}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox Grid.Row="0" Header="{Binding Caption}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="{Binding Text}"
FontSize="{Binding Converter={StaticResource FontSizeConverter}, ConverterParameter='16'}"
VerticalAlignment="Center"
TextWrapping="Wrap"/>
<ContentControl Grid.Row="1" x:Name="contentControl" >
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Foreground="Red" Text="Errore: input type non valido. Contattare l'assistenza tecnica."/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding InputType}" Value="Text">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ctrl:TextField Text="{Binding Value, ElementName=wndDialog}"
ctrl:WatermarkService.Watermark="{Binding WatermarkText, ElementName=wndDialog}"
ctrl:WatermarkService.HideWhenFocused="False"
MaxLength="{Binding MaxLength, ElementName=wndDialog}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding InputType}" Value="Integer">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ctrl:IntegerField Value="{Binding Value, ElementName=wndDialog}"
ZeroFill="{Binding ZeroFill, ElementName=wndDialog}"
MaxLength="{Binding MaxLength, ElementName=wndDialog}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding InputType}" Value="Decimal">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ctrl:DecimalField Value="{Binding Value, ElementName=wndDialog}"
DecimalDigits="{Binding DecimalDigits, ElementName=wndDialog}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
</GroupBox>
<StackPanel Grid.Row="1"
Orientation="Horizontal"
HorizontalAlignment="Right"
Margin="8">
<StackPanel.Resources>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource EuroButton}">
<Setter Property="Width" Value="80"/>
<Setter Property="Margin" Value="8,0,0,0"/>
</Style>
</StackPanel.Resources>
<Button IsDefault="True"
Command="{x:Static lib:DialogCommands.OkCommand}">
<AccessText Text="_Ok"/>
</Button>
<Button IsCancel="True"
Command="{x:Static lib:DialogCommands.CancelCommand}">
<AccessText Text="_Annulla"/>
</Button>
</StackPanel>
</Grid>
</Border>
我想要实现的是当 InputType 为 Text 时向用户显示 'TextField'(自定义 TextBox),当 InputType 为 Integer 时向用户显示 'IntegerField',等等。 InputType 的类型是一个枚举名称 DialogInputType,它包含三个值(Text、Integer、Decimal)。 这工作正常,但是我需要一种方法来将事件处理程序附加到 ContentControl 内的字段,当它的内容已正确设置并且不为空时。 我希望 DataTriggers 在 InputType 更改时重新评估,但失败了: (可视化 COBOL .NET)
01 InputTypeProperty type DependencyProperty public static initialize only
value type DependencyProperty::Register(
"InputType",
type of DialogInputType,
type of AGInputBox,
new FrameworkPropertyMetadata(
type DialogInputType::Text,
new PropertyChangedCallback(method OnInputTypeChanged)
)
).
*> property definition omitted...
method-id OnInputTypeChanged private static.
procedure division using by value sender as type DependencyObject, by value e as type DependencyPropertyChangedEventArgs.
if sender not instance of type AGInputBox
goback
end-if
declare wnd as type AGInputBox = sender as type AGInputBox
if wnd::contentControl::Content instance of type FieldBase *> debugger arrives here
declare textField as type FieldBase
set textField = wnd::contentControl::Content as type FieldBase
attach method wnd::OnFieldTemplateApplied to textField::TemplateApplied
end-if
end method.
VS 调试器显示 ContentControl 的 Content 为 null,但随后 window 被正确可视化,也许它的内容是稍后设置的... 它在以下情况下也是空的: - Window 的加载事件 - Window 的 ContentRendered 事件 而且我无法在 DataTemplate 控件中设置 Loaded RoutedEventHandler,无论是 Loaded="OnFieldLoaded" 还是 Style + EventSetter,因为它被禁止并且不会编译(即使编译器错误建议使用 EventSetter:/ ).
编辑:我尝试了 l33t 的解决方案,但不幸的是,OnContentChanges 从未被调用,即使内容设置正确。 我创建了这个 class:
class-id ClassLibraryNew.Controls.NotifyingContentControl public
inherits type ContentControl.
01 ContentChanged type EventHandler event public.
method-id new public.
procedure division.
invoke super::new()
end method.
method-id OnContentChanged protected override.
procedure division using by value oldContent as object, by value newContent as object.
invoke super::OnContentChanged(oldContent, newContent) *> I put a debugger breakpoint here but it's not getting hit
invoke RaiseContentChanged()
end method.
method-id RaiseContentChanged private.
procedure division.
declare handler as type EventHandler = ContentChanged
declare e as type EventArgs = new EventArgs()
if handler not = null
invoke run handler(by value self, e)
end-if
end method.
end class.
即使您设法确定内容的值,也不能保证这就是您在 UI 中看到的值。
你可以试试这个:
public class ContentControlEx : ContentControl
{
protected override void OnContentChanged(object oldContent, object newContent)
{
// Do stuff...
base.OnContentChanged(oldContent, newContent);
}
}
然后用ContentControlEx
代替常规的
将 DataTemplates
定义为资源并处理 TextField
、IntegerField
和 DecimalField
根元素的 Loaded
事件:
<ContentControl Grid.Row="1" x:Name="contentControl" >
<ContentControl.Resources>
<DataTemplate x:Key="tfTemplate">
<ctrl:TextField ... Loaded="LoadedHandler"/>
</DataTemplate>
<!-- + DataTemplates for IntegerField and DecimalField -->
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Foreground="Red" Text="Errore: input type non valido. Contattare l'assistenza tecnica."/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding InputType}" Value="Text">
<Setter Property="ContentTemplate" Value="{StaticResource tfTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding InputType}" Value="Integer">
<Setter Property="ContentTemplate" Value="{StaticResource ifTemplate}" />
</DataTrigger>
...
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>