开发者

Custom Control TemplateBinding question

Imagine a form designer with a grid overlay that would represent coordinates on a plane. I'm trying to bind the properties of the grid overlay to the Canvas within a custom ItemsControl.

The grid is created using a VisualBrush. The VisualBrush's Viewbox and Viewport are bound to a Rect in the code, as well are the Height and Width of the Rectangle used to display the grid tile. However, when the control displays, the grid tiles seem to be "infinitely small" (the grid is just grey) in that if I zoom into the grid, the program will eventually just seize up, unable to render it. Obviously, this is not the effect I'm going for.

<Style TargetType="{x:Type Controls:FormControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Controls:FormControl}">
                <Border Background="White"
                        BorderBrush="Black"
                        BorderThickness="1"
                        Padding="49">
                    <Grid Height="{TemplateBinding CanvasHeight}"
                          Width="{TemplateBinding CanvasWidth}">
                        <Grid.Background>
                            <!--"0,0,6,11.3266666666667"-->
                            <VisualBrush TileMode="Tile"
                                         Viewbox="{TemplateBinding GridUnitViewbox}"
                                         ViewboxUnits="Absolute"
                                         Viewport="{TemplateBinding GridUnitViewbox}"
                                         ViewportUn开发者_运维技巧its="Absolute">
                                <VisualBrush.Visual>
                                    <Rectangle Height="{Binding RelativeSource={RelativeSource TemplatedParent},Path=GridUnitViewbox.Height}"
                                               HorizontalAlignment="Left"
                                               Opacity="{TemplateBinding GridOpacity}"
                                               Stroke="Black"
                                               StrokeThickness=".1"
                                               VerticalAlignment="Top"
                                               Width="{Binding RelativeSource={RelativeSource TemplatedParent},Path=GridUnitViewbox.Width}" />
                                </VisualBrush.Visual>
                            </VisualBrush>
                        </Grid.Background>
                        <ItemsPresenter />
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <Canvas />
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="ItemContainerStyle">
        <Setter.Value>
            <Style TargetType="Controls:FormControlItem">
                <Setter Property="Canvas.Left"
                        Value="{Binding Path=X}" />
                <Setter Property="Canvas.Top"
                        Value="{Binding Path=Y}" />
            </Style>
        </Setter.Value>
    </Setter>
</Style>

Any idea what I am doing wrong here. Thanks.

EDIT: Maybe a little more background of what I'm doing may put it in better context. I work for a tax preparation software company and, currently, our forms division creates substitute forms using a markup that was written for our product like a million years ago. It's a bit cumbersome creating forms this way, so I'm developing a visual "Form Designer" for them that will be more like a WYSIWYG and translate the contents of the designer into markup. Well, the IRS is real anal about everything on the form being EXACTLY where it was on the original, so there is a very loose standard by which a "grid overlay" can be placed over the form to determine where things need to go; basically a coordinate plane of sorts.

FormControl is essentially the visual representation of one of these substitute forms that one of the forms designers would be creating.

CanvasWidth and CanvasHeight are CLR wrappers to Dependency Properties. They are assigned values in OnApplyTemplate() by multiplying the dimensions of GridUnitViewbox by however many grid tiles need to be in the grid overlay, ie. a 78x63 grid in most cases.

The names CanvasWidth and CanvasHeight I think might be a little misleading in that they do not refer to the Canvas control, but to the Grid that houses the Canvas (probably need to change the naming convention). That said, CanvasHeight, CanvasWidth and GridUnitViewbox are not dependent on any control's properties, but rather calculations that are done in OnApplyTemplate().

    public static readonly DependencyProperty GridUnitViewboxProperty =
        DependencyProperty.Register("GridUnitViewbox", typeof(Rect), typeof(FormControl),
        new FrameworkPropertyMetadata(new Rect(0, 0, 6, 11.3266666666667),
            FrameworkPropertyMetadataOptions.AffectsMeasure |
            FrameworkPropertyMetadataOptions.AffectsParentMeasure));

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        FormattedText formattedText = new FormattedText(
            "X",
            CultureInfo.GetCultureInfo("en-us"),
            FlowDirection.LeftToRight,
            new Typeface("Courier New"),
            10,
            Brushes.Black);

        this.GridUnitViewbox = new Rect(0, 0, formattedText.WidthIncludingTrailingWhitespace, formattedText.Height);

        if (this.PageLayout == PageLayoutType.Landscape)
        {
            this.CanvasHeight = this.GridUnitViewbox.Height * 48.0;
            this.CanvasWidth = this.GridUnitViewbox.Width * 109.0;
        }
        if (this.PageLayout == PageLayoutType.Portrait)
        {
            this.CanvasHeight = this.GridUnitViewbox.Height * 63.0;
            this.CanvasWidth = this.GridUnitViewbox.Width * 78.0;
        }
    }

The Grid control is actually doing exactly what I want it to do. It is the VisualBrush and the Rectangle within that are either not binding correctly or are not being updated properly. The comment right below <Grid.Background> was the hard-coded testing value that I was using for VisualBrush's Viewbox and Viewport as well as Rectangle's dimensions before I got around to binding the values and it produced, visually, exactly what I was going for. I've also confirmed that 6 and 11.3266666666667 are, in fact, the values for the GridUnitViewbox's dimensions during runtime.

I have a feeling that the binding is producing '0,0,0,0' however, because the grid overlay is just a grey shading that is eating up an immense amount of resources; locks the program, in fact, if you zoom into it. As you can see, in the code I tried adding AffectsMeasure and AffectsParentMeasure to the Metadata options of the dependency property in hopes that perhaps the UI was not updating properly after GridUnitViewbox's dimensions were updated, but I was wrong. I'm not sure what else it could be.


Alright, just in case anyone else encounters a similar issue, I found the workaround. Apparently VisualBrush is a little finicky about TemplateBindings. I adjusted the XAML thusly and it solved the problem:

<VisualBrush TileMode="Tile"
             Viewbox="{Binding RelativeSource={RelativeSource TemplatedParent},Path=GridUnitViewbox}"
             ViewboxUnits="Absolute"
             Viewport="{Binding RelativeSource={RelativeSource TemplatedParent},Path=GridUnitViewbox}"
             ViewportUnits="Absolute">

Here is the article where I got the information from.


What is CanvasWidth and CanvasHeight? Are they defined?

Also, I believe that a canvas has no size unless explicitly specified. When you bind to it, the width/height may be zero.

Try ActualWidth and ActualHeight or the canvas after the rendering pass to see if they contain any values, but afaik they don't unless you provide one.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜