0

I am trying to implement MVVM into my software.

What I want: I want a ViewModel.cs (ViewModel) file to substitute for the MainWindow.xaml.cs (MainWindow) file (which should only have InitializeComponent() inside)

What I did: I moved the data from my MainWindow to the newly created ViewModel.

What went wrong: I am having issues binding the MainWindow's XAML file to the ViewModel, with errors being

The name 'comPortList/donglesView' does not exist in the current context

I referenced the following links I considered related to my issue

  1. How do XAML files associate with cs files?
  2. binding property from another cs file with converter WPF

but I came up blank. Is there something I am missing? Please advise, or let me know if I am not providing enough info.

Helpful Data

  1. Relevant MainWindow.xaml code: The bottom three lines (comPortList, btnPortOpen and donglesView) need to work off code in the ViewModel.
<Window x:Class="comPortTesterEX.MainWindow"
        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:comPortTesterEX"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
        <!-- -->
    
    
    <Grid>
        <ListBox x:Name="comPortList" SelectionMode="Single" Grid.Row="0" Grid.Column="0" />
        <Button x:Name="btnPortOpen" Grid.Row="0" Grid.Column="1" Click="PortOpen_Click" Content ="Open Port"/>
        <TreeView x:Name="donglesView" Grid.Row="1" Grid.RowSpan="3" Grid.Column="0" Grid.ColumnSpan="2">
  1. ViewModel Code: the bottom three lines in 1. rely on code here, but I do not know how to link the two.
namespace comPortTesterEX
{
    class ViewModel : ObservableObject
    {
        public ObservableCollection<Dongle> dongles;

        DispatcherTimer timer;

        public ViewModel()
        {
            timer = new DispatcherTimer();
            timer.Tick += new EventHandler(checkAndUpdateComPortList);
            timer.Interval = new TimeSpan(0, 0, 1);
            timer.Start();
            dongles = new ObservableCollection<Dongle>();
            Trace.WriteLine("Started");
            donglesView.ItemsSource = dongles;
        }

        private void checkAndUpdateComPortList(object sender, EventArgs e)
        {
            List<String> portNames = new List<String>();
            foreach (string portName in SerialPort.GetPortNames())
            {
                portNames.Add(portName);
            }
            if (SerialPort.GetPortNames().Count() == 0)
            {
                portNames.Clear();
            }
            comPortList.ItemsSource = portNames;
        }
...
        private void PortOpen_Click(object sender, RoutedEventArgs e)
        {
            bool isComPortInList = false;
            //Checks for each highlighted item (limited to one)
            foreach (String name in comPortList.SelectedItems)
            {
                if (dongles.Count() == 0) // If there is nothing in bottom list -> CREATE ONE
                {
                    createDongle(dongles, name, 0);
                }
                else //If there is already a list
                {
                    for (int i = 0; i < dongles.Count(); i++) // Compare highlighted to EVERY ITEM IN LIST
                    {
                        // Check if it already exists in list
                        if (dongles[i].ComPortName == name)
                        {
                            isComPortInList = true;
                        }   // return true if it does
                    }
                    if (isComPortInList == false)
                    {
                        //Added element is last element, not 0th
                        createDongle(dongles, name, dongles.Count - 1);
                    }
                }
            }
        }
    }
}

ObservableObject coding was copied from Rachel Lim's MVVM page, link is https://rachel53461.wordpress.com/2011/05/08/simplemvvmexample/

Filburt
  • 17,626
  • 12
  • 64
  • 115

2 Answers2

2

You cannot access donglesView.ItemsSource in a class other than MainWindow (technically you can, but you are not supposed to).

Instead of the private dongles and portNames fields, the view model should expose public readonly properties

public ObservableCollection<Dongle> Dongles { get; }
    = new ObservableCollection<Dongle>();

public ObservableCollection<string> PortNames { get; }
    = new ObservableCollection<string>();

to which the view bind like this:

<TreeView ItemsSource="{Binding Dongles}" ... />
<ListBox ItemsSource="{Binding PortNames}" ... />

Updates of the collection would look like this:

public void UpdatePortNames()
{
    PortNames.Clear();
    foreach (string portName in SerialPort.GetPortNames())
    {
        PortNames.Add(portName);
    }
}

You also have to assign an instance of the view model class to the DataContext of the MainWindow, either in XAML

<Window.DataContext>
    <local:ViewModel/>
</Window.DataContext>

or in code

public MainWindow()
{
    DataContext = new ViewModel();
    InitializeComponent();
}
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • I understand, however on implementing the DataContext via XAML (word for word), I encountered the following error: XDG0008: The name "ViewModel" does not exist in the namespace "....." The error also occurred on previous, self-guided attempts to solve the issue despite everything sharing the same namespace, and despite deleting the .vs file assuming it was a generic error. – FrankGarica Jun 04 '21 at 08:49
  • I used the default `local` namespace prefix. Your namespaces may be different. Just do the assignment in code. – Clemens Jun 04 '21 at 08:58
  • Perhaps you did not build assembly the project after making the changes. The XAML Designer gets information about types not from the project, but from the project assembly. When launched for execution, if there are changes in it, the project will be automatically rebuilt and the error will disappear (if it occurred for this reason). Or call "Build project" yourself "manually". – EldHasp Jun 07 '21 at 04:58
-2

Turns out you can't totally count MainWindow.xaml.cs out of the equation, you can just minimize its impact and reduce everything to initialize component, and then bind it to something else like App.

  • You are not right. In many cases, this file can even be deleted. Its use is permissible (with a typical WPF implementation) in very rare cases. Mainly to demonstrate some simple, tutorial examples. – EldHasp Jun 07 '21 at 05:01
  • Apologies, but I have not seen evidence to the contrary. Could you provide some links so I can see how they are implemented? – FrankGarica Jun 07 '21 at 08:20
  • 1
    Look at the title of your question: "Using a View Model instead of MainWindow.xaml.cs" is exactly what you want to do in a MVVM approach. There is a separate view model class, of which an instance is assigned to the DataContext of a top-level view element, e.g. the MainWindow. You'll find many examples on the internet if you search for "wpf mvvm". – Clemens Jun 07 '21 at 08:41
  • Один из моих ответов на этом Форуме: https://stackoverflow.com/questions/67597202/how-to-fix-this-behavior-in-a-wpf-tabcontrol/67617794#67617794 – EldHasp Jun 07 '21 at 09:21