1
votes

I'd like to create my own UserControl (let's call it "RectAtCoordinates") that would work similarly to Canvas, however it should:

-Store collection of (x,y) integer coordinates

-Draw rectangle (with arbitrary chosen size and fill) on canvas for each (x,y) pair. (x,y) specify position of rectangle.

First of all, I've created simple Coordinates class:

class Coordinates : DependencyObject
    {
        public static readonly DependencyProperty XProperty =
            DependencyProperty.Register("X", typeof(int), typeof(Coordinates));

        public static readonly DependencyProperty YProperty =
            DependencyProperty.Register("Y", typeof(int), typeof(Coordinates));

        public int X
        {
            get { return (int)GetValue(XProperty); }
            set { SetValue(XProperty, value); }
        }

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

        public Coordinates(int x, int y)
        {
            this.X = x;
            this.Y = y;
        }
    }

Here's my RectAtCoordinates UserControl (.xaml):

<UserControl x:Class="RectAtPoint.RectAtCoordinates"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <ItemsControl ItemsSource="{Binding Path=Coos, Mode=OneWay}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas Width="300" Height="300" Background="White"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Rectangle Fill="Black" Width="50" Height="50"/>
            </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>
</UserControl>

And finally code behind RectAtCoordinates:

public partial class RectAtCoordinates : UserControl
    {
        public static readonly DependencyProperty CoosProperty =
            DependencyProperty.Register("Coos", typeof(Coordinates), typeof(RectAtCoordinates));

        private ObservableCollection<Coordinates> Coos
        {
            get { return (ObservableCollection<Coordinates>)GetValue(CoosProperty); }
            set { SetValue(CoosProperty, value); }
        }

        public RectAtCoordinates()
        {
            InitializeComponent();
            Coos = new ObservableCollection<Coordinates>();
        }

        public void AddRectangleAtPosition(int x, int y)
        {
            Coos.Add(new Coordinates(x, y));
        }
    }

However, after building my project it crashes. I get CLR20r3 problem. After further inspection I've changed RectAtCoordinates constructor into:

public RectAtCoordinates()
        {
            InitializeComponent();
            try
            {
                Coos = new ObservableCollection<Coordinates>();
            }
            catch (Exception e)
            {
                MessageBox.Show(e.ToString());
            }
        }

And got this error:

System.ArgumentException: 'System.Collections.ObjectModel.ObservableCollection`1[RectAtPoint.Coordinates]' is not a valid value for property 'Coos'.

at System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType, Boolean isInternal)

at System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)

at RectAtPoint.RectAtCoordinates.set_Coos(ObservableCollection`1 value) in c:...\RectAtCoordinates.xaml.cs:line 28

at RectAtPoint.RectAtCoordinates..ctor() in c:...\RectAtCoordinates.xaml.cs:line 36

I'm novice considering WPF, binding and Dependency Properties, so please, take into consideration that I could've made some basic mistakes. I've found many similar problems, but I couldn't fully understand solutions and therefore properly apply them.

2
I think you need to declare the default value for every dp. msdn.microsoft.com/en-us/library/ms752914.aspx. In Visual Studio, in your class type propdp and press Tab 2 times. It is the snippet for dependency property. - iulian3000

2 Answers

3
votes

I think that your problem is in here:

 public static readonly DependencyProperty CoosProperty =
            DependencyProperty.Register("Coos", typeof(Coordinates), typeof(RectAtCoordinates));

Is should be:

 public static readonly DependencyProperty CoosProperty =
            DependencyProperty.Register("Coos", typeof(ObservableCollection<Coordinates>), typeof(RectAtCoordinates));

Give it a try :)

=== EDIT === For the coordinates.

I think you can do something like:

      public void AddRectangleAtPosition(int x, int y)
            {
                Coos.Add(new Coordinates(x, y));
if (PropertyChanged != null)
                        {
                            PropertyChanged(this, new PropertyChangedEventArgs("Coos"));
                        }
            }

Then your class should have:

public partial class RectAtCoordinates : UserControl, INotifyPropertyChanged

And after that you can have a region like I usually have for the NotifyPropertyChanged as this:

#region notifypropertychanged region

public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged(string propertyName)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(propertyName);
    return true;
}

#endregion

}

Give a try :)

-3
votes

I've finally found a solution: data context was not set properly. I had to add

this.DataContext = this;

to UserControl's constructor or add:

DataContext="{Binding RelativeSource={RelativeSource Self}}"

to UserControl's xaml definition.