0

So I'm working with a system of switches and bulbs, which are attached together by wires. I'm supposed to make the switches draggable, in such a way that the wires also move. Since you cannot update the points directly in a PointCollection (I think they're a struct I think?), I've figured out a way, but this seems VERY verbose, and I figure there must be a better way to do this.

Basically I retrieve the points, do the manipulation, clear the collection, and add the new points:

Point firstPoint = new Point(firstPointCollection[0].X, firstPointCollection[0].Y + offset.Y);
Point secondPoint = new Point(firstPointCollection[1].X + offset.X, firstPointCollection[1].Y + offset.Y);
Point thirdPoint = new Point(firstPointCollection[2].X + offset.X, firstPointCollection[2].Y + offset.Y);
firstPointCollection.Clear();
firstPointCollection.Add(firstPoint);
firstPointCollection.Add(secondPoint);
firstPointCollection.Add(thirdPoint);

In a system with multiple wires, which all consist of multiple points, this very quickly gets very tedious to write. This must all be done in C#, but if there is a better way to do with this with Data Binding of some sort, please let me know.

Amnestic
  • 650
  • 2
  • 8
  • 23
  • I'm curious why this would get tedious. No matter which point you drag, you only have three points to update, right? The same block of code should handle any drag operation. Also, you don't have to clear the collection--you should be able to replace just the points you change by using the collection's indexer, e.g., `points[i] = new Point(...)`. – Mike Strobel Oct 24 '14 at 13:50

3 Answers3

3

well the whole thing could be done with data binding so as you update the location of a bulb it auto updates the UI, stay with me here the code example is a bit long..

the result of this demo is just some circles with lines joining them but you could template this up however you want.

enter image description here

The xaml window, this declares an items control that will display the bulbs (and the lines joining them)

<Window x:Class="points.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:points"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="350" Width="525">
    <ItemsControl ItemsSource="{Binding Bulbs}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Canvas HorizontalAlignment="Left" VerticalAlignment="Top">
                    <Ellipse Fill="Blue" Width="10" Height="10">
                        <Ellipse.RenderTransform>
                            <TranslateTransform X="{Binding X}" Y="{Binding Y}"/>
                        </Ellipse.RenderTransform>
                    </Ellipse>
                    <Line X1="{Binding X}" Y1="{Binding Y}" X2="{Binding NextBulb.X}" Y2="{Binding NextBulb.Y}" Stroke="Red">
                        <Line.RenderTransform>
                            <TranslateTransform X="5" Y="5"/>
                        </Line.RenderTransform>
                    </Line>
                </Canvas>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</Window>

and this is the cs, I've created a bulb structure that holds the X and Y location of a bulb and a reference to the next bulb in the chain, I've placed all bulbs in to a line and then just moved the Y location of element 5 a bit lower to show it updating.

using System.Collections.ObjectModel;
using System.Windows;

    namespace points
    {
        public class Bulb : DependencyObject
        {
            public double X
            {
                get { return (double)GetValue(XProperty); }
                set { SetValue(XProperty, value); }
            }

            public static readonly DependencyProperty XProperty =
                DependencyProperty.Register("X", typeof(double), typeof(Bulb), new PropertyMetadata(0d));

            public double Y
            {
                get { return (double)GetValue(YProperty); }
                set { SetValue(YProperty, value); }
            }

            public static readonly DependencyProperty YProperty =
                DependencyProperty.Register("Y", typeof(double), typeof(Bulb), new PropertyMetadata(0d));

            public Bulb NextBulb
            {
                get { return (Bulb)GetValue(NextBulbProperty); }
                set { SetValue(NextBulbProperty, value); }
            }
            public static readonly DependencyProperty NextBulbProperty =
                DependencyProperty.Register("NextBulb", typeof(Bulb), typeof(Bulb), new PropertyMetadata(null));

        }
        public partial class MainWindow : Window
        {
            public ObservableCollection<Bulb> Bulbs
            {
                get { return (ObservableCollection<Bulb>)GetValue(BulbsProperty); }
                set { SetValue(BulbsProperty, value); }
            }
            public static readonly DependencyProperty BulbsProperty =
                DependencyProperty.Register("Bulbs", typeof(ObservableCollection<Bulb>), typeof(MainWindow), new PropertyMetadata(null));

            public MainWindow()
            {
                Bulbs = new ObservableCollection<Bulb>();

                InitializeComponent();

                for (int i = 0; i < 10; i++)
                {
                    double x = i * 50;
                    double y = 25;
                    Bulbs.Add(new Bulb()
                        {
                            X = x,
                            Y = y,
                            NextBulb = i > 0 ? Bulbs[i - 1] : null,
                        });
                }

                Bulbs[5].Y = 50;
            }
        }
    }

The outcome of this is that altering the Bulbs structure in any way will update the bulb display on the UI without having to clear and recreate your collection every time, you can even run animations on the properties for some pretty cool effects.

Andy
  • 6,366
  • 1
  • 32
  • 37
  • I will definitely toy around with this - seems a lot more streamlined than what I've designed thus far. – Amnestic Oct 24 '14 at 15:17
0

I really like the accepted answer - an upvote well deserved - because it was well written and really works. But, I thought it would be worth noting: if you intend to use it for many quick changing values, updating a PointCollection will perform better. See How to draw a simple (line) graph?.

Regarding the code and it's verbosity, maybe this would have (7 years ago) helped:

firstPointCollection = new PointCollection(firstPointCollection.Select(p=>new Point(p.X + offset.X, p.Y + offset.Y)));

Cheers ;o)

mike
  • 1,627
  • 1
  • 14
  • 37
-1

First of all, the Offset() method

As Amnestic pointed out even though Point is a struct you can mutate it through the Offset method. However since he is using a PointCollection and not an array of Points when you use the indexer you will get a copy of the struct, not the original.

If the points were in a Point[] you could just use this:

firstPointCollection[0].Offset(x, y)

However because we are using a PointCollection you need to set the indexer as such:

firstPointCollection[0] = firstPointCollection[0].Offset(x, y)

The discussion over whether structs should be mutable or not is a whole other kettle of fish.

Slugart
  • 4,535
  • 24
  • 32
  • Doesn't Offset just edit the point in the collection by x and y, and not return anything since it's a void method? I tried simply doing `firstPointCollection[0].Offset(0, offset.Y);` without assignment, but that didn't update the point for some reason. – Amnestic Oct 24 '14 at 15:28
  • Totally right, i didn't check the OP's assertion or the reference material i myself linked to – Slugart Oct 24 '14 at 15:35