2

In GDI+/WinForms, I could do this in the Click() event and using the graphics Object:

AddPoint(p); //Add Point contains some code to make sure there is only 3 dots
foreach (Point p in PointList) {
    DrawRectangle(p);
}
Invalidate();

If I try something similar in WPF, it won't cleanup the dots I created (I'm guessing because of how WPF works). What this means is if I check to make sure there is only three dots at a time, and pop off the oldest point to make room for the new one, the rectangle drawn would be still there.

So the question is, how can I create something in WPF that allows me to

  • Draw a rectangle at a Point
  • Remove rectangles/points from a WPF canvas after there is more than 3
Dominic K
  • 6,975
  • 11
  • 53
  • 62

2 Answers2

9

You're doing WPF the WinForms way. Don't do that. It's like writing VB code in C++. It can only end in tears.

To do this the WPF way, use databinding and a view model class to do the logic of "no more than 3 at a time." Then, for the UI, just bind to the PointList in your view model.

Here's what my XAML should look like. Notice I'm just using an ItemsControl and a Canvas, then binding the ItemsSource to PointList:

<Window x:Class="WpfTester.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">

    <ItemsControl ItemsSource="{Binding Path=PointList}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Rectangle Fill="Red" Width="25" Height="25" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.ItemContainerStyle>
            <Style>
                <Setter Property="Canvas.Left" Value="{Binding Path=X}" />
                <Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
            </Style>
        </ItemsControl.ItemContainerStyle>
    </ItemsControl>
</Window>

Then we just need to create the PointList. We'll use the normal WPF means: a view model class to hold the point list:

class MainViewModel
{
    public MainViewModel()
    {
        PointList = new ObservableCollection<Point>();

        // Some example data:
        AddPoint(new Point(10, 10));
        AddPoint(new Point(200, 200));
        AddPoint(new Point(500, 500));
    }

    public ObservableCollection<Point> PointList { get; private set; }

    public void AddPoint(Point p)
    {
        // 3 at most, please!
        if (PointList.Count == 3)
        {
            PointList.RemoveAt(0);
        }
        PointList.Add(p);
    }
}

Piece of cheese, yeah? So the final part is just telling the XAML to load your view model. Inside the the code-behind for your XAML, set the DataContext to your view model:

// Inside MainWindow.xaml.cs
public MainWindow()
{
    InitializeComponent();

    // Add this line:
    this.DataContext = new MainViewModel();
}

Now that you've got that in place, you can add/remove rectangles anywhere in your code simply by calling viewModel.AddPoint or viewModel.PointList.Remove, and the UI will automatically update to reflect the changes.

Judah Gabriel Himango
  • 58,906
  • 38
  • 158
  • 212
  • I think I explained it very clearly at the bottom of my question: 1) Draw a rectangle at a Point 2) Remove rectangles/points from a WPF canvas after there is more than 3. And yes, I sadly know I'm doing it the WinForms way :( – Dominic K Aug 24 '10 at 18:32
  • Ok. I'll update my answer to include a proper way to do this. – Judah Gabriel Himango Aug 24 '10 at 18:35
  • Ok, I've updated my answer to include an example of a proper way to do this with WPF databinding. – Judah Gabriel Himango Aug 24 '10 at 18:58
  • Okay, I wrapped the ItemsControl in a Canvas then hooked the MouseDown() event on that- in that method, I created the new viewModel and called the AddPoint. However, I don't see it adding any rectangles. – Dominic K Aug 24 '10 at 19:28
  • Create the view model once, in the constructor. Call AddPoint in your MouseDown handler. Also, you shouldn't be wrapping an ItemsControl in a Canvas, but rather, using an ItemsControl whose ItemsPanelTemplate is a Canvas. – Judah Gabriel Himango Aug 24 '10 at 20:26
  • 1
    Okay, I got it working. I did this: `MainViewModel mvm= new MainViewModel(); this.DataContext= mvm; mvm.AddPoint(new Point(x,y));`. Worked like a charm! EDIT: Just saw your comment, exactly what I was thinking :) – Dominic K Aug 24 '10 at 20:27
  • Yep. Now if you want to add points in the MouseDown handler, just call mvm.AddPoint inside your MouseDown handler, and they'll show up automagically. – Judah Gabriel Himango Aug 24 '10 at 20:28
  • Thanks, great! Just more info: I hooked MouseDown() to the ItemsControl, and set ItemsControl's background to white/invisible so it would catch the events. One more question- do you have any idea how to make the point appear in the middle of the cursor, not 0,0? – Dominic K Aug 24 '10 at 20:32
  • If all your rectangles are the same size, (e.g. 25 in this example), just do something like AddPoint(MousePositionX - (25 / 2), MousePositionY - (25 / 2)) – Judah Gabriel Himango Aug 25 '10 at 15:51
1

I would use wpf data binding to bind the content of the canvas to a collection of rectangles you store elsewere. You need to learn databinding anyway if you want to do serious WPF development.

Edit: Of course you store just the Rectangles, the data binding should create a shape for each rectangle.

  • How do I databind rectangles with positions (x/y)? – Dominic K Aug 24 '10 at 18:30
  • This is the geometry primitive you need: http://msdn.microsoft.com/en-us/library/system.windows.media.rectanglegeometry.aspx –  Aug 24 '10 at 18:39
  • And check this answer for a similar question: http://stackoverflow.com/questions/889825/wpf-is-it-possible-to-bind-a-canvass-children-property-in-xaml/1030191#1030191 –  Aug 24 '10 at 18:46