views:

133

answers:

1

I'm a WPF n0ob and I'm struggling with selecting the appropriate control to get the layout I want.

What I'm trying to do is draw a bunch of squares (virtual post-it notes) onto the screen. Each note is going to be a decent size (~150 pixels or so) and there could be hundreds of these notes. I want the whole thing to be scrollable so that you can resize the window however you like and the whole thing should be zoomable.

I've done this and it works.
But what I've done seems awfully wrong....

In the code, I'm dynamically creating post it notes and adding them to a giant canvas. I'm manually doing the math to determine where to place each note and how big the canvas should be. I added some labels at the top and had to go back and add a 'Y Offset' value to push all the squares down. I actually generate three different canvas controls and then add each one of them to a stack panel that is inside of a ScrollViewer. I added a scroll bar and set the the stack panel to zoom in and out as you adjust the bar.

It 'works', but I feel like I'm really not using WPF the way it's meant to be used. I tried achieving the same thing with a grid, but the grid didn't seem to want to size itself appropriately.

Can someone tell me a 'better' way to achieve the same look?

Here's my Xaml code - as you can see; there isn't much to it....

<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid x:Name="LayoutRoot">
    <Grid.RowDefinitions>
        <RowDefinition Height="25" />
        <RowDefinition />
    </Grid.RowDefinitions>
    <Slider x:Name="ZoomSlider" Minimum="0.01" Value="1" Maximum="2" Margin="0,0,0,6" />
    <ScrollViewer x:Name="MyScroller" Grid.Row="1" HorizontalScrollBarVisibility="Visible"  HorizontalContentAlignment="Center" VerticalContentAlignment="Center">
        <StackPanel x:Name="TicketsGrid"  Background="White" HorizontalAlignment="Center">

        </StackPanel>
    </ScrollViewer>
</Grid>

And then here is what I'm doing in code (ugly!!!)

For Each myWorkItem As WorkItem In myWorkItems
        Dim newRect As New Border

        newRect.Width = TicketSizeX
        newRect.Height = TicketSizeY

        If myWorkItem.State.ToUpper.Contains("HOLD") Then
            newRect.Background = New SolidColorBrush(Colors.Purple)
        Else
            newRect.Background = New SolidColorBrush(Color)
        End If

        newRect.CornerRadius = New System.Windows.CornerRadius(5)
        newRect.BorderThickness = New System.Windows.Thickness(1)
        newRect.BorderBrush = New SolidColorBrush(Colors.Black)

        Dim myPanel As New StackPanel
        newRect.Child = myPanel

        Dim lblTitle As New Label
        lblTitle.Content = myWorkItem.Id
        lblTitle.FontWeight = System.Windows.FontWeights.Bold

        Dim lblDesc As New TextBlock
        lblDesc.Text = myWorkItem.Title
        lblDesc.TextWrapping = TextWrapping.Wrap


        myPanel.Children.Add(lblTitle)
        myPanel.Children.Add(lblDesc)

        newRect.SetValue(Canvas.LeftProperty, CType(((TicketCount Mod TicketsXPerUser) * TicketStepX) + (xOffset * TicketStepX * TicketsXPerUser), Double))
        newRect.SetValue(Canvas.TopProperty, CType(((Math.Floor((TicketCount / TicketsXPerUser)) * TicketStepY)) + NameLabelHeight, Double))

        myCanvas.Children.Add(newRect)
        TicketCount += 1
    Next

    MyCanvas.Width = (TicketStepX * TicketsXPerUser) * myTFS.SharedCodeTeam.Count
    MyCanvas.Height = (CType(((Math.Floor((MaxTicket / TicketsXPerUser)) + 1) * TicketStepY), Double))

    TicketsGrid.Children.Add(MyCanvas)
+5  A: 
  1. ScrollViewer with an ItemsControl inside.
  2. Bind the ItemsSource property of the ItemsControl to an ObservableCollection<PostIt> (where PostIt is a plain old CLR object with all the info that goes on the post it).
  3. Add a DataTemplate to the ItemsTemplate property of the ItemsControl
  4. Add controls to the DataTemplate and bind them directly to an instance of PostIt
  5. Add PostIt instances to the ObservableCollection<PostIt> in your code.

The ScrollViewer handles all scrolling. That's all you need.

The ItemsControl is designed to bind against a collection. For each instance in the collection, it figures out what DataTemplate to use, creates a copy of the template, sets the root's DataContext to the instance it pulled from the collection, and adds the template to itself. It does this for each instance found in the collection.

In your codebehind, all you need to do is create a bunch of PostIts and add them to the collection. No godawful construction of UI elements like you're doing. Urgh.

If you can grasp this concept, you are a step away from understanding MVVM, the Model-View-Controller pattern in WPF. Read about it, try it out. Its a very simple way of making very complex applications with complex UI but with a minimum of code (and none of that crap you're doing currently).

Will
Was writing up exactly the same prescription, the only thing I think you left out was mentioning that he could choose different layouts using by setting the ItemsPanel as well. By default it's StackPanel with vertical orientation, but I think he wanted to have WrapPanel like functionality.
Drew Marsh
Yeah, I bet you're right on that one. +1
Will
Good, if not slightly offensive, newb intro to MVVM here: http://jmorrill.hjtcentral.com/Home/tabid/428/EntryId/432/MVVM-for-Tarded-Folks-Like-Me-or-MVVM-and-What-it-Means-to-Me.aspx
Will