使用多重绑定聚焦时文本框的不同格式不会更新源



我想做的很简单,当TextBox有焦点时显示一种格式,当它没有焦点时显示另一种格式。在我的例子中,当不聚焦时,我将一个数字四舍五入到3位,但当聚焦进行编辑时,我会显示实际的整数。

我有一个使用多绑定的相当简单的解决方案,我觉得我几乎做到了。一切都按预期进行,即时窗口中没有错误,但绑定不会更新源。

我使用这种风格来传递我的绑定,以及TextBox是否有焦点到转换器。

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

示例用法:

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

多值转换器就在这里。

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

值得一提的是,我从这篇CodeProject文章中派生了一个解决方案。关键是使用样式中的触发器来切换内容模板。提供的示例并不完美,但它是一次很好的学习体验。

这种方法的唯一缺点是必须在UserControl中定义ContentTemplates和Style,因为ContentTemplates直接引用TextBox事件处理程序。这是因为对TextBox的引用必须传递给代码后面。当您试图覆盖样式时,您将丢失切换ContentTemplate的触发器。

这个缺点对我来说很好,因为我绑定到重要属性(如ContentStringFormat)的应用程序设置。

编辑

以下是完整XAML中更好的方法。我在博客上写了一篇相应的文章。

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

以及示例用法:

<Label Name="TestControl"
       Width="120"
       Content="{Binding Path=MyNumber, Mode=TwoWay}"
       Style="{StaticResource EditableDecimalLabel}"
       />

相关内容

  • 没有找到相关文章

最新更新