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)
- ScrollViewer with an ItemsControl inside.
- 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).
- Add a DataTemplate to the ItemsTemplate property of the ItemsControl
- Add controls to the DataTemplate and bind them directly to an instance of PostIt
- 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).