2013-04-11 29 views
18

Tôi đang xem this question và phát hiện ra rằng ràng buộc Label.Content với giá trị không phải chuỗi sẽ áp dụng kiểu dáng TextBlock tiềm ẩn, tuy nhiên ràng buộc với một chuỗi thì không.Tại sao một kiểu TextBlock tiềm ẩn được áp dụng khi gắn Label.Content với một chuỗi không phải chuỗi, nhưng không phải là một chuỗi?

Dưới đây là một số mẫu mã để tạo lại vấn đề:

<Window.Resources> 
    <Style TargetType="Label"> 
     <Setter Property="FontSize" Value="26"/> 
     <Setter Property="Margin" Value="10"/> 
     <Setter Property="VerticalAlignment" Value="Center"/> 
    </Style> 
    <Style TargetType="{x:Type TextBlock}"> 
     <Setter Property="FontSize" Value="26"/> 
     <Setter Property="Margin" Value="10"/> 
    </Style> 
</Window.Resources> 

<Grid> 
    <StackPanel Orientation="Horizontal"> 
     <Label Content="{Binding SomeString}" Background="Red"/> 
     <Label Content="{Binding SomeDecimal}" Background="Green"/> 
    </StackPanel> 
</Grid> 

Trường hợp mã cho các giá trị ràng buộc là

SomeDecimal = 50; 
SomeString = SomeDecimal.ToString(); 

Và kết quả cuối cùng trông như thế này, với Margin tài sản từ các ngầm Kiểu nhận dạng TextBlock được áp dụng cho Nhãn được gắn với một chuỗi không phải chỉ:

enter image description here

Cả hai nhãn nhận được kết xuất như

<Label> 
    <Border> 
     <ContentPresenter> 
      <TextBlock /> 
     </ContentPresenter> 
    </Border> 
</Label> 

Khi tôi kiểm tra VisualTree với Snoop, tôi có thể thấy rằng nó trông giống hệt nhau cho cả hai yếu tố, ngoại trừ TextBlock thứ 2 áp dụng Margin từ phong cách tiềm ẩn, trong khi đầu tiên thì không.

enter image description here

Tôi đã sử dụng Blend để kéo ra một bản sao của Label Template mặc định, nhưng không thấy bất cứ điều gì lạ ở đó, và khi tôi áp dụng mẫu cho cả hai nhãn của tôi, điều tương tự sẽ xảy ra.

<Label.Template> 
    <ControlTemplate TargetType="{x:Type Label}"> 
     <Border BorderBrush="{TemplateBinding BorderBrush}" 
       BorderThickness="{TemplateBinding BorderThickness}" 
       Background="{TemplateBinding Background}" 
       Padding="{TemplateBinding Padding}" 
       SnapsToDevicePixels="True"> 
      <ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" 
           Content="{TemplateBinding Content}" 
           ContentStringFormat="{TemplateBinding ContentStringFormat}" 
           HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
           RecognizesAccessKey="True" 
           SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
           VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> 
     </Border> 
     <ControlTemplate.Triggers> 
      <Trigger Property="IsEnabled" Value="False"> 
       <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/> 
      </Trigger> 
     </ControlTemplate.Triggers> 
    </ControlTemplate> 
</Label.Template> 

Nó cũng cần lưu ý rằng thiết lập mặc định ContentTemplate đến một TextBlock không làm cho cả hai mục làm mà không có phong cách tiềm ẩn, vì vậy nó phải có cái gì để làm với khi WPF cố gắng để làm cho một giá trị không theo chuỗi như là một phần của giao diện người dùng.

<Window.Resources> 
    <Style TargetType="Label"> 
     <Setter Property="FontSize" Value="26"/> 
     <Setter Property="Margin" Value="10"/> 
     <Setter Property="VerticalAlignment" Value="Center"/> 
    </Style> 
    <Style x:Key="TemplatedStyle" TargetType="Label" BasedOn="{StaticResource {x:Type Label}}"> 
     <Setter Property="ContentTemplate"> 
      <Setter.Value> 
       <DataTemplate> 
        <TextBlock Text="{Binding }"/> 
       </DataTemplate> 
      </Setter.Value> 
     </Setter> 
    </Style> 
    <Style TargetType="{x:Type TextBlock}"> 
     <Setter Property="FontSize" Value="26"/> 
     <Setter Property="Margin" Value="10"/> 
    </Style> 
</Window.Resources> 

<Grid> 
    <StackPanel Orientation="Horizontal"> 
     <Label Content="{Binding SomeString}" Background="Red"/> 
     <Label Content="{Binding SomeDecimal}" Background="Green"/> 
     <Label Content="{Binding SomeString}" Background="Red" 
       Style="{StaticResource TemplatedStyle}"/> 
     <Label Content="{Binding SomeDecimal}" Background="Green" 
       Style="{StaticResource TemplatedStyle}"/> 
    </StackPanel> 
</Grid> 

enter image description here

gì là logic gây ra một tổ chức phi chuỗi đưa vào giao diện người dùng được rút ra sử dụng một phong cách TextBlock tiềm ẩn, nhưng một chuỗi đưa vào giao diện người dùng không? Và điều này xảy ra ở đâu?

+1

Câu hỏi này hấp dẫn tôi, vì vậy tôi phải bắt đầu phản xạ cũ. Vâng, chỉ có một điều tôi có thể tìm thấy cho đến nay. Có vẻ như TextBlock tạo sự khác biệt giữa văn bản đơn giản và các đối tượng "phức tạp", nó sử dụng hai lớp con khác nhau cho cả hai. Thật không may đây là nơi nó kết thúc. Tôi không thể tìm thấy bất kỳ dấu vết của một phong cách chuyên ngành hoặc bất cứ điều gì. Một điều cần thử là cung cấp một ContentTemplate với một TextBlock và kiểm tra xem điều đó có thay đổi gì đó không. Trong trường hợp đó, nó có thể có một cái gì đó để làm với mẫu mặc định huyền bí đó. – dowhilefor

+0

@dowhilefor Tôi đã thử kéo mẫu mặc định từ Blend và áp dụng nó cho cả hai nhãn, và điều tương tự xảy ra (câu hỏi được cập nhật với thông tin đó). Đoán duy nhất của tôi sẽ là WPF biết để render 'System.String' như là một' TextBlock' mà không có kiểu dáng ngầm, nhưng khi nó đi để render một giá trị không phải chuỗi, nó vẽ nó như một ràng buộc 'TextBlock' thành' .ToString() 'của đối tượng và áp dụng bất kỳ kiểu dáng tiềm ẩn nào. – Rachel

+0

@dowhilefor Bạn là chính xác mặc dù thiết lập mặc định 'ContentTemplate' thành một' 'làm cho cả hai mục hiển thị mà không có kiểu ngầm, vì vậy có vẻ như nó có liên quan đến mặc định 'ContentTemplate' cho các đối tượng không phải là chuỗi – Rachel

Trả lời

2

EDIT: (? Có lẽ di chuyển này để phía dưới)

Và tôi chọc nhiều hơn một chút - và Tôi nghĩ rằng tôi đã đến mấu chốt của vấn đề (w/nhấn mạnh vào 'Tôi nghĩ')

Đặt này vào một số Button1_Click hoặc một cái gì đó (một lần nữa, chúng ta cần phải đi 'lười' về vấn đề này - như chúng ta cần cây thị giác xây dựng - chúng ta không thể làm điều đó trên 'Loaded' như chúng ta chỉ cần thực hiện các mẫu - điều này đòi hỏi kỹ thuật khởi tạo tốt hơn đúng, nhưng nó chỉ là một thử nghiệm để ai quan tâm)

void Button_Click(object sender, EventArgs e) 
{ 
var insideTextBlock = FindVisualChild<TextBlock>(_labelString); 
var value = insideTextBlock.GetProperty<bool>("HasImplicitStyleFromResources"); // false 
value = insideTextBlock.GetProperty<bool>("ShouldLookupImplicitStyles"); // true 

var boundaryElement = insideTextBlock.TemplatedParent; // ContentPresenter and != null 

insideTextBlock = FindVisualChild<TextBlock>(_labelDecimal); 
value = insideTextBlock.GetProperty<bool>("HasImplicitStyleFromResources"); // true 
value = insideTextBlock.GetProperty<bool>("ShouldLookupImplicitStyles"); // true 

boundaryElement = insideTextBlock.TemplatedParent; // == null !! 

Như đã đề cập ở đây Implicit styles in Application.Resources vs Window.Resources?
Các FindImplicitStyleResource (trong FrameworkElement) sử dụng một cái gì đó giống như ...

boundaryElement = fe.TemplatedParent; 

Và dường như nếu không có TemplatedParent (và do cách các TextBlock được xây dựng trong DefaultTemplate) - có là bộ không 'ranh giới' - và tìm kiếm các nguồn lực tiềm ẩn/phong cách - tuyên truyền tất cả các cách.



gốc trả lời: (đọc đầu tiên nếu bạn chỉ cần đến)

(@dowhilefor và @Jehof đã đề cập đến những điều chính)
Tôi không chắc chắn này là một 'câu trả lời' như vậy - nó vẫn là một công việc đoán - nhưng tôi cần thêm không gian để giải thích những gì tôi nghĩ đang diễn ra.

Bạn có thể tìm ra 'ContentPresenter nguồn' mã trên web - nó dễ dàng hơn so với sử dụng phản xạ - chỉ 'google' cho nó, tôi không đăng nó ở đây vì những lý do rõ ràng :)

Đó là về ContentTemplate được chọn cho ContentPresenter (và theo thứ tự này) ...

ContentTemplate // if defined 
ContentTemplateSelector // if defined 
FindResource // for typeof(Content) - eg if defined for sys:Decimal takes that one 
DefaultTemplate used internally by the presenter 
...specific templates are chosen based on typeof(Content) 

và thực sự nó không có bất cứ điều gì để làm với Label nhưng bất kỳ ContentControl hoặc kiểm soát mẫu có sử dụng ContentPresenter. Hoặc bạn có thể liên kết với tài nguyên, vv

Đây là một repro của những gì đang xảy ra bên trong - mục tiêu của tôi là để tái tạo hành vi tương tự cho 'chuỗi' hoặc bất kỳ loại nội dung.

Trong XAML chỉ 'tên' các nhãn (và nó không phải là một lỗi đánh máy, một cố tình đưa chuỗi trong cả hai để san bằng sân chơi loại) ...

<Label Name="_labelString" Content="{Binding SomeString}" Background="Red"/> 
<Label Name="_labelDecimal" Content="{Binding SomeString}" Background="Green"/> 

Và từ mã đằng sau (mã tối thiểu mà loại bắt chước những gì người dẫn chương trình thực hiện):
lưu ý: tôi đã làm nó trên Loaded như tôi cần truy cập vào người dẫn chương trình mặc nhiên tạo

void Window1_Loaded(object sender, RoutedEventArgs e) 
{ 
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(TextBlock)); 
factory.SetValue(TextBlock.TextProperty, new TemplateBindingExtension(ContentProperty)); 
var presenterString = FindVisualChild<ContentPresenter>(_labelString); 
presenterString.ContentTemplate = new DataTemplate() { VisualTree = factory }; 

// return; 

var presenterDecimal = FindVisualChild<ContentPresenter>(_labelDecimal); 
presenterDecimal.ContentTemplate = new DataTemplate(); 
// just to avoid the 'default' template kicking in 

// this is what 'default template' does actually, the gist of it 
TextBlock textBlock = new TextBlock(); 
presenterDecimal.SetProperty(typeof(FrameworkElement), "TemplateChild", textBlock); 
textBlock.Text = presenterDecimal.Content.ToString(); 

phần đầu tiên (cho _labelString) làm những gì 'text' mẫu thực hiện cho chuỗi.

Nếu bạn return ngay sau đó - bạn sẽ nhận được hai hộp tìm kiếm giống nhau, không có mẫu ẩn.

Phần thứ hai (đối với _labelDecimal) bắt chước 'mẫu mặc định' được gọi cho 'thập phân'.

Kết quả cuối cùng sẽ hoạt động giống như ví dụ ban đầu. Chúng tôi xây dựng các mẫu như cho stringdecimal - nhưng chúng tôi có thể đặt bất kỳ nội dung nào vào nội dung (nếu nó có ý nghĩa tất nhiên).

Là tại sao - tôi đoán là một cái gì đó như thế này (mặc dù cách xa nào đó - người sẽ nhảy vào với một cái gì đó hợp lý hơn tôi đoán) ...

Theo liên kết này FrameworkElementFactory

Lớp này là cách không dùng nữa để tạo mẫu theo cách lập trình, là các lớp con của FrameworkTemplate như ControlTemplate hoặc DataTemplate; không phải tất cả chức năng mẫu đều có sẵn khi bạn tạo mẫu bằng cách sử dụng lớp này. Cách được đề xuất để tạo mẫu theo chương trình là tải XAML từ chuỗi hoặc luồng bộ nhớ bằng cách sử dụng phương thức Tải của lớp XamlReader.

Và tôi đoán nó không gọi bất kỳ kiểu được xác định nào cho TextBlock.

Trong khi 'mẫu khác' (mẫu mặc định) - thực sự xây dựng TextBlock và ở đâu đó dọc theo các dòng đó - nó thực sự chọn kiểu ẩn.

Thành thật mà nói, đó là nhiều như tôi đã có thể kết luận, ngắn đi qua toàn bộ WPF 'internals' và làm thế nào/nơi thực sự phong cách được áp dụng.


Tôi đã sử dụng mã này Finding control within WPF itemscontrol cho FindVisualChild.
SetProperty chỉ là sự phản ánh - cho một thuộc tính mà chúng tôi cần truy cập để có thể thực hiện tất cả điều này. ví dụ.

public static void SetProperty<T>(this object obj, string name, T value) { SetProperty(obj, obj.GetType(), name, value); } 
public static void SetProperty<T>(this object obj, Type typeOf, string name, T value) 
{ 
    var property = typeOf.GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); 
    property.SetValue(obj, value, null); 
} 
0

Sau khi xem qua câu hỏi này và nhận xét có giá trị từ tất cả, tôi đã thực hiện một số nghiên cứu về Tạo kiểu TextBlock.

Để hiểu biết của tôi vấn đề ở đây không phải với Nhãn hoặc TextBlock, đó là với contentpresenter và điều khiển sử dụng contentpresenter như Label, nút và ComboBoxItem.

Một trong những đặc tính của người dẫn chương trình nội dung từ MSDN: http://msdn.microsoft.com/en-us/library/system.windows.controls.contentpresenter.aspx

"Nếu có một TypeConverter có thể chuyển đổi các loại nội dung cho một chuỗi, ContentPresenter sử dụng mà TypeConverter và tạo ra một TextBlock để chứa chuỗi đó. TextBlock được hiển thị "

Trong ví dụ trên, Đối SomeString Content người dẫn chương trình là chuyển đổi nó thành TextBlock và áp dụng biên độ TextBlock (10) cùng với Label lề (10) khiến nó là 20.

Để tránh trường hợp này, bạn cần ghi đè kiểu TextBlock trong contentpresenter như được hiển thị bên dưới

      <ContentPresenter > 
           <ContentPresenter.Resources> 
            <Style TargetType="{x:Type TextBlock}"> 
             <Setter Property="Margin" Value="5" /> 
            </Style> 
           </ContentPresenter.Resources> 
          </ContentPresenter> 

Sau đây là những thay đổi đối với mã của bạn.

<Window.Resources> 
     <Style TargetType="Label"> 
      <Setter Property="FontSize" Value="26"/> 
      <Setter Property="Margin" Value="10"/> 

      <Setter Property="VerticalAlignment" Value="Center"/> 
      <Setter Property="Template"> 
       <Setter.Value> 

        <ControlTemplate TargetType="Label"> 
         <Grid> 
          <Rectangle Fill="{TemplateBinding Background}" /> 
          <ContentPresenter > 
           <ContentPresenter.Resources> 
            <Style TargetType="{x:Type TextBlock}"> 
             <Setter Property="Margin" Value="5" /> 
            </Style> 
           </ContentPresenter.Resources> 
          </ContentPresenter> 
         </Grid> 

        </ControlTemplate> 
       </Setter.Value> 
      </Setter> 
     </Style> 
     <Style TargetType="{x:Type TextBlock}"> 
      <Setter Property="FontSize" Value="26"/> 
      <Setter Property="Margin" Value="10"/> 
      <Setter Property="Foreground" Value="Pink" /> 
     </Style> 
    </Window.Resources> 

    <Grid> 
     <StackPanel Orientation="Horizontal"> 
      <Label Content="{Binding SomeString}" Background="Red" /> 
      <Label Content="{Binding SomeDecimal}" Background="Green"/> 
     </StackPanel> 
    </Grid> 
</Window> 

Giải thích này chỉ dựa trên hiểu biết của tôi. Hãy cho tôi biết ý kiến ​​của bạn.

Cảm ơn

+0

Tôi thực sự đã thêm một chút vào câu hỏi của mình, điều này giải thích rằng nếu bạn ghi đè 'ContentTemplate' của Nhãn, nó sẽ ngừng áp dụng kiểu TextBlock ngầm định.Câu hỏi của tôi là nhiều hơn về lý do tại sao đặt một chuỗi trong giao diện người dùng * * áp dụng kiểu ẩn, trong khi đặt một chuỗi không (sau đó được chuyển thành chuỗi) trong giao diện người dùng thì không. Tôi nghĩ rằng bạn có thể được gần gũi với các báo MSDN mặc dù. Tôi nghi ngờ rằng quá trình sử dụng 'TypeConverter' để tạo một' TextBlock' đang áp dụng bất kỳ kiểu ngầm nào, bất kể các ranh giới mẫu. – Rachel

+0

@Rachel - 'TypeConverter' chỉ là nơi tôi đã tạo' textBlock.Text = presenter.Content.ToString(); '(Tôi có thể đăng mã - và liên kết đầy đủ nếu bạn muốn - nhưng tôi chỉ miễn cưỡng chia sẻ điều đó) - nó điền vào các văn bản ở rất 'kết thúc'. Nó không liên quan gì đến việc tạo TextBlock (các mẫu làm). Xem 'EDIT' của tôi - vấn đề là với' ranh giới' thực sự. – NSGaga

0

Theo nhận xét của tôi, tôi thêm thông tin khác cho câu hỏi. Nó không phải là một câu trả lời trực tiếp, nhưng cung cấp thông tin bổ sung cho vấn đề được mô tả.

XAML bên dưới sẽ hiển thị hành vi được mô tả trực tiếp trong Nhà thiết kế của Visual Studio và tôi đã thu hẹp nó xuống ContentPresenter, dường như là nguồn gốc của vấn đề. Kiểu được áp dụng cho cả ContentPresenter đầu tiên (intPresenterboolPresenter), nhưng không phải là kiểu cuối cùng sử dụng chuỗi dưới dạng Nội dung (stringPresenter).

<Window.Resources> 
    <system:Int32 x:Key="intValue">5</system:Int32> 
    <system:Boolean x:Key="boolValue">false</system:Boolean> 
    <system:String x:Key="stringValue">false</system:String> 
    <Style TargetType="{x:Type TextBlock}"> 
    <Setter Property="FontSize" Value="26" /> 
    <Setter Property="Margin" Value="10" /> 
    </Style> 
</Window.Resources> 

<Grid> 
    <StackPanel Orientation="Horizontal"> 
    <ContentPresenter x:Name="intPresenter" 
         VerticalAlignment="Center" 
         Content="{StaticResource intValue}" /> 
    <ContentPresenter x:Name="boolPresenter" 
         VerticalAlignment="Center" 
         Content="{StaticResource boolValue}" /> 
    <ContentPresenter x:Name="stringPresenter" 
         VerticalAlignment="Center" 
         Content="{StaticResource stringValue}" /> 
    </StackPanel> 
    </Grid> 

Trong debugger tôi đã phân tích rằng stringPresenter sử dụng DefaultStringTemplate trong khi intPresenter không.

enter image description here

của nó cũng thú vị mà các Language của intPresenter được thiết lập, trong khi do stringPresenter của nó không.

Và việc thực hiện phương pháp này trông giống như thế (lấy từ dotPeek)

private bool IsUsingDefaultStringTemplate 
    { 
     get 
     { 
     if (this.Template == ContentPresenter.StringContentTemplate || this.Template == ContentPresenter.AccessTextContentTemplate) 
      return true; 
     DataTemplate dataTemplate1 = ContentPresenter.StringFormattingTemplateField.GetValue((DependencyObject) this); 
     if (dataTemplate1 != null && dataTemplate1 == this.Template) 
      return true; 
     DataTemplate dataTemplate2 = ContentPresenter.AccessTextFormattingTemplateField.GetValue((DependencyObject) this); 
     return dataTemplate2 != null && dataTemplate2 == this.Template; 
     } 
    } 

Các StringContentTemplate và AccessTextTemplate đang sử dụng một FrameworkElementFactory để tạo ra Visuals.

Các vấn đề liên quan