none
给TextBox自定义ControlTemplate后的Width问题 RRS feed

  • 问题

  • 先见代码:

    <Window x:Class="wpfDemo2.textbox"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
        Title="textbox" Height="300" Width="300">
      <Window.Resources>
        <ResourceDictionary>
          <LinearGradientBrush x:Key="TextBoxBorder" EndPoint="0,20" MappingMode="Absolute" StartPoint="0,0">
            <GradientStop Color="#ABADB3" Offset="0.05"/>
            <GradientStop Color="#E2E3EA" Offset="0.07"/>
            <GradientStop Color="#E3E9EF" Offset="1"/>
          </LinearGradientBrush>
          <Style TargetType="{x:Type TextBox}">
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
            <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
            <Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="Padding" Value="1"/>
            <Setter Property="AllowDrop" Value="true"/>
            <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
            <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
            <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
            <Setter Property="Template">
              <Setter.Value>
                <ControlTemplate TargetType="{x:Type TextBox}">
                  <DockPanel>
                    <Microsoft_Windows_Themes:ListBoxChrome x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderFocused="{TemplateBinding IsKeyboardFocusWithin}" SnapsToDevicePixels="true">
                      <ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Width="{TemplateBinding Width}"/>
                    </Microsoft_Windows_Themes:ListBoxChrome>
                    <TextBlock Foreground="Red" FontSize="20" Text="error"></TextBlock>
                  </DockPanel>
                  <ControlTemplate.Triggers>
                    <Trigger Property="IsEnabled" Value="false">
                      <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                      <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                    </Trigger>
                  </ControlTemplate.Triggers>
                </ControlTemplate>
              </Setter.Value>
            </Setter>
          </Style>
        </ResourceDictionary>
      </Window.Resources>
      <Grid>
        <TextBox Height="25" Width="100"></TextBox>
      </Grid>
    </Window>
    
    

    是这样的,当我用ControlTemplate给TextBox后面加了个TextBlock后,我设置textbox的宽度,就会失效。

    然后我给ScrollViewer加了 Width="{TemplateBinding Width}",正常,但是后面的TextBlock被挤掉了,不管把宽度设成多少都看不见了。

    我本来的设想是这样的,width宽度只给textbox加上,而后面的TextBlock都可以一直显示着的。按理说上面的代码应该可以实现我的目的,但不明白为何TextBlock就是显示不出来╮(╯_╰)╭

    2011年5月29日 3:05

答案

  • 你好, Behindmoon

    首先你在Template中选择了使用了DockPanel . 按照DockPanel的布局原则. 放在ListBoxChrome中的ScrollViewer 的宽度只会按内容填充, TextBlock会使用剩下的宽度.

    所以 当ScrollViewer不设定宽度时就是0

    而当设定Width="{TemplateBinding Width}"的情况下 ScrollViewer使用掉了所有宽度. TextBlock就没有可用的宽度了..

    所以你可以将DockPanel换为Grid , 设置两列, 第一列 Width="*" 第二列Width="Auto"

    如果你还想用DockPanel的Dcok效果..可以试试将TextBlock放到ListBoxChrome的前面..


    just another day.
    • 已标记为答案 Behindmoon 2011年6月2日 0:24
    2011年5月29日 11:34

全部回复

  • "我本来的设想是这样的,width宽度只给textbox加上,而后面的TextBlock都可以一直显示着的"

    这个想法跟WPF的布局机制冲突

    ControlTemplate里的Visual无素,会占据整个外面的TextBox的空间,且仅仅只会占据外面TextBox的空间,所以,width给txtbox了,那textblock就没有可以显示的空间了

     

    可以考虑自定议一个UserControl,它由1个TextBox和1个TextBlock组成而成

    给你的自定义控件增加一个依赖属性,比如InnerTextBoxWidth,用于控制内部textbox的宽度

    2011年5月29日 10:10
  • 你好,感谢回复,

    有两点想不明白

    1. 在给Textbox设置width时,这个width到底给了谁,是ScrollViewer吗(没有设定Width="{TemplateBinding Width}"的情况下),显然不是。因为设置width后输入框的宽度没有任何变化。而进行了Width="{TemplateBinding Width}"的设定后,ScrollViewer长度才随着width属性的变化而变化。那么是整个Textbox的空间吗?如果是的话DockPanel内的两个元素为何没有自动填充满空余的空间呢?

    2. 如果我自定义了依赖属性InnerTextBoxWidth给ScrollViewer绑定上,那么TextBlock所占的宽度是怎么体现出来的,Width-InnerTextBoxWidth吗?

     

    2011年5月29日 10:36
  • 你好, Behindmoon

    首先你在Template中选择了使用了DockPanel . 按照DockPanel的布局原则. 放在ListBoxChrome中的ScrollViewer 的宽度只会按内容填充, TextBlock会使用剩下的宽度.

    所以 当ScrollViewer不设定宽度时就是0

    而当设定Width="{TemplateBinding Width}"的情况下 ScrollViewer使用掉了所有宽度. TextBlock就没有可用的宽度了..

    所以你可以将DockPanel换为Grid , 设置两列, 第一列 Width="*" 第二列Width="Auto"

    如果你还想用DockPanel的Dcok效果..可以试试将TextBlock放到ListBoxChrome的前面..


    just another day.
    • 已标记为答案 Behindmoon 2011年6月2日 0:24
    2011年5月29日 11:34
  • 你好,KA_KA07,谢谢解答

    我在自定义控件的过程中遇到了一些疑问,希望能帮忙看看

    1. 我在generic.xaml中设定了自定义控件的样式,然后我想在程序中动态改变它的ControlTemplate,比如说获取焦点之前用的是一个ControlTemplate,获取焦点之后替换成另一个ControlTemplate,并不是换肤的意思。我现在的解决方案是在后台拿到ControlTemplate里的元素后,用C#代码对其样式进行改变,太过冗长,感觉很不好。

    我在后台用Application.Current.Resources["xxx"]拿不到generic.xaml里定义的东西

    2. 自定义控件的默认样式都是在generic.xaml中,我并未在应用程序中找到任何设置此默认样式的信息,也就是说系统强制性地提供了这一行为,如果我要改变默认样式,也就是换肤的话,改怎么做?

     

    刚接触自定义控件,如果有比较详细透彻资料请推荐一下,谢谢

    2011年5月31日 1:38
  • 你好, Behindmoon

    首先是资源查找问题. 定义在Generic.xaml下的资源是不能在Application.Current.Resources集合下找到的..

    你将资源写到App.xaml里 或自己写一个ResourceDictionary 然后merge到app.xaml中 之后就可以用StaticResource

     

     

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
     <ControlTemplate x:Key="TextBoxTemplate1" TargetType="{x:Type TextBox}">
      <Border /><!-- 这里是默认Template -->
     </ControlTemplate>
     
     <ControlTemplate x:Key="TextBoxTemplate2" TargetType="{x:Type TextBox}">
      <Grid /><!-- 这里是聚焦Template -->
     </ControlTemplate>
    
     <Style x:Key="TextBoxStyle" TargetType="{x:Type TextBox}">
      <Setter Property="Template" Value="{StaticResource TextBoxTemplate1}" />
      <Style.Triggers>
       <Trigger Property="IsFocused" Value="True">
        <Setter Property="Template" Value="{StaticResource TextBoxTemplate2}" />
       </Trigger>
      </Style.Triggers>
     </Style>
     
    </ResourceDictionary>
    

     

    不过不建议直接改Template ... 如果聚焦只是一些很简单的边框颜色改变的话 直接再ControlTemplate中使用Trigger就好了..

     

    关于资源:http://msdn.microsoft.com/en-us/library/ms750613.aspx

    关于资源合并:http://msdn.microsoft.com/en-us/library/aa350178.aspx


    just another day.
    2011年5月31日 12:59
  • 你好,KA_KA07

    你的建议很正确,使用在ControlTemplate中使用Trigger比较优雅与合理,但如果遇到要给一个属性值在原来的基础上进行一些运算的话,比如让Height + 2, 这样的话,我不知道用setter如何来实现。

    另外,将控件的样式定义在ResourceDictionary中再合并到App中的话,这样会让自定义控件缺乏独立性,有没有获取Generic.xaml里的元素的方法?

    2011年5月31日 13:53
  • 类似于Height+2 这种只能在code中实现了..

    另外, 如果你是一个新的自定义控件的话.. 也是可以单独建立一个资源文件.. 然后合并到Generic.xaml中 .

    不过这时资源文件定义的一些ResourceKey 只能在本文件使用...  隐性键的话程序中是可以找到的..

     


    just another day.
    2011年5月31日 14:13
  • 我在后台使用
    ((Trigger)this.Template.Triggers[0]).Setters.Add(new Setter(HeightProperty, this.Height + 6, null));
    

    然后抛出异常:使用 SetterBaseCollection 之后(密封),无法对其进行修改。

    好悲剧啊,难道要回归使用后台代码获取模板元素一个属性一个属性改的方式,而且在IsFocused属性改变后还要一个一个改回来-_-|||

    2011年5月31日 14:47