Home > Back-end >  WPF Nested ItemsControl with custom Panel & ItemContainer
WPF Nested ItemsControl with custom Panel & ItemContainer

Time:12-12

I am creating a rudimentary Gantt chart as a visual representation of a schedule of events. To do this, I have an ItemsControl to render schedule line items in a StackPanel. Within that "parent" ItemsControl, I have another ItemsControl to render the Gantt chart view - basically just shapes. This looks like the following:

        <ItemsControl ItemsSource="{Binding ScheduleLines}"
                      Grid.Row="1"
                      Height="100">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>

            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="100"/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>

                        <TextBlock Text="{Binding Name}"
                                   Grid.Column="0"/>

                        <ItemsControl ItemsSource="{Binding Events}"
                                      Grid.Column="1">

                            <ItemsPanelTemplate>
                                <components:ScheduleRowPanel/>
                            </ItemsPanelTemplate>

                            <ItemContainerTemplate>
                                <scheduleitems:ScheduleEventElement EventDate="{Binding EventDate}"
                                                                    Status="{Binding Status}"/>
                            </ItemContainerTemplate>

                        </ItemsControl>
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>

The datacontext for this view has an ObservableCollection called ScheduleLines. Each item of ScheduleLines has another observable collection of Events.

Inside the child ItemsControl, I have a custom panel that arranges the events:

    public class ScheduleRowPanel : Panel
    {
        //Scale and MinDate are dependency properties on this custom panel

        protected override Size ArrangeOverride(Size finalSize)
        {
            foreach(UIElement child in Children)
            {
                ArrangeChild(child, MinDate, Scale, finalSize.Height);
            }

            return finalSize;
        }

        private void ArrangeChild(UIElement child, DateOnly minDate, double scale, double panelHeight)
        {
            if (child.GetType() == typeof(ScheduleEventElement))
            {
                ScheduleEventElement eventElement = (ScheduleEventElement)child;
                DateOnly eventDate = eventElement.EventDate;

                double xoffset = scale * (eventDate.DayNumber - minDate.DayNumber);
                double yoffset = panelHeight / 2;

                child.Arrange(new Rect(xoffset, yoffset, 50, 50));
            }
            else
            {
                throw new InvalidOperationException("Have not implemented any other type of schedule entity");
            }
        }

    }

The panel arranges events from the following, that are rotated rectangles with a fill:

    public class ScheduleEventElement : FrameworkElement
    {

        public DateOnly EventDate
        {
            get { return (DateOnly)GetValue(EventDateProperty); }
            set { SetValue(EventDateProperty, value); }
        }

        // Using a DependencyProperty as the backing store for EventDate.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty EventDateProperty =
            DependencyProperty.Register("EventDate", typeof(DateOnly), typeof(ScheduleEventElement), new PropertyMetadata(DateOnly.FromDayNumber(1)));


        public EventStatus Status
        {
            get { return (EventStatus)GetValue(StatusProperty); }
            set { SetValue(StatusProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Status.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StatusProperty =
            DependencyProperty.Register("Status", typeof(EventStatus), typeof(ScheduleEventElement), new PropertyMetadata(EventStatus.Scheduled));

        protected override void OnRender(DrawingContext drawingContext)
        {
            RectangleGeometry rectangle = new RectangleGeometry();
            SolidColorBrush fill = new SolidColorBrush();

            rectangle.Rect = new Rect(0, 0, 10, 0);

            RotateTransform rotate = new RotateTransform();
            rotate.CenterX = 0.5;
            rotate.CenterY = 0.5;
            rotate.Angle = 45;

            rectangle.Transform = rotate;

            switch (Status)
            {
                case EventStatus.Scheduled:
                    fill.Color = (Color)ColorConverter.ConvertFromString("#262626");
                    break;
                case EventStatus.Early:
                    fill.Color = (Color)ColorConverter.ConvertFromString("#009d9a");
                    break;
                case EventStatus.Late:
                    fill.Color = (Color)ColorConverter.ConvertFromString("#6929c4");
                    break;
            }

            drawingContext.DrawGeometry(fill, null, rectangle);
        }

    }

I am getting the following error when running the nested ItemControl: enter image description here

I am understanding this as I need to modify the inner ItemControl to instead of having ItemsSource = {Binding Events} to have <ItemsControl> <ItemsControl.ItemsSource/>, but I am not sure how to specify the items source using that syntax. Is my understanding correct? If so, what would be the correct syntax for this?

Further, do I have a correct implementation of the ItemsContainerTemplate? Or should this be in the ItemsTemplate?

Thanks

CodePudding user response:

you can't just write <ItemsPanelTemplate> and <ItemContainerTemplate> inside <ItemsControl> tag, you need to write them inside relveant property tags: <ItemsControl.ItemsPanel> and <ItemsControl.ItemTemplate>.

<ItemsControl ItemsSource="{Binding Events}"
              Grid.Column="1">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <components:ScheduleRowPanel/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <scheduleitems:ScheduleEventElement EventDate="{Binding EventDate}"
                                                Status="{Binding Status}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
  • Related