-2

I've been struggling with the MVVM concept a lot. I am developing a remote file browser, so I can manage files on another computer from my own. I have a button that will open a new tab, so you can have multiple tab views of the files. When a new tab is opened, it should send a request to the client to get a list of drives, which will then be displayed in the newly created tab.

The way I am currently doing this is almost certainly not correct MVVM. I am using a Button_Click to enqueue the item count of the SubTabControl in a global queue in my code-behind. Then, I proceed to send the command to the client to request the list of drives.

private void Button_Click(object sender, RoutedEventArgs e)
{
    _DrivesUpdateQueue.Enqueue(SubTabControl.Items.Count);
    _ems.Clients[0].Send(_ems.ModuleName, (byte)Commands.GetDrives, String.Empty);
}

Once I receive the command, I add each drive to the ViewModel by casting the SubTabControl to my view model with the index.

public void UpdateDrives(ModularPacket packet)
{
        _drives.Clear();
        Debug.WriteLine("Drives! Drive size is " + Marshal.SizeOf(typeof(Drive)));

        int index = _DrivesUpdateQueue.Dequeue();

        for (int i = 0; i < packet.BodyLength; i += Marshal.SizeOf(typeof(Drive)))
        {
            Drive d = Utils.ByteArrayToStructure<Drive>(packet.Body, i);

            string x = ParseSize(d.freeSpace);

            ((TabItemViewModel)SubTabControl.Items[index]).Items.Add(d);
        }
}

Main XAML

<mah:MetroWindow x:Class="FileExplorerModuleServer.FileExplorerModuleServerUI"
             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:mah="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro" 
             xmlns:local="clr-namespace:FileExplorerModuleServer"
             mc:Ignorable="d" Title="File Explorer" Height="398.809" Width="653.198">

    <Window.DataContext>
        <!--Set the DataContent to be an instance of our view-model class -->
        <local:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <TabControl Name = "MasterTabControl" HorizontalAlignment="Stretch" Margin="10,10,0,0" VerticalAlignment="Stretch">
            <TabItem Header="Browse Files">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <!--The Command of the button is bound to the View Model -->
                    <Button Grid.Row="0" HorizontalAlignment="Right" Width="100"  
                            Content="New Tab" 
                            Command="{Binding NewTabCommand}" Click="Button_Click" />

                    <!--ItemsSource is bound to the 'Tabs' property on the view-
                        model, while DisplayMemeberPath tells TabControl 
                        which property on each tab has the tab's name -->
                    <TabControl Name="SubTabControl" Grid.Row="1" 
                    ItemsSource="{Binding Tabs}" 
                    DisplayMemberPath="Header" SelectedIndex="{Binding SelectedIndex}">
                        <TabControl.ContentTemplate>
                            <DataTemplate>
                                <!--The UserControl/Grid were pointless, so I 
                                    removed them. ItemsSource of the ListView is 
                                    bound to an Items property on each object in
                                    the Tabs collection-->
                                <ListView ItemsSource="{Binding Items}">
                                    <ListView.View>
                                        <GridView>
                                            <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>
                                            <GridViewColumn Header="Type" DisplayMemberBinding="{Binding Type}"/>
                                            <GridViewColumn Header="Total Size" DisplayMemberBinding="{Binding TotalSize}"/>
                                            <GridViewColumn Header="Free Space" DisplayMemberBinding="{Binding FreeSpace}"/>
                                        </GridView>
                                    </ListView.View>
                                </ListView>
                            </DataTemplate>
                        </TabControl.ContentTemplate>
                    </TabControl>
                </Grid>
            </TabItem>
        </TabControl>
    </Grid>
</mah:MetroWindow>

View Models:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace FileExplorerModuleServer
{
    public class MainWindowViewModel : ViewModelBase
    {
        public int SelectedIndex
        {
            get => _selectedIndex;
            set => Set(ref _selectedIndex, value);
        }
        private int _selectedIndex;

        // store our list of tabs in an ObservableCollection
        // so that the UI is notified when tabs are added/removed
        public ObservableCollection<TabItemViewModel> Tabs { get; }
            = new ObservableCollection<TabItemViewModel>();

        // this code gets executed when the button is clicked
        public ICommand NewTabCommand
            => new RelayCommand(() => 
            {
                Tabs.Add(new TabItemViewModel() { Header = $"Loading..." }); 
                SelectedIndex = Tabs.Count - 1;
            });
    }

    public class TabItemViewModel : ViewModelBase
    {
        // this is the title of the tab, note that the Set() method
        // invokes PropertyChanged so the view knows if the 
        // header changes
        public string Header
        {
            get => _header;
            set => Set(ref _header, value);
        }
        private string _header;

        // these are the items that will be shown in the list view
        public ObservableCollection<Drive> Items { get; set; }
            = new();
    }
}
1

0

Browse other questions tagged or ask your own question.