2

I have tried to find a good consistent folder structure in Visual Studio to capture all the possibilities. This so far has been what I've came up with for my project. Folder Structure

My Application looks like: My Application

The xaml is pretty straight forward:

<Window x:Class="Check.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:viewModels="clr-namespace:SA_BOM_Check.ViewModels"
        mc:Ignorable="d"
          d:DataContext="{d:DesignInstance Type=viewModels:MainWindowViewModel, IsDesignTimeCreatable=True}"
        Height="600" Width="800"
        DataContext="{StaticResource ResourceKey=MainWindowViewModel}">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150" />
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"></RowDefinition>
            <RowDefinition Height="40"></RowDefinition>
            <RowDefinition Height="40"></RowDefinition>
            <RowDefinition Height="10"></RowDefinition>
            <RowDefinition Height="30"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="30"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="1"  Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Right" Text="XA File: " Height="30" FontSize="20" FontWeight="Bold"/>
        <TextBox x:Name="TxtXAFile" Grid.Row="1" Grid.Column="1"  Text="{Binding Path=XAFile, Mode=TwoWay}" VerticalAlignment="Center" FontSize="15"></TextBox>
        <Button x:Name="BtnXaBrowse" Grid.Row="1" Grid.Column="2" VerticalAlignment="Center" Margin="10,5,80,1" FontSize="20" FontWeight="DemiBold" Click="BtnXaBrowse_OnClick">Browse...</Button>
        <TextBlock Grid.Row="2"  Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Right"  Text="Inventor File: " Height="30" FontSize="20" FontWeight="Bold"/>
        <TextBox Grid.Row="2" Grid.Column="1" x:Name="TxtInventorFile" Text="{Binding Path=InventorFile, Mode=TwoWay}" VerticalAlignment="Center" FontSize="15"/>
        <Button x:Name="BtnInventorBrowse" Grid.Row="2" Grid.Column="2" VerticalAlignment="Center"  Margin="10,0,80,0" FontSize="20" FontWeight="DemiBold" Click="BtnInventorBrowse_OnClick">Browse...</Button>
        <Button x:Name="BtnDiff" Grid.Row="2" Grid.Column="3" VerticalAlignment="Center"  Margin="10,5,80,1" FontSize="20" FontWeight="DemiBold" Command="{Binding GetDifferences}">Differences</Button>
        <Line Grid.Row="3" Grid.Column="0"  Grid.ColumnSpan="4" X1="0" Y1="0" X2="1" Y2="0" Stroke="Black" StrokeThickness="2" Stretch="Uniform"></Line>
        <Label Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="4" Content="Missing in Inventor (Inventor - XA)" FontSize="15" FontStyle="Normal" HorizontalAlignment="Center" FontWeight="Bold"  Foreground="Black"/>
        <DataGrid AutoGenerateColumns="True" Grid.Row="5" Grid.Column="0"  Grid.ColumnSpan="4" IsReadOnly="True"
                  FontSize="18" ItemsSource="{Binding Path=XADiffDataTable}"
                  ColumnWidth="*">
            <DataGrid.ColumnHeaderStyle>
                <Style TargetType="{x:Type DataGridColumnHeader}">
                    <Setter Property="FontWeight" Value="Bold"></Setter>
                </Style>
            </DataGrid.ColumnHeaderStyle>
        </DataGrid>
        <Label Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="4" 
               Content="Missing In XA (XA - Inventor)" FontSize="15" FontStyle="Normal" 
               HorizontalAlignment="Center" FontWeight="Bold"  Foreground="Black"/>
        <DataGrid Grid.Row="7" Grid.Column="0" Grid.ColumnSpan="4" 
                  AutoGenerateColumns="True" IsReadOnly="True"
                  FontSize="18" ItemsSource="{Binding Path=InventorDiffDataTable}"
                  ColumnWidth="*">
            <DataGrid.ColumnHeaderStyle>
                <Style TargetType="{x:Type DataGridColumnHeader}">
                    <Setter Property="FontWeight" Value="Bold"></Setter>
                </Style>
            </DataGrid.ColumnHeaderStyle>
        </DataGrid>
    </Grid>
</Window>

Code Behind:

using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
using SA_BOM_Check.ViewModels;

namespace SA_BOM_Check.Views
{

    public partial class MainWindow : Window
    {
        //private readonly MainWindowViewModel _vm;

        public MainWindow()
        {
            InitializeComponent();
            //_vm = (MainWindowViewModel) this.DataContext;
        }

        private void BtnXaBrowse_OnClick(object sender, RoutedEventArgs e)
        {
            TxtXAFile.Text = OpenFileDialog();
            TxtXAFile.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
            //_vm.XAFile = OpenFileDialog();
        }

        private void BtnInventorBrowse_OnClick(object sender, RoutedEventArgs e)
        {
            TxtInventorFile.Text = OpenFileDialog();
            TxtInventorFile.GetBindingExpression((TextBox.TextProperty))?.UpdateSource();
        }

        private string OpenFileDialog()
        {
            OpenFileDialog openFileDialog = new();
            return openFileDialog.ShowDialog() == true ? openFileDialog.FileName : "";
        }
    }
}

Keeping the ShowDialog inside the code behind was inspired by BionicCode https://stackoverflow.com/users/3141792/bioniccode Where he answered the question about OpenFileDialogs Open File Dialog MVVM Actual Answer (which should have been the answer to the question) is at https://stackoverflow.com/a/64861760/4162851

The InventorAndXACompare class in summary is

        private static readonly Dictionary<string, Part> INVENTOR_PARTS 
            = new Dictionary<string, Part>();
        private static readonly Dictionary<string, Part> XA_PARTS = new Dictionary<string, Part>();
        private static readonly Dictionary<string, Part> XA_PARTS_OMIT = new Dictionary<string, Part>();
        private static readonly DataTable MISSING_IN_INVENTOR = new DataTable("XACompared");
        private static readonly DataTable MISSING_IN_XA = new DataTable("InventorCompared");

public static DataTable[] CompareFiles(string xaFile, string inventorFile)
private static void ParseInventor(string inventorFile)
private static void ParseXA(string xaFile)
private static void CompareXAToInventor()
private static void CompareInventorToXA()

The compare files takes two files. It then parses those files into two dictionaries that are compared in both directions where there are differences it adds those rows to a datatable inside the MainWindowViewModel those are then binded to the View by

        private DataTable _xaDiffDataTable;
        public DataTable XADiffDataTable
        {
            get { return _xaDiffDataTable;}
            set
            {
                _xaDiffDataTable = value;
                OnPropertyChanged("XADiffDataTable");
            }
        }

        private DataTable _inventorDiffDataTable;
        public DataTable InventorDiffDataTable
        {
            get { return _inventorDiffDataTable;}
            set
            {
                _inventorDiffDataTable = value;
                OnPropertyChanged("InventorDiffDataTable");
            }
        }

I would like to know if there is a better way of structuring this and if InventorAndXACompare would be better placed in the Models folder?

  • Given its responsibility a class like InventorAndXACompare is part of the model. We can say that all logic that is not dealing with displaying data (View) or preparing data for the view/ for the model (View Model) is part of the Model. You want your application logic in the Model. Let the View Model collect the input from the View and pass it to the Model (e.g. InventorAndXACompare) for further process. – BionicCode Sep 22 '21 at 07:25
  • In this context you must know if the result of the parser is only used to display data or if your parser works independent of the View, for example where parsing is done to collect meta data of a document or to perform grammar checking. Many very small applications tend to not have a real Model. If you have such a very small application like a script like solution with a GUI, then you are fine to just take care to separate the View from the rest of your code. – BionicCode Sep 22 '21 at 07:25
  • Thank you @BionicCode but, you have confused me a little. In your first comment you say it is part of the Model then in your second comment you just lean toward not having it be part of the view? The main purpose of InventorAndXACompare is to take two files parse them for the needed data then create two datatables of the differences. One table is parts in Inventor but not XA the other is parts in XA but not inventor.(also quantity differences as well). If the user didn't specify the data but instead the data was coming directly from a database it would of course be in a model. – Philip Borchert Sep 22 '21 at 12:20
  • @Sorashi I am not sure why you deleted your answer? I just wanted some more information on what you meant by changing the class into an interface/abstract. – Philip Borchert Sep 22 '21 at 12:21
  • @BionicCode would you be willing to post an answer so I can accept it. – Philip Borchert Sep 22 '21 at 12:22
  • No I was trying to say theat when your application is very small you may want to drop the Model. Then you only have a View and everything else. Otherwise parsers are usually in the Model. – BionicCode Sep 22 '21 at 12:23
  • It does not matter whether data comes from a database or from a file picked by the user. Data sources and sinks are always part of the Model. – BionicCode Sep 22 '21 at 12:29

1 Answers1

2

In an MVVM architecture you would usually find parsers in the Model component. The View Model generally does not process data. Usually it may convert data to meet the constraints of the Model's interface or to prepare data for presentation in the View.

Also from an MVVM point of view the data source and sink is always part of the Model. It does not matter whether data comes from a database or from a file picked by the user.

BionicCode
  • 1
  • 4
  • 28
  • 44
  • This might be an unnecessary distinction. My parser takes the data files and parses the needed pieces into a dictionary. Then another piece takes the dictionary and compares them in both directions and generates a data table that can then be used by the View. Since it is manipulating the data for the view would that mean that part would be seperated and placed in the view? – Philip Borchert Sep 22 '21 at 12:52
  • 1
    Of all three possibilities we can safely eliminate the View. It does never do anything with your data except showing it or collewcting it (user input). All the logic that modifies data belongs to the Model. If you are writing a diff-comparer then this logic would even be the business logic, the core logic of your application. It's definitely Model. This is the flow: a) user picks a file in the View b) View passes the file path to the Model via the View Model – BionicCode Sep 22 '21 at 14:09
  • 1
    c) Model reads the designated file and processes it e.g. find the differences and create data models that encapsulate those information d) Model exposes the result data e.g. ,file content and meta data about the file content like differences to the View Model (e.g. via event or as return value of a method call) e) View Model may prepare the data e.g. by wrapping them into bindable data models f) View gets the data via data binding for display. – BionicCode Sep 22 '21 at 14:09
  • Thank you @BionicCode I am starting to understand. – Philip Borchert Sep 22 '21 at 17:12