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 定义为资源并处理 TextFieldIntegerFieldDecimalField 根元素的 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>