views:

1156

answers:

3

Hi, 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.

A: 

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;
mdm20
This is sort of an old school way of doing it, very winforms. There is really no need to create or fiddle with basic shapes like this in code.
Egor
A: 
sixlettervariables
+1  A: 

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"&gt;

 <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
Egor