我想做的很简单,当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}"
/>