문제

What I want to do is simple, show one formatting when a TextBox has focus and another when it doesn't. In my case I'm rounding a number to 3 digits when not focused but showing the actual, entire number when focused for editing.

I have a fairly simple solution using a multibinding and I feel like I'm almost there. Everything works as expected and there are no errors in the immediate window, but the binding won't update the source.

I'm using this style to pass my binding and whether or not the TextBox has focus to the converter.

<Style x:Key="RoundedTextBox" TargetType="{x:Type ContentControl}">
    <Setter Property="Focusable" Value="False"/>
    <Setter Property="ContentTemplate">
        <Setter.Value>
            <DataTemplate>
                <TextBox x:Name="TB" TextAlignment="Right" DataContext="{TemplateBinding Content}">
                    <TextBox.Text>
                        <MultiBinding Converter="{StaticResource DecRounder}" UpdateSourceTrigger="PropertyChanged">
                            <MultiBinding.Bindings>
                                <Binding ElementName="TB" Path="DataContext" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" BindsDirectlyToSource="True" />
                                <Binding ElementName="TB" Path="IsFocused" Mode="OneWay" />
                            </MultiBinding.Bindings>
                        </MultiBinding>
                    </TextBox.Text>
                </TextBox>
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>

Example usage:

<ContentControl Style="{StaticResource RoundedTextBox}"
                Content="{Binding Path=Model.SomeNumber}" />

And the multi-value converter is here.

public class DecimalRoundingConverter : IMultiValueConverter
{

    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {

        if (values.Length != 2)
            throw new Exception("Invalid number of values for DecimalRoundingConverter!");

        if (values[0] is string)
            return values[0];

        if (values[0] != null && !(values[0] is decimal))
            throw new Exception("Invalid type for first value used with DecimalRoundingConverter!");
        if (!(values[1] is bool))
            throw new Exception("Invalid type for second value used with DecimalRoundingConverter!");
        if (targetType != typeof(string))
            throw new Exception("Invalid target type used with DecimalRoundingConverter!");

        if (values[0] == null)
            return null;

        decimal number = (decimal)values[0];

        bool isFocused;
        if (values[1] == null)
            isFocused = true;
        else if (values[1] is bool)
            isFocused = (bool)values[1];
        else
            if (!bool.TryParse((string)values[1], out isFocused))
                throw new Exception("Invalid converter parameter used with DecimalRoundingConverter!");

        return string.Format(isFocused ? "{0:.#############################}" : "{0:.###}", number);

    }

    public object[] ConvertBack(object value, Type[] targetType, object parameter, System.Globalization.CultureInfo culture)
    {

        decimal d;
        var ret = new object[2];

        if (value == null)
            ret[0] = null;
        else if (decimal.TryParse((string)value, out d))
            ret[0] = d;
        else
            ret[0] = value;

        ret[1] = Binding.DoNothing;

        return ret;

    }

}
도움이 되었습니까?

해결책

For what it's worth, I derived a solution from this CodeProject article. The key is using a Trigger in the style to switch the content template. The provided example isn't perfect, but it was a good learning experience.

The only drawback to this approach is that the ContentTemplates and Style must be defined in the UserControl since the ContentTemplates refer directly to TextBox event handlers. This is because the reference to the TextBox must be passed to the code behind. When you try to override the style you'll lose the trigger that switches ContentTemplate.

This drawback was fine for me since I'm binding to an application setting for important properties, like ContentStringFormat.

EDIT

Here's a better method in full XAML. I wrote up a corresponding article on my blog.

<ControlTemplate x:Key="EditableDecimalTemplate" TargetType="{x:Type ContentControl}">
    <ContentPresenter Name="contentHolder" 
                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                            RecognizesAccessKey="True" 
                            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
        <ContentPresenter.Content>
            <Grid Margin="0">
                <Border Name="nonFocusedBorder"
                        Grid.ZIndex="3" IsHitTestVisible="False"
                        BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" 
                        Background="{TemplateBinding Background}" 
                        />
                <TextBox Name="editTextBox"
                            Grid.ZIndex="1" Opacity="0"
                            Margin="0" Padding="{TemplateBinding Padding}"
                            HorizontalAlignment="Stretch" VerticalAlignment="Center"
                            TextAlignment="Right"
                            Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content, Mode=TwoWay}"
                            BorderThickness="{TemplateBinding BorderThickness}" 
                            />
                <Border BorderBrush="{x:Null}" Height="{Binding ElementName=editTextBox, Path=ActualHeight}" Margin="0,0,3,0"
                        Padding="{TemplateBinding BorderThickness}">
                    <TextBlock Name="displayTextBlock" 
                                Grid.ZIndex="2" IsHitTestVisible="False"
                                VerticalAlignment="Center" HorizontalAlignment="Stretch" 
                                Margin="{TemplateBinding Padding}"
                                TextAlignment="Right"
                                Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content, StringFormat={}{0:#.###;-#.###;0}, Mode=OneWay}"
                                />
                </Border>
                <Border/>
            </Grid>
        </ContentPresenter.Content>
    </ContentPresenter>
    <ControlTemplate.Triggers>
        <Trigger SourceName="editTextBox" Property="IsKeyboardFocused" Value="True">
            <Setter TargetName="displayTextBlock" Property="Opacity" Value="0" />
            <Setter TargetName="editTextBox" Property="Opacity" Value="1" />
            <Setter TargetName="nonFocusedBorder" Property="Visibility" Value="Collapsed"/>
        </Trigger>
        <Trigger Property="BorderThickness" Value="0">
            <Setter TargetName="editTextBox" Property="BorderThickness" Value="1" />
            <Setter TargetName="nonFocusedBorder" Property="BorderThickness" Value="1" />
            <Setter TargetName="nonFocusedBorder" Property="BorderBrush" Value="Transparent" />
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

<Style x:Key="EditableDecimalLabel" TargetType="{x:Type Label}">
    <Setter Property="Template" Value="{StaticResource EditableDecimalTemplate}" />
    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
    <Setter Property="VerticalContentAlignment" Value="Stretch"/>
    <Setter Property="Padding" Value="4" />
    <Setter Property="FontFamily" Value="Consolas, Lucida Console, Courier New"/>
    <Setter Property="TextElement.FontSize" Value="13" />
    <Setter Property="SnapsToDevicePixels" Value="True"/>
    <Setter Property="BorderThickness" Value="1" />
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="BorderBrush" Value="#B5CFFF"/>
        </Trigger>
    </Style.Triggers>
</Style>

And sample usage:

<Label Name="TestControl"
       Width="120"
       Content="{Binding Path=MyNumber, Mode=TwoWay}"
       Style="{StaticResource EditableDecimalLabel}"
       />
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top