0

I have a list of rectangles that I want to display on a canvas. I used Krzysztof Skowronek's answer to this question, but he doesn't use ReactiveUI Binding; I am. But I can't get any of my rectangles to show up on my canvas. Everything compiles with no errors and there are no exceptions being thrown. There's not much to this code because I created a new project just to test this out before adding it to my actual project. Here's what I got.

MainWindowViewModel.cs

namespace CanvasTest
{
    public class MainWindowViewModel : ReactiveObject
    {
        //test canvas implementation
        private ObservableCollection<IShape> _shapes;
        public ObservableCollection<IShape> Shapes
        {
            get => _shapes;
            set => this.RaiseAndSetIfChanged(ref _shapes, value);
        }

        public ReactiveCommand<Unit, Unit> AddRectangle { get; }

        public MainWindowViewModel()
        {
            // Test Canvas Implementation
            Shapes = new ObservableCollection<IShape>();

            AddRectangle = ReactiveCommand.Create<Unit>(x => { 
                Shapes.Add(new Rectangle { Top = 25, Left = 10, Height = 50, Width = 50 });
            });
        }
    }

    public interface IShape
    {
        int Top { get; set; }
        int Left { get; set; }
    }

    public abstract class Shape : ReactiveObject, IShape
    {
        private int _top;
        public int Top
        {
            get => _top;
            set => this.RaiseAndSetIfChanged(ref _top, value);
        }

        private int _left;
        public int Left
        {
            get => _left;
            set => this.RaiseAndSetIfChanged(ref _left, value);
        }
    }

    public class Rectangle : Shape
    {
        private int _width;
        public int Width
        {
            get => _width;
            set => this.RaiseAndSetIfChanged(ref _width, value);
        }

        private int _height;
        public int Height
        {
            get => _height;
            set => this.RaiseAndSetIfChanged(ref _height, value);
        }
    }
}

I checked that the command is working using breakpoints.

MainWindow.xaml

<rxui:ReactiveWindow x:Class="CanvasTest.MainWindow"
        x:TypeArguments="ct:MainWindowViewModel"
        xmlns:rxui ="http://reactiveui.net"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:ct="clr-namespace:CanvasTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <ItemsControl x:Name="audioDisplayItemsControl" 
                      BorderBrush="Black"
                      BorderThickness="2"
                      Height="100">
            <ItemsControl.Resources>
                <DataTemplate DataType="{x:Type ct:Rectangle}">
                    <Rectangle Canvas.Top="{Binding Top, Mode=OneWay}" 
                               Canvas.Left="{Binding Left, Mode=OneWay}"
                               Width="{Binding Width}"
                               Height="{Binding Height}"
                               Fill="Green"/>
                </DataTemplate>
            </ItemsControl.Resources>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemContainerStyle>
                <Style TargetType="ContentPresenter">
                    <Setter Property="Canvas.Top" Value="{Binding Top, Mode=OneWay}"/>
                    <Setter Property="Canvas.Left" Value="{Binding Left, Mode=OneWay}"/>
                </Style>
            </ItemsControl.ItemContainerStyle>
        </ItemsControl>
        
        <Button x:Name="addButton" 
                Grid.Row="1" 
                Content="Add" 
                Width="50"
                Margin="0,0,0,10"/>
    </Grid>
</rxui:ReactiveWindow>

MainWindow.xaml.cs

namespace CanvasTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
    {
        public MainWindow()
        {
            InitializeComponent();
            ViewModel = new MainWindowViewModel();

            this.WhenActivated(d =>
            {
                this.OneWayBind(ViewModel,
                    vm => vm.Shapes,
                    v => v.audioDisplayItemsControl.ItemsSource)
                 .DisposeWith(d);

                this.BindCommand(ViewModel,
                    vm => vm.AddRectangle,
                    v => v.addButton)
                .DisposeWith(d);
            });
        }
    }
}

If there's anything else you need to know I'll add it, but I can't think of anything.

I am using WPF and have the following NuGet Packages installed. ReactiveUI, ReactiveUI.WPF, ReactiveUI.Events.WPF; all v11.4.17.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
master_ruko
  • 629
  • 1
  • 4
  • 22
  • 1
    not sure if it helps at all but ReactiveUI.Events.* is pretty much defunct now. The official line is to use Pharmacist. – Rich Bryant Jun 19 '20 at 22:33
  • As a note, setting Mode=OneWay on all those Bindings is pointless, because it is the default. It is also pointless to set Canvas.Top and Left on the Rectangle in the DataTemplate, because the Rectangle is no direct child of the Canvas - that's why you have the ItemContainerStyle. Did you check if your UI works with a plain MVVM approach, without any ReactiveUI magic? – Clemens Jun 19 '20 at 23:56
  • @Clemens No, I have never done any MVVM, except ReactiveUI, and I'm still just learning it. For that matter, I don't know XAML that well either. – master_ruko Jun 20 '20 at 01:07
  • @Clemens I added this to my XAML header: `DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"`, and this to my ItemControl: `ItemsSource="{Binding Shapes}`. I removed the ReactiveUI binding. I am now getting the rectangles showing in the canvas. So, Im assuming that it has something to do with the RxUI bindings. I would like to keep all the binding as RxUI bindings; Is it not possible in this case? – master_ruko Jun 20 '20 at 05:11
  • @Clemens Also I know that OneWay is the default, I just like to put it there as a reminder to me that it is indeed one-way. – master_ruko Jun 20 '20 at 05:14

1 Answers1

0

As per my comment above I added a data context to MainWindow.xaml header so I can bind to the collection in my ViewModel:

DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"

and did a XAML binding to my Shapes ObservableCollection

<ItemsControl ItemsSource="{Binding Shapes}" ... >

After talking to Ani Betts on the reactiveui Slack channel, I found that this is the best way to do it. You can make it work by giving a data template to the rxui binding; it creates a lot of overhead and since canvas in non-recycling, with a lot of canvas items, it can be expensive memory-wise.

master_ruko
  • 629
  • 1
  • 4
  • 22