none
Data binding to a VisualBrush viewbox RRS feed

  • Question

  • Hi,

    I know that you cant bind to the viewbox property because it is a Rect and doesnt derrive from DependencyObject (is that correct?) but can someone please tell how to work around this?

    I basically want to show several croped sections of an ink canvas. These croped sections appear in a list box, hence the use of controltemplates and datatemplates. If i could just bind the viewbox it would all be okay....but i cant Sad

    Here is the relevent bit of xaml:

        <ControlTemplate x:Key="ThumbnailTemplate">
          <StackPanel Margin="0,0,0,0" Width="Auto" Height="Auto" Orientation="Horizontal" Background="{x:Null}">
            <Viewbox Width="72" Height="72" Margin="2,2,2,2">
              <Rectangle Width="600" Height="600" Stroke="Black" Margin="5,0,5,0">
                <Rectangle.Fill>
                  <VisualBrush Viewbox="{Binding Path=Bounds}"> <-- How do i do this?
                    <VisualBrush.Visual>
                      <InkCanvas  
                                   Width="600"
                                   Height="600"
                                   Background="{x:Null}"
                                   EditingMode="None"                              
                                   Strokes="{Binding Path=Strokes}" Margin="0,0,0,0" x:Name="InkCanvas">               
                      </InkCanvas>
                    </VisualBrush.Visual>
                  </VisualBrush>
                </Rectangle.Fill>
              </Rectangle>
            </Viewbox>
            <TextBlock Text="{Binding Path=Name}" Margin="5,0,5,0" Width="Auto" Height="Auto" TextWrapping="Wrap" VerticalAlignment="Center"/>
          </StackPanel>
        </ControlTemplate>

        <!-- A data template for a single thumbnial -->
        <DataTemplate x:Key="ThumbnailListTemplate">
          <ContentControl Template="{StaticResource ThumbnailTemplate}" />
        </DataTemplate>   
    Thursday, January 10, 2008 5:39 PM

Answers

  • I've quickly mocked up with an XAMLPad ready example of how to enable this type of binding:

    Code Block
    <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <
    Page.Resources>
    <
    x:Array Type="{x:Type Rect}" x:Key="data">
    <
    Rect>0,0,0.5,0.5</Rect>
    <
    Rect>0.5,0,1,0.5</Rect>
    <
    Rect>0,0.5,0.5,1</Rect>
    <
    Rect>0.5,0.5,1,1</Rect>
    </
    x:Array>
    </
    Page.Resources>
    <
    StackPanel>
    <
    Border
    Width="300"
    Height="200"
    BorderBrush="Green"
    BorderThickness="1"
    Name="border"
    Margin="5">
    <
    InkCanvas Name="inkCanvas"/>
    </
    Border>

    <
    ListBox
    Width="400"
    Height="300"
    Margin="5"
    BorderBrush="Green"
    BorderThickness="1"
    ItemsSource="{StaticResource data}">
    <
    ListBox.ItemTemplate>
    <
    DataTemplate>
    <
    Rectangle Margin="2" Width="300" Height="80" Stroke="Green" StrokeThickness="1">
    <
    Rectangle.Fill>
    <
    VisualBrush Viewbox="{Binding}" Visual="{Binding ElementName=inkCanvas}"/>
    </
    Rectangle.Fill>
    </
    Rectangle>
    </
    DataTemplate>
    </
    ListBox.ItemTemplate>
    </
    ListBox>
    </
    StackPanel>
    </
    Page>

    Freezable objects like VisualBrush is not part of the element tree (neither logical tree or visual tree), but in order to enable data binding for them, WPF uses a special (sorta hackery) method called "inheritance context", so when the binding set up on the Freezable is looking for the binding source, it will refers to its inheritance context point (in this case, the Rectangle will be its inheritance context point).

    From my educated guess, in the future version of WPF, more inheritance context scenario will be enabled, such as ToolTip, ContextMenu etc.

    Hope this helps

    Tuesday, January 15, 2008 3:01 AM

All replies

  • Viewbox is a dependency property, so it does support data binding.
    What's the type of the "Bounds" property?
    If it's of Rect type, so you should be able to directly bind it to the Viewbox property, if not, you can write a custom IValueConverter to do the type conversion.

    Hope this helps
    Monday, January 14, 2008 4:29 AM
  • Hmmm. i cant directly bind to it unless i am missing something?

    Viewbox is of type Rect...which is not derrived from depedency object.

    I can do this:

    <VisualBrush Viewbox = "0,0,0.50.5" ...

    but i cant do this:

    <VisualBrush Viewbox = {Binding Path=Bounds} ...

    I dont see how writing a custom IValueConverter can help (i am probably not understanding correctly) because i want to convert to a Rect from a bound value, not a literal one.
    Monday, January 14, 2008 9:35 AM
  • The target object must be a dependency object... not the type of the target property.  In your case, the target object is a VisualBrush, which is a DO and the target property is Viewbox, which, as Marco points out, is a DP.

     

    <EDIT>

    Neither the VisualBrush nor the InkCanvas belong to the element tree, so you will be hardpressed to establish the bindings in . . .

    <SNIP>

    Removing the remainder of the felonious information posted at 4 AM by some drunken impersonator of Dr. WPF. Wink

    </SNIP>

    Please see Marco's excellent example below for the correct answer. Big Smile

    </EDIT>

    Monday, January 14, 2008 12:21 PM
  • I've quickly mocked up with an XAMLPad ready example of how to enable this type of binding:

    Code Block
    <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <
    Page.Resources>
    <
    x:Array Type="{x:Type Rect}" x:Key="data">
    <
    Rect>0,0,0.5,0.5</Rect>
    <
    Rect>0.5,0,1,0.5</Rect>
    <
    Rect>0,0.5,0.5,1</Rect>
    <
    Rect>0.5,0.5,1,1</Rect>
    </
    x:Array>
    </
    Page.Resources>
    <
    StackPanel>
    <
    Border
    Width="300"
    Height="200"
    BorderBrush="Green"
    BorderThickness="1"
    Name="border"
    Margin="5">
    <
    InkCanvas Name="inkCanvas"/>
    </
    Border>

    <
    ListBox
    Width="400"
    Height="300"
    Margin="5"
    BorderBrush="Green"
    BorderThickness="1"
    ItemsSource="{StaticResource data}">
    <
    ListBox.ItemTemplate>
    <
    DataTemplate>
    <
    Rectangle Margin="2" Width="300" Height="80" Stroke="Green" StrokeThickness="1">
    <
    Rectangle.Fill>
    <
    VisualBrush Viewbox="{Binding}" Visual="{Binding ElementName=inkCanvas}"/>
    </
    Rectangle.Fill>
    </
    Rectangle>
    </
    DataTemplate>
    </
    ListBox.ItemTemplate>
    </
    ListBox>
    </
    StackPanel>
    </
    Page>

    Freezable objects like VisualBrush is not part of the element tree (neither logical tree or visual tree), but in order to enable data binding for them, WPF uses a special (sorta hackery) method called "inheritance context", so when the binding set up on the Freezable is looking for the binding source, it will refers to its inheritance context point (in this case, the Rectangle will be its inheritance context point).

    From my educated guess, in the future version of WPF, more inheritance context scenario will be enabled, such as ToolTip, ContextMenu etc.

    Hope this helps

    Tuesday, January 15, 2008 3:01 AM
  • Nice sample, Marco!

     

    Yes, DependencyObject.InheritanceContext is what enables cool features like binding individual stops of a gradient brush or individual transform properties to data item properties.  I've removed the disinformation from my prior post to protect the innocent.  Wink

     

    Kev__msdn,

     

    In your original post, you had explicitly assigned an InkCanvas to the Visual property of your VisualBrush.  You were binding the Strokes property of the InkCanvas to a Strokes property on your data item.  In this manner, you would get distinct InkCanvas objects (one per templated item).  Is this really what you want? 

     

    In Marco's example, a single InkCanvas is being used and the Visual property of the VisualBrush within the item template is bound to it.  This is a more typical usage scenario.

     

    If you do really need a separate InkCanvas for each item, then you probably don't need a VisualBrush at all.  You can simply place an InkCanvas directly in your data template and use a Viewbox element to get the Viewbox behaviors.  You will gain definite performance benefits by doing this.

     

    There is a perf cost associated with every TileBrush (e.g., VisualBrush).  You must pay the price of software intermediate render targets (IRTs).  Specifically, the visuals must be rendered to a software target prior to rendering to the hardware.  Since you are not using the actual visual (the InkCanvas) for anything other than rendering, there really is no reason to have a visual brush.

     

    Having said that, it should work.  If your template is not working, you should verify that there is indeed a property called Bounds on the target data item.  If the data item is a dependency object, make sure the Bounds property is a DP.  If the data item is just a CLR object, make sure the class implements INotifyPropertyChanged and raises change notifications for the Bounds property.

     

    If the binding is not resolving, you should see debug output when running under the debugger.  Or you can insert a value converter, as Marco suggested originally, in the binding to see what value is coming into the converter.

    Tuesday, January 15, 2008 6:21 AM
  • Hi, many thaks for all the help,

     

    I have changed the code somewhat to be more like Marco's example - i am still using a visual brush.

     

    I now have an observable collection of objects, each one has an associated ink canvas. There is a list box down the side of the application that is bound to this observable collection and displays the canvases as thumbnails. When one of these items is selected the ink canvas is shown in the main area of the app.

     

    What i am really trying to achieve is to allow these ink canvas objects to have 'views'. Such that a view is a cropped section of the canvas that can still be drawn on and that affects any other 'views' of the canvas so that everything updates at the same time.

     

    I gave up on binding the stroke collection, i bind the whole canvas now - because i need to capture more than just the strokes.

     

    To be honest, the only reason i am using a VisualBrush is because i want a quick and easy way to display just a part of the inkCanvas. I have experimented with the Clip property but I want the clipped region to fill the screen so i then have to mess about with transforms etc.

     

    I was suprised to learn about the performance hit! If you have any other advise on more elegant ways to achieve this then i am all ears! I am still struggling to find my WPF feet!

     

    Tuesday, January 15, 2008 9:46 AM
  • It sounds like you are headed down the correct path by following Marco's example.

     

     Kev_msdn wrote:

     

    What i am really trying to achieve is to allow these ink canvas objects to have 'views'. Such that a view is a cropped section of the canvas that can still be drawn on and that affects any other 'views' of the canvas so that everything updates at the same time.

     

     

    That's not easily done.  A VisualBrush can definitely be used to create the views, but those views are just rendered representations of the ink canvas.  They are not "live" (so to speak).  You might be able to capture mouse input over one of your views and redirect it to the actual ink canvas.  Then any changes to the real canvas as a result of that input would immediately be reflected in the other views.

     

    And then there's the question about handling and redirecting other types of input (such as stylus input, which you would certainly want).  It probably can be done, but it may involve significant work.

    Tuesday, January 15, 2008 10:17 AM
  • Yes, sorry probably confusing you now!

     

    I am only using the visual brush for the thumbnails on the list box. You dont have to be able to draw on those. Clicking on one brings up some control in the main area of the app that you can draw on. All the thumbnails have to do is stay up to date.

     

    I am getting  close to what i want to achieve but now i cant seem to bind to the Canvas.Left property. ClipLeft and ClipTop  are both DependencyProperties on the Sketch object. They are of type double. The mose over help is saying that it is not a valid Double value. I guess this is something to do with 'inheritence context' again?

     

    Code Block

    <UserControl

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    x:Class="XXXXX.Whiteboard"

    x:Name="MainWindow">

    <UserControl.Resources>

    </UserControl.Resources>

    <Grid DataContext="{Binding Path=Sketches/, ElementName=MainWindow}">

    <Grid.ColumnDefinitions>

    <ColumnDefinition Width="Auto"/>

    <ColumnDefinition Width="*"/>

    </Grid.ColumnDefinitions>

    <Viewbox Grid.Column="1">

    <DocumentPageView x:Name="xpsView_"/>

    </Viewbox>

    <Viewbox Grid.Column="1">

    <!-- We will use an outer canvas and a translate transform to restrict the

    view to the clipped region specified by the Sketch element -->

    <Canvas Width="{Binding Path=ClipWidth}"

    Height="{Binding Path=ClipHeight}"

    ClipToBounds="True">

    <ContentControl Content="{Binding Path=Canvas}"

    Canvas.Left="{Binding Path=ClipLeft}"

    Canvas.Top="{Binding Path=ClipTop}">

    </ContentControl>

    </Canvas>

    </Viewbox>

    </Grid>

    </UserControl>

     

     

     

     

    Tuesday, January 15, 2008 11:16 AM
  • Well i finally managed to achieve what i needed.

     

    Unfortunately (but for the bertter) this involved sub-classing Canvas and applying the clipping and positioning in code. Thanks for all the help with the VisualBrush binding stuff.

     

    I have to say i'm not 100% convinced about XAMLs binding capability. Its very powerfull and great when it works but when it doesnt i'm left scratching my head quite alot.

     

    Cheers,

    Tuesday, January 15, 2008 4:56 PM