0

I'm coding a WPF input dialog window, that will show a different Control based on a dependency property named InputType. The language I'm using is Visual COBOL .NET, but the issue is not related to the language but to WPF itself, and the language is easily understandable by VB and C# programmers. This is the XAML code for my dialog window

<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>

What I want to achieve is showing the user a 'TextField' (custom TextBox) when the InputType is Text, 'IntegerField' when the InputType is Integer, etc.. InputType's type is an enum name DialogInputType which contains three values (Text, Integer, Decimal). This works fine, however I need a way to to attach an event handler to the Field inside the ContentControl when its content has been correctly set and is not null. I expected the DataTriggers to re-evaluate when the InputType changed, instead this fails: (Visual 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.

The VS debugger shows that the ContentControl's Content is null, but then the window is correctly visualized, and maybe its content is set later... It is also null in: - Loaded Event of the Window - ContentRendered Event of the Window And I can't set a Loaded RoutedEventHandler inside the DataTemplate Control, neither with Loaded="OnFieldLoaded" nor with Style + EventSetter, because it's forbidden and won't compile (even if the compiler error suggests to use the EventSetter :/).

Edit: I tried l33t's solution but unfortunately OnContentChanges is never getting called, even if the content is correctly set. I created this 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.
Sasino
  • 134
  • 2
  • 11
  • "I expected the DataTriggers to re-evaluate when the InputType changed...". They will provided that you implement the INotifyPropertyChanged interface and raise the PropertyChanged event: https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx – mm8 Sep 08 '17 at 11:54
  • Hi, thanks for your reply; InputType is a registered DependencyProperty with a PropertyChangedCallback attached, and when it changes, my method is correctly fired, and after that, the content is correctly set. The problem is that when it gets fired, the content still has to be set... I guess it's something related to ContentTemplate and Content – Sasino Sep 08 '17 at 12:38
  • Is your problem how to switch the template based on the value of the source property named InputType? – mm8 Sep 08 '17 at 12:44
  • No, it correctly works. – Sasino Sep 08 '17 at 13:15
  • My problem is that I want to do some code on the ContentControl's Content when it gets loaded, but I can't find out when – Sasino Sep 08 '17 at 13:18
  • Handle the Loaded event of the TextField, IntegerField and DecimalField controls? – mm8 Sep 08 '17 at 13:23
  • This works if you define the templates as resources. See my answer. – mm8 Sep 08 '17 at 13:29

2 Answers2

1

Even if you manage to determine the value of the content, there is no guarantee that this is the value you see in the UI.

You can try this:

public class ContentControlEx : ContentControl
{
    protected override void OnContentChanged(object oldContent, object newContent)
    {
        // Do stuff...
        base.OnContentChanged(oldContent, newContent);
    }
}

Then use ContentControlEx in place of the regular one.

l33t
  • 18,692
  • 16
  • 103
  • 180
  • Hi thanks for your reply, I tried your solution but unfortunately it did not work, for details please see my updated question. – Sasino Sep 08 '17 at 13:15
1

Define the DataTemplates as resources and handle the Loaded event of the TextField, IntegerField and DecimalField root elements:

<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>
mm8
  • 163,881
  • 10
  • 57
  • 88