Search code examples
c#wpfxamldata-bindingoxyplot

Data binding fails when window constructor takes string as parameter (OxyPlot, WPF)


I need to open a new window while passing a string into its constructor, and this seems to preclude some important data binding in the new window.

My MainWindow consists of a TextBox where the user puts in some text and then clicks a button, opening a new window which makes an OxyPlot displaying that text. This is important because this single user input will eventually be used to open many different windows and make different plots using the same text. Can't make the user provide the string every time they open a new window.

MainWindow with some user input

The new window ("FirstPlotWindow" because there will be more windows implemented later) uses OxyPlot to plot these words at random positions. The new window also has a second TextBox and a second button for adding more words to the plot.

FirstPlotWindow with user's words plotted

I've done my best to make it so that when the user types some words into this second box and clicks the second button, more words will be added to the plot. But it doesn't work.

Here is the XAML code for FirstPlotWindow:

<Window x:Class="WpfApp2.FirstPlotWindow"
    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:local="clr-namespace:WpfApp2"
    xmlns:oxy="http://oxyplot.org/wpf"
    mc:Ignorable="d"
    Title="FirstPlotWindow" Height="450" Width="800">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition Width="5*"/>
    </Grid.ColumnDefinitions>
    <TextBox x:Name="userInput2" Margin="0,40,0,0" HorizontalAlignment="Center" VerticalAlignment="Top" Width="100" Height="100"/>
    <Button x:Name="changePlotButton" Content="Put new words &#x0a; into plot" HorizontalAlignment="Center" VerticalAlignment="Center" Click="changePlotButton_Click"/>
    <oxy:PlotView x:Name="firstPlotView" Grid.Column="1" Model="{Binding MyModel}"/>
</Grid>

As you can see, I haven't set up the DataContext here because I need the plot model to take the original words ("Hello darkness my old friend") as an argument, as you can see below. Here is the code-behind for the window:

using System.Collections.Generic;
using System.Windows;

namespace WpfApp2
{
public partial class FirstPlotWindow : Window
{
    private FirstPlotModel modelInWindow;
    public FirstPlotModel ModelInWindow 
    { 
        get { return (FirstPlotModel)DataContext; } 
        set { modelInWindow = value; } 
    }
    public FirstPlotWindow(string inputText)
    {
        InitializeComponent();

        ModelInWindow = new FirstPlotModel(inputText);
        DataContext = modelInWindow;
    }

    private void changePlotButton_Click(object sender, RoutedEventArgs e)
    {
        List<string> moreWords = new List<string>(userInput2.Text.Split(" "));
        ModelInWindow.PopulatePlotWithWords(moreWords);
    }
}
}

And here is the code for FirstPlotModel.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using OxyPlot;
using OxyPlot.Annotations;
using OxyPlot.Axes;

namespace WpfApp2
{
public class FirstPlotModel: INotifyPropertyChanged
{
    public PlotModel MyModel { get; set; }
    Random randomizer;

    public event PropertyChangedEventHandler PropertyChanged;

    public FirstPlotModel(string inputText)
    {
        MyModel = new PlotModel { Title = "First Plot" };

        MyModel.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom });
        MyModel.Axes.Add(new LinearAxis { Position = AxisPosition.Left });

        List<string> wordList = new List<string>(inputText.Split(" "));
        PopulatePlotWithWords(wordList);
    }

    public void PopulatePlotWithWords(List<string> wordList)
    {
        randomizer = new Random();
        foreach (string word in wordList)
        {
            MyModel.Annotations.Add(new TextAnnotation
            {
                TextPosition = new DataPoint(
                    randomizer.Next(1, 100),
                    randomizer.Next(0, 100)),
                Text = word
            });
        }
    }

    private void OnPropertyChanged([CallerMemberName] String propName = null)
    {
        if (PropertyChanged != null)
            PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
    }
}
}

I know that entering the second set of words and pressing the second button successfully changes the MyModel property in the ModelInWindow object, because when I put a breakpoint after changePlotButton_Click I can see that the words have been added. Since the MyModel property changed, I believe the OxyPlot should update. But the OxyPlot doesn't change.

I designed the code to be as close to this as possible: OxyPlot WPF not working with Button Click

But declaring the DataContext in XAML seems impossible here! Please help!!


Solution

  • OxyPlot doesn't support binding like this. Implementing INotifyPropertyChanged and OnPropertyChanged will not make an OxyPlot automatically update. Instead, you must declare a new DataContext every time you click; do this in the C# code, whether or not the binding is initially set up in XAML.