0

I have programmed an application that draws elements in a canvas. These elements are Rectangles (but I used the Border class because I want to instert text in it). These elements represent objects (Tasks in my case).

I draw these elements in code behind like this:

foreach (var task in TasksList)
{
    var rect = new Border();
    rect.Background = (SolidColorBrush)(new 
        BrushConverter().ConvertFrom("#0074D9"));
    rect.BorderBrush = (SolidColorBrush)(new 
        BrushConverter().ConvertFrom("#001f3f"));
    rect.BorderThickness = new Thickness(2);

    rect.Width = 60;
    rect.Height = 60;
    var t = new TextBlock
    {
        Text = task.Id.ToString(),
        Foreground = new SolidColorBrush(Colors.White),
        HorizontalAlignment = HorizontalAlignment.Center,
        VerticalAlignment = VerticalAlignment.Center
    };
    rect.Child = t; 

    Canvas.SetLeft(rect, coordX);
    Canvas.SetTop(rect, coordY);
    Canvas.Children.Add(rect);
}

Insted of designing the border (rect) in code behind. I would like to design it as a XAML resource and create instances of this resources in code behind. How can accomplish this? How can I use bindings in this case? For instance, in the xaml resource I need to define that the property ID of the task needs to be binded to the Text property of a TextBlock placed in the middle of the border. But later, in the code behind, how do I specify the DataContext of the properties defined in the xaml?

Hope you could guide me. Thanks

chincheta73
  • 187
  • 1
  • 22
  • 1
    Do not create UI elements in code behind. Instead, use an ItemsControl with a Canvas as ItemsPanel and an appropriate ItemTemplate. Read the [Data Templating Overview](https://msdn.microsoft.com/en-us/library/ms742521.aspx) article on MSDN. See also [this question](http://stackoverflow.com/q/22324359/1136211) for an example. – Clemens May 11 '17 at 09:01

1 Answers1

1

It's not really clear where the coordinate values come from, so I turned them into part of the TaskItem class. You can change this aspect however you may want.

In order to realize your code in WPF XAML, you need a visual representation of the item (DataTemplate) and a way to determine the item placement - I use a style for this aspect. The items will be placed on the canvas with an ItemsControl as suggested by Clemens.

The TaskItem is a plain class for this example. If you want to modify its contents after creation, you should better implement it with INotifyPropertyChanged.

public class TaskItem
{
    public int Id { get; set; }
    public double CoordX { get; set; }
    public double CoordY { get; set; }
}

The xaml, expecting the DataContext to contain a TasksList collection:

<Window.Resources>
    <DataTemplate x:Key="dtTaskItem" DataType="{x:Type local:TaskItem}">
        <Border
            Background="#0074D9"
            BorderBrush="#001F3F"
            BorderThickness="2"
            Width="60"
            Height="60">
            <TextBlock
                Text="{Binding Id}"
                Foreground="White"
                HorizontalAlignment="Center" VerticalAlignment="Center"/>
        </Border>
    </DataTemplate>

    <Style x:Key="canvasTaskItemStyle" TargetType="ContentPresenter">
        <Setter Property="Canvas.Left" Value="{Binding CoordX}"/>
        <Setter Property="Canvas.Top" Value="{Binding CoordY}"/>
    </Style>
</Window.Resources>
<Grid x:Name="grid1">
    <ItemsControl
        ItemsSource="{Binding TasksList}"
        ItemTemplate="{StaticResource dtTaskItem}"
        ItemContainerStyle="{StaticResource canvasTaskItemStyle}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</Grid>

And some example data to play with:

var tasksList = new List<TaskItem>()
{
    new TaskItem { Id = 1, CoordX = 10, CoordY = 20 },
    new TaskItem { Id = 2, CoordX = 60, CoordY = 160 },
    new TaskItem { Id = 5, CoordX = 140, CoordY = 80 },
    new TaskItem { Id = 3, CoordX = 50, CoordY = 50 },
    new TaskItem { Id = 8, CoordX = 100, CoordY = 100 },
};
grid1.DataContext = new { TasksList = tasksList };

Sidenote: the resources could as well be placed inside a Grid.Resources section. The Window.Resources is just a default build I use when testing my answers to some WPF related stackoverflow question. The local:TaskItem expects the xaml namespace to be defined for the current project (In my case xmlns:local="clr-namespace:WpfTests_2").

grek40
  • 13,113
  • 1
  • 24
  • 50
  • Thank you @grek40 This is exactly what I wanted. Now that I have this working, how could I have two kind of objects in the same canvas? One representing the tasks (as Borders) and one representing the dependence between tasks (as lines). In the model, every task has a property called PredecessorTasks which is a list of Tasks – chincheta73 May 11 '17 at 16:42
  • Well to give you a starting point, just remove the `x:Key="dtTaskItem"` from the `DataTemplate` and the `ItemTemplate="{StaticResource dtTaskItem}"` from the ItemsControl. The example will still work, but this time the data template is implicitely selected based on the `DataType="{x:Type local:TaskItem}"`. Next step: create more implicit data templates for other class types, they will also be used when the item type appears in the list items. – grek40 May 11 '17 at 18:24
  • Thank you @grek40. Got this working as well :) Next thing is to add a tooltip to the border, which is not working. But I will ask this in a new question. Thank you :) – chincheta73 May 11 '17 at 20:19