Search code examples
c#wpf.net-6.0

OxyPlot - This PlotModel is already in use by some other PlotView control


I am using WPF, and .NET 6.0. My application is basically Josh Smith's MVVM Demo with the business logic replaced. The plot code is in a .NET 6.0 project separate from the application. The application has independent workspaces on separate tabs. I can open multiple tabs, some with plots, some without, but if I try to go back to a tab with plots, I see the error message above. When I try to go back to the tab with plots, I can see the views get instantiated again, just before the exception is thrown. I use this same code in a .NET Framework 4.7.2 application with no problems.

My XAML:

<UserControl x:Class="DataPlot.Views.BasicTrackView"
             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" 
             xmlns:local="clr-namespace:DataPlot.Views"
             xmlns:oxy="http://oxyplot.org/wpf"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <oxy:PlotView x:Name="Plot" Model="{Binding PlotModel}" Grid.Column="1" MinHeight="{Binding MinimumPlotHeight}">
            <oxy:PlotView.DefaultTrackerTemplate>
                <ControlTemplate>
                    <oxy:TrackerControl Position="{Binding Position}" LineExtents="{Binding PlotModel.PlotArea}">
                        <oxy:TrackerControl.Background>
                            <LinearGradientBrush EndPoint="0,1">
                                <GradientStop Color="#f0e0e0ff" />
                                <GradientStop Offset="1" Color="#f0ffffff" />
                            </LinearGradientBrush>
                        </oxy:TrackerControl.Background>
                        <oxy:TrackerControl.Content>
                            <TextBlock Text="{Binding}" Margin="7" />
                        </oxy:TrackerControl.Content>
                    </oxy:TrackerControl>
                </ControlTemplate>
            </oxy:PlotView.DefaultTrackerTemplate>
        </oxy:PlotView>

    </Grid>
</UserControl>

Code behind:

public partial class BasicTrackView : UserControl
{
    public BasicTrackView()
    {
        InitializeComponent();
    }
}

I have several plots like this in an ItemsControl:

<UserControl x:Class="DataPlot.Views.TrackContainerView"
             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" 
             xmlns:local="clr-namespace:DataPlot.Views"
             xmlns:vm="clr-namespace:DataPlot.ViewModels"
             xmlns:helpers="clr-namespace:DataPlot.Helpers"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <DataTemplate DataType="{x:Type vm:BasicTrackViewModel}">
            <local:BasicTrackView/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type vm:GenericTrackViewModel}">
            <local:GenericTrackView IsActive="{Binding IsActive}"/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type vm:CollapsableTrackViewModel}">
            <local:CollapsableTrackView IsActive="{Binding IsActive}"/>
        </DataTemplate>
    </UserControl.Resources>
    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Label Grid.Row="0" Content="Controls?" Width="60" Height="25" HorizontalAlignment="Right" Margin="0,2,20,2" Grid.Column="4"
               HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Background="LightGray">
            <Label.ToolTip>
                <ToolTip>
                    <DockPanel Width="200" Height="75">
                        <TextBox Text="Zoom: Ctrl + Right Mouse, or&#x0a;Zoom with mouse wheel&#x0a;Pan: Right Mouse" AcceptsReturn="True"/>
                    </DockPanel>
                </ToolTip>
            </Label.ToolTip>
        </Label>

        <ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
            <ItemsControl Grid.Row="1" ItemsSource="{Binding TrackViewModels}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Grid helpers:GridHelpers.RowCount="{Binding TrackViewModels.Count}" helpers:GridHelpers.StarRows="All"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemContainerStyle>
                    <Style>
                        <Setter Property="Grid.Row" Value="{Binding RowIndex}" />
                    </Style>
                </ItemsControl.ItemContainerStyle>
            </ItemsControl>
        </ScrollViewer>

    </Grid>
</UserControl>

Is there something I can do to keep the application from trying to recreate views that already exist?


Solution

  • I would like to credit bigcrazal (in his comment) for providing an easy solution with his link to code provided by edvinasz. Since links can go bad, I will put the code below. In my question I mentioned that I had no problem with this code in old .NET Framework 4.7.2 projects. I now believe this is because I was using static tabs in those past applications.

    using System;
    using System.Linq;
    using System.Reflection;
    using OxyPlot;
    
    /// <summary>
    /// Use this sub implementation of the <see cref="PlotModel"/> if the view will be declared using data template.
    /// Because views will be automatically generated, and new view will be different this causes current version to throw an error.
    /// </summary>
    public class ViewResolvingPlotModel : PlotModel, IPlotModel
    {
        private static readonly Type BaseType = typeof(ViewResolvingPlotModel).BaseType;
        private static readonly MethodInfo BaseAttachMethod = BaseType
            .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)
            .Where(methodInfo => methodInfo.IsFinal && methodInfo.IsPrivate)
            .FirstOrDefault(methodInfo => methodInfo.Name.EndsWith(nameof(IPlotModel.AttachPlotView)));
    
        void IPlotModel.AttachPlotView(IPlotView plotView)
        {
            //because of issue https://github.com/oxyplot/oxyplot/issues/497 
            //only one view can ever be attached to one plotmodel
            //we have to force detach previous view and then attach new one
            if (plotView != null && PlotView != null && !Equals(plotView, PlotView))
            {
                BaseAttachMethod.Invoke(this, new object[] { null });
                BaseAttachMethod.Invoke(this, new object[] { plotView });
            }
            else
            {
                BaseAttachMethod.Invoke(this, new object[] { plotView });
            }
        }
    }