English 中文(简体)
WPF Dynamic Binding X and Y Co-ordinates
原标题:
  • 时间:2009-11-10 22:29:37
  •  标签:
  • wpf
  • binding

I have a question on WPF dynamic positioning.

I want to place Elipses on the screen based on X and Y co-ordinates that i have stored in a collection in C#.

I have been made aware of the drawing ability in WPF which you do from C# using the Windows.Media and Windows.Shapes.

Now what i actually want to do is use these namespaces to draw the elipses in the first case in a canvas all done in c# using my datasource that i have in c# to position the elipses using the x and y co-ordinates.

Now the complex part which is confusing me is what if the data in the datasource is changed as the data in the database changes, i will implement some sort of routine that checks the database every few seconds pulling back back any data that has changed since the last retrieval. Now i have seen the IPropertyChanged interface which i will inhert from for my class that i expose as my datasource for the page so when i retrieve the updated dataset i can call the PropertyChanged event which will notify WPF that the datasource has changed.

How would i bind the elipses in the UI when i was laying them out originally in C# to certain items from the datasource so when the datasource changed the elipses would automatically change as required to reflect the changed datasource as long as the ID for each x and y co-ordinate remained the same. So can i bind to specific rows from the collection for each elipse in my canvas when i m setting them out?

I don t even know if its possible to bind a datasource to a Canvas inside which i can use the collection as i require to begin with but i thought i d put this question out there incase someone has done something similar so i have a good starting point.

Thanks Iffy.

问题回答

To build on what others have said here is a complete self contained example - you can copy it straight into kaxaml or xamlpad (or blend, but I think in that case it has to go into a body of a usercontrol or a window) and see how it works.

Instead of using the rendertransform I prefer to use the canvas and set the left and top property, I just find it more readable that way. Alternatively, you can use a grid and set the margin but then you ll need a value converter of some kind.

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

 <Grid.Resources>

  <!-- This is our list of shapes, in this case an inline XML list -->
  <XmlDataProvider x:Key="ShapeList">
   <x:XData>
    <ObjectList xmlns="">
     <Shapes>
      <shape height="30" width="30" x="50" y="50"/>
      <shape height="30" width="40" x="100" y="100"/>
      <shape height="30" width="50" x="150" y="150"/>
      <shape height="30" width="60" x="200" y="200"/>
      <shape height="30" width="70" x="250" y="350"/>
     </Shapes>
    </ObjectList>
   </x:XData>
  </XmlDataProvider>
 </Grid.Resources>

 <ItemsControl ItemsSource="{Binding Source={StaticResource ShapeList}, XPath=ObjectList/Shapes/*}">

  <!-- this template sets the panel as canvas for easy positioning -->
  <ItemsControl.ItemsPanel>
   <ItemsPanelTemplate>
    <Canvas IsItemsHost="True"/>
   </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>

  <!-- this template defines how each bound item is represented -->
  <ItemsControl.ItemTemplate>
   <DataTemplate>
    <Border Width="{Binding XPath=@width}" Height="{Binding XPath=@height}">
     <Ellipse Fill="White" Stroke="Black" StrokeThickness="2"/>
    </Border>
   </DataTemplate>
  </ItemsControl.ItemTemplate>

  <!-- This style positions each bound item s container -->
  <ItemsControl.ItemContainerStyle>
   <Style>
    <Setter Property="Canvas.Left" Value="{Binding XPath=@x}"/>
    <Setter Property="Canvas.Top" Value="{Binding XPath=@y}"/>
   </Style>
  </ItemsControl.ItemContainerStyle>

 </ItemsControl>
</Grid>

Instead of binding to an inline xml list you can bind to a collection on your viewmodel (best choice), a dependency property on your control or window, set the resource from codebehind, etc.

The key point is that you shouldn t be laying out the ellipses in C# unless you absolutely have to. Provide the data as some sort of a list of meaningful objects. Then create a data template that defines how that data is represented. Assuming you don t have to do any sort of complicated processing of your object to get the relevant ellipse properties you should be able to do this without any code, or at most with a few value converters.

This is the sort of UI separation that allows you to deal with updating the datasource (business logic) and displaying items (ui) separately.

So basically the idea is:

  • Expose a collection of objects - in my example this would be a collection of classes mirroring the structure of the shape xml element in the list. This can be the business object itself, or a viewmodel - a class that wraps a business objects and exposes conveniently bindable properties (in this case, position and size). The collection itself would prefereably be an ObservableCollection, so that UI is notified when you add or remove objects. Toss in some design time data into it if possible.
  • Bind to the collection, using the WPF datatemplates to define how an element should be presented. In this case I used a plain ItemsControl with a few simple templates, but this can be as complex as required
  • Work out how the collection will be updated from the original datasource. If you set up the previous steps correctly this is essentially a separate problem

You can use a translate transform to position the ellipses as you create them.

        TranslateTransform transform = new TranslateTransform();
        transform.X = X;
        transform.Y = Y;
        Ellipse ellipse = new Ellipse();
        ellipse.RenderTransform = transform;
        ...

You could store the ellipses in a dictionary with the id as they key for quick and easy retrieval.

        TranslateTransform transform = data[id].RenderTransform as TranslateTransform;
        transform.X = newX;
        transform.Y = newY;

You can accomplish this within a DataTemplate if your Ellipse objects are represented by a class, and perhaps displayed in an ItemsControl.

<Ellipse>
    <Ellipse.LayoutTransform>
        <TranslateTransform X="{Binding XCoord}"
                            Y="{Binding YCoord}" />
    </Ellipse.LayoutTransform>
</Ellipse>

You would choose between LayoutTransform and RenderTransform based on the panel which held your Ellipse objects.

I also recommend reviewing an article by Bea Stollnitz (neé Costa) which shows how to leverage a ListBox backed by a Canvas with DataBinding to produce offset objects. Very cool.





相关问题
WPF convert 2d mouse click into 3d space

I have several geometry meshes in my Viewport3D, these have bounds of (w:1800, h:500, d:25). When a user clicks in the middle of the mesh, I want the Point3D of (900, 500, 25)... How can I achieve ...

Editing a xaml icons or images

Is it possible to edit a xaml icons or images in the expression design or using other tools? Is it possible to import a xaml images (that e.g you have exported) in the expression designer for editing?...

WPF: writing smoke tests using ViewModels

I am considering to write smoke tests for our WPF application. The question that I am faced is: should we use UI automation( or some other technology that creates a UI script), or is it good enough to ...

WPF - MVVM - NHibernate Validation

Im facing a bit of an issue when trying to validate a decimal property on domain object which is bound to a textbox on the view through the viewmodel. I am using NHibernate to decorate my property on ...

How do WPF Markup Extensions raise compile errors?

Certain markup extensions raise compile errors. For example StaticExtension (x:Static) raises a compile error if the referenced class cannot be found. Anyone know the mechanism for this? Is it baked ...

WPF design-time context menu

I am trying to create a custom wpf control, I m wondering how I can add some design-time features. I ve googled and can t seem to get to my goal. So here s my simple question, how can I add an entry ...

How to combine DataTrigger and EventTrigger?

NOTE I have asked the related question (with an accepted answer): How to combine DataTrigger and Trigger? I think I need to combine an EventTrigger and a DataTrigger to achieve what I m after: when ...

热门标签