3

I have a custom control that is used in a form, this control combines a label and a textbox. When the form is in read only mode the control is changed to show a label instead of the textbox. To do this, I have defined a Content Template for my control, as follows:

<Style TargetType="{x:Type Controls:FormFieldControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="200"/>
                        <ColumnDefinition Width="200"/>
                    </Grid.ColumnDefinitions>

                    <AdornerDecorator Grid.Column="1"
                                      Visibility="{Binding RelativeSource={RelativeSource AncestorType=Controls:FormFieldControl}, 
                                                   Path=IsReadOnly, 
                                                   Converter={StaticResource BooleanToInverseVisibilityConverter}, 
                                                   Mode=OneWay}">
                        <ContentPresenter Name="ContentPresenter" />
                    </AdornerDecorator>

                    <AdornerDecorator Grid.Column="1"
                                      Visibility="{Binding RelativeSource={RelativeSource AncestorType=FormFieldControl}, 
                                      Path=IsReadOnly, Converter={StaticResource BooleanToVisibilityConverter}, 
                                      Mode=OneWay}">
                        <ContentPresenter Name="ReadOnlyContentPresenter" />
                    </AdornerDecorator>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

ReadOnlyContentPresenter's content is a label that's been applied the following style:

<Style x:Key="ReadOnlyFormFieldControl" TargetType="{x:Type Label}">
    <Setter Property="Width" Value="400" />
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=Controls:FormFieldControl}, 
                               Path=ReadOnlyWidth, 
                               Converter={StaticResource IsSetConverter}}"
                     Value="True">
            <Setter Property="Width" 
                    Value="{Binding RelativeSource={RelativeSource AncestorType=Controls:FormFieldControl}, Path=ReadOnlyWidth}" />
        </DataTrigger>
    </Style.Triggers>
</Style>

This works fine for about 80 controls, but when I add more controls to the form then the binding in the label style fails to find the source (error 4):

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='MyProject.Controls.FormFieldControl', AncestorLevel='1''. BindingExpression:Path=ReadOnlyWidth; DataItem=null; target element is 'Label' (Name=''); target property is 'ReadOnlyWidth' (type 'Double?')

The same happens with the style applied to the editable part of the control. I think the issue is related to the amount of controls being processed, if I move the code of control no. 81 (failing) right above control no. 80 (working), the binding in control no. 81 works now, but the one in control no. 80 fails. I did this for several controls, and the behaviour is consistent.

I'm still not clear as to how the AdornerDecorator works exactly, and while investigating I found this question that mentions a problem with having more than 144 adorners in an adornerlayer, so I removed the AdornerDecorator's in my code only to find that the binding issue that was happening to controls no. 81 and over is now happening to all my FormFieldControl's.

Finally, I decided to try with ContentControl's instead of ContentPresenter's in my control template. This works fine for all the controls in the form. I know that ContentPresenter is a lightweight form of ContentControl and that it's more suitable to use inside a content template than theContentControl is. However, I don't quite understand why the bindings fail when using a ContentPresenter and work when using a ContentControl.

I also find it odd that only a limited set of controls work, and when not using the AdornerDecorator none of them will find the binding source.

Has anyone come across something similar? Is there something I'm doing wrong with the ContentPresenter? Or if this is expected behaviour, could someone help me understand what's going on?

Any ideas are appreciated.

4

2 回答 2

0

我认为原因可能是这个:

var contentPresenter = new ContentPresenter { Content = new Button() };
var contentControl = new ContentControl { Content = new Button() };
if ((contentPresenter.Content as FrameworkElement).Parent == null)
    Debug.WriteLine("ContentPresenter won't let you get ancestors");
if ((contentControl.Content as FrameworkElement).Parent != null)
    Debug.WriteLine("ContentControl will let you get ancestors");

两行都显示,这意味着 contentPresenter 是罕见的未正确设置“父”属性的 FrameworkElement 之一。

于 2014-10-21T10:24:42.890 回答
0

从您的问题中不清楚标签是仅显示文本框中的内容还是必须显示自定义标签,但如果是前者,那么我认为您应该只使用IsReadOnly已经提到的属性。

您可以定义一个自定义,它在只读时ControlTemplate隐藏背景和边框,TextBox使其看起来好像只是一个Label.

试试这个简单的例子:

<StackPanel>
    <StackPanel.Resources>
        <Style x:Key="{x:Type TextBox}"
               TargetType="{x:Type TextBoxBase}">
            <Setter Property="SnapsToDevicePixels"
                    Value="True" />
            <Setter Property="OverridesDefaultStyle"
                    Value="True" />
            <Setter Property="KeyboardNavigation.TabNavigation"
                    Value="None" />
            <Setter Property="FocusVisualStyle"
                    Value="{x:Null}" />
            <Setter Property="MinWidth"
                    Value="120" />
            <Setter Property="MinHeight"
                    Value="20" />
            <Setter Property="AllowDrop"
                    Value="true" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TextBoxBase}">
                        <Border Name="Border"
                                CornerRadius="2"
                                Padding="2"
                                Background="AliceBlue"
                                BorderBrush="Black"
                                BorderThickness="1">
                            <ScrollViewer Margin="0"
                                          x:Name="PART_ContentHost" />
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsReadOnly"
                                     Value="True">
                                <Setter TargetName="Border"
                                        Property="Background"
                                        Value="Transparent" />
                                <Setter TargetName="Border"
                                        Property="BorderBrush"
                                        Value="Transparent" />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </StackPanel.Resources>
    <TextBox Margin="50,25"
             Name="tb" />

    <Button Content="Switch modes"
            Margin="50,25"
            Click="Button_Click" />
</StackPanel>

您将需要以下按钮单击处理程序来切换只读模式:

private void Button_Click(object sender, RoutedEventArgs e)
{
    tb.IsReadOnly = !tb.IsReadOnly;
}

这是TextBox可编辑时的结果:

在此处输入图像描述

TextBox就是只读时的样子。

在此处输入图像描述

于 2012-11-27T17:01:40.937 回答