WPF MVVM ColorLister with navigationWPF ListView Pagination using MVVM PatternMVVM, Navigation, and More - Part 3WPF MVVM NavigationWPF without MVVMNavigation between pages in WPF MVVM applicationAsyncCommand using MVVM and WPFWPF Mvvm async function ViewModelMVVM Navigation WPFAddition Program using wpf through MVVM

What's the biggest difference between these two photos?

Do you need to burn fuel between gravity assists?

Lost & Found Mobile Telepone

Is there a "right" way to interpret a novel, if not, how do we make sure our novel is interpreted correctly?

What makes things real?

Why does low tire pressure decrease fuel economy?

Are programming languages necessary/useful for operations research practitioner?

Bit floating sequence

How can Schrödinger's cat be both dead and alive?

How to find a reviewer/editor for my paper?

How do I decide when to use MAPE, SMAPE and MASE for time series analysis on stock forecasting

A PEMDAS issue request for explanation

Why should I always enable compiler warnings?

After a few interviews, What should I do after told to wait?

How to say "In Japan, I want to ..."?

LGPL HDL in larger FPGA design

How to set any file manager in Linux to show the duration like the Length feature in Windows Explorer?

How do I reference a custom counter that shows the section number?

How is lower/no gravity simulated on a planet with gravity, without leaving the surface?

WPF MVVM ColorLister with navigation

Chandrayaan 2: Why is Vikram Lander's life limited to 14 Days?

Sloth and the Hindrances

Isn't that (two voices leaping to C like this) a breaking of the rules of four-part harmony?

How to descend a few exposed scrambling moves with minimal equipment?



WPF MVVM ColorLister with navigation


WPF ListView Pagination using MVVM PatternMVVM, Navigation, and More - Part 3WPF MVVM NavigationWPF without MVVMNavigation between pages in WPF MVVM applicationAsyncCommand using MVVM and WPFWPF Mvvm async function ViewModelMVVM Navigation WPFAddition Program using wpf through MVVM






.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;








5












$begingroup$


By hashing together various online tutorials I've constructed my first MVVM application that lets users navigate between "tabs" and a "tab" (ColorList_Tab) that performs basic list functions such as adding, editing, deleting and saving colors.



Application running



A new tab can be addded by adding a new data context to the MainWindow resources and then adding the viewmodel to PageViewModels list in the MainWindowViewModel. This tab will then be represented by its Icon property from inheriting from IPageViewModel.



ColorList_Tab supports adding, editing, deleting and saving colors to a text file in a Json format. Two textboxes are used to input the color name and hex, add and edit mode can be switched between by clicking on the AddSwitchCommand and EditSwitchCommand buttons. The execute command then executes the selected mode. Edit and delete will perform the function upon the currently selected item in the Color List, with every change being saved to a text file in Json form. SampleColorCommand sets the InputHexString to the color of cursor on screen.



Besides general critique and corrections, I was hoping a few of my questions could be addressed:



  1. Is MainWindowViewModel a good way of implementing navigation in MVVM?


  2. Currently my App.Xaml.Cs is unused however I've seen examples where it is used to bind the viewmodel, which should I use?


  3. Have I correctly placed the AddColorItem, EditColorItem and DeleteColorItem methods in the Model?


  4. Should ColorClass use try and catch to create the brush or should an if else statement be used with a regex check on the hex code?


MainWindow:



<Window x:Class="MVVM_Color_Utilities.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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:local="clr-namespace:MVVM_Color_Utilities"
mc:Ignorable="d"

xmlns:ViewModel ="clr-namespace:MVVM_Color_Utilities.ViewModel"
xmlns:ColorList="clr-namespace:MVVM_Color_Utilities.ColorsList_Tab"

Title="MainWindow" Height="600" Width="1000"
WindowStartupLocation="CenterScreen" Background="LightGray"
WindowStyle="None" AllowsTransparency="True">

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

<Window.Resources>
<DataTemplate DataType="x:Type ColorList:ColorListViewModel">
<ColorList:ColorListView/>
</DataTemplate>
</Window.Resources>

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<Rectangle Grid.Column="1" Fill="StaticResource BurgundyBrush" />
<Rectangle Grid.RowSpan="2" Fill="StaticResource CyanTerracottaBrush" Margin="0,0,-0.5,0"/>

<ItemsControl Grid.Row="1" ItemsSource="Binding PageViewModels" HorizontalAlignment="Stretch">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Height="70"
Command="Binding DataContext.ChangePageCommand, RelativeSource=RelativeSource AncestorType=x:Type Window"
CommandParameter="Binding ">
<materialDesign:PackIcon Kind="Binding Icon" Height="30" Width="30" />
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

<Rectangle Grid.Column="1" Fill="StaticResource BurgundyBrush" Name="DragWindow" MouseDown="DragWindow_MouseDown"/>
<ContentControl Grid.Column="1" Grid.RowSpan="2" Content="Binding CurrentPageViewModel" />

<!--#region Window Controls-->
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top" Height="30" x:Name="Stack" Margin="7,0,0,0">
<!--Minimise-->
<Button Click="MinimizeWindowButton_Click" x:Name="MinimizeWindowButton">
<materialDesign:PackIcon Kind="Remove" Background="x:Null" BorderBrush="x:Null"/>
</Button>
<!--Toggle Window State-->
<Button Click="ChangeWindowState_Click" x:Name="ChangeWindowState" >
<materialDesign:PackIcon Kind="CropSquare" Background="x:Null" BorderBrush="x:Null"/>
</Button>
<!--Close Window-->
<Button Click="CloseWindow_Click" Height="Auto" x:Name="CloseWindow" >
<materialDesign:PackIcon Kind="Close" Background="x:Null" BorderBrush="x:Null"/>
</Button>
</StackPanel>
<!--#endregion-->
</Grid>
</Window>


MainWindowViewModel:



using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using MVVM_Color_Utilities.ViewModel.Helper_Classes;

namespace MVVM_Color_Utilities.ViewModel

public class MainWindowViewModel : ObservableObject

#region Fields
private ICommand _changePageCommand;
private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;
#endregion

#region Constructors
public MainWindowViewModel()

PageViewModels.Add(new ColorsList_Tab.ColorListViewModel());
//PageViewModels.Add(new ImageQuantizer_Tab.ImageQuantizerViewModel());

CurrentPageViewModel = PageViewModels[0];

#endregion
public ICommand ChangePageCommand

get

if (_changePageCommand == null)

_changePageCommand = new RelayCommand(
p => ChangeViewModel((IPageViewModel)p),
p => p is IPageViewModel);

return _changePageCommand;


public List<IPageViewModel> PageViewModels

get

if (_pageViewModels == null)
_pageViewModels = new List<IPageViewModel>();
return _pageViewModels;


public IPageViewModel CurrentPageViewModel

get

return _currentPageViewModel;

set

if (_currentPageViewModel != value)

_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");



private void ChangeViewModel(IPageViewModel viewModel)

if (!PageViewModels.Contains(viewModel))

PageViewModels.Add(viewModel);


CurrentPageViewModel = PageViewModels
.FirstOrDefault(vm => vm == viewModel);





IPageViewModel Interface
(Sets the icon for each viewmodel for display in the main window):



using MaterialDesignThemes.Wpf;

namespace MVVM_Color_Utilities.ViewModel.Helper_Classes

public interface IPageViewModel

PackIconKind Icon get;




ColorList_Tab View:



<UserControl x:Class="MVVM_Color_Utilities.ColorsList_Tab.ColorListView"
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:MVVM_Color_Utilities.ColorsList_Tab"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
>
<UserControl.InputBindings>
<KeyBinding Key="Enter" Command="Binding ExecuteCommand"/>
<KeyBinding Modifiers="Ctrl" Key="D" Command="Binding DeleteItem"/>
<KeyBinding Modifiers="Ctrl" Key="A" Command="Binding AddSwitchCommand"/>
<KeyBinding Modifiers="Ctrl" Key="E" Command="Binding EditSwitchCommand"/>
<KeyBinding Modifiers="Ctrl" Key="Q" Command="Binding SampleColorCommand"/>
</UserControl.InputBindings>

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<Grid Width="270" HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>

<!--This changes the "background" of the buttons as setting the background of buttons
while using material design causes an ugly shadow effect-->
<Rectangle Grid.Column="0" >
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="StaticResource BurgundyBrush"/>
<Style.Triggers>
<DataTrigger Binding="Binding AddingModeBool" Value="True">
<Setter Property="Fill" Value="StaticResource BurgundyLightBrush"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>

<Rectangle Grid.Column="1">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="StaticResource BurgundyLightBrush"/>
<Style.Triggers>
<DataTrigger Binding="Binding AddingModeBool" Value="True">
<Setter Property="Fill" Value="StaticResource BurgundyBrush"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>

<Button Grid.Column="0" Command="Binding Path=AddSwitchCommand">
<materialDesign:PackIcon Kind="Add"/>
</Button>
<Button Grid.Column="1" Command="Binding Path=EditSwitchCommand" >
<materialDesign:PackIcon Kind="Edit" />
</Button>
<Button Grid.Column="2" Command="Binding Path=DeleteItem">
<materialDesign:PackIcon Kind="Delete" VerticalAlignment="Center"/>
</Button>
</Grid>

<!--#region ListBox Header-->
<Grid Grid.Row="1" Background="StaticResource BurgundyLightBrush" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.8*"/>
<ColumnDefinition Width="1.5*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0.7*"/>
<ColumnDefinition Width="1.2*"/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<Style TargetType="TextBox">
<Setter Property="Background" Value="StaticResource BurgundyFaintBrush"/>
<Setter Property="Height" Value="20"/>
</Style>
</Grid.Resources>

<Rectangle Height="18" Width="60" Fill="White"/>
<Rectangle Height="18" Width="60" Fill="Binding IndicatorBrush" Stroke="Gray" StrokeThickness="0.4"/>

<TextBox Grid.Column="1" Width="160" Text="Binding InputName, UpdateSourceTrigger=PropertyChanged" />
<TextBox Grid.Column="2" Width="80" Text="Binding InputHex, UpdateSourceTrigger=PropertyChanged"/>

<Button Grid.Column="4" Command="Binding Path=SampleColorCommand">
<materialDesign:PackIcon Kind="Colorize" Width="40"/>
</Button>
<Button Grid.Column="5" Command="Binding Path=ExecuteCommand">
<materialDesign:PackIcon Kind="done" Width="100"/>
</Button>
</Grid>
<!--#endregion-->

<!--#region ListBox-->
<ListBox Grid.Row="2" ItemsSource="Binding ColorListSource" SelectedIndex="Binding SelectedItemIndex">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="StaticResource x:Type ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Property="Selector.IsSelectionActive" Value="true"/>
</MultiTrigger.Conditions>
<Setter Property="Background" Value="Gray"/>
</MultiTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid
>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.8*"/>
<ColumnDefinition Width="1.5*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0.7*"/>
<ColumnDefinition Width="1.2*"/>
</Grid.ColumnDefinitions>
<Rectangle Height="18" Width="60" Fill="Binding SampleBrush" Stroke="Gray" StrokeThickness="0.4"/>
<TextBlock Text="Binding Name" Width="160" Grid.Column="1"/>
<TextBlock Text="Binding Hex" Width="70" Grid.Column="2"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!--#endregion-->
</Grid>
</UserControl>



ColorList_Tab ViewModel:



using System.Collections.ObjectModel; 
using MVVM_Color_Utilities.ViewModel.Helper_Classes;
using MaterialDesignThemes.Wpf;
using System.Windows.Media;
using System.Windows.Input;
using MVVM_Color_Utilities.Helpers;
using System.Text.RegularExpressions;

namespace MVVM_Color_Utilities.ColorsList_Tab

class ColorListViewModel : ObservableObject, IPageViewModel

private readonly ColorListModel model = new ColorListModel();
private Regex _hexReg = new Regex("^#([0-9a-fA-F]0,8)?$"); //"^#(?:(?:[0-9a-fA-F]3)1,2




Observable Objects (not my code)



using System.ComponentModel;

namespace MVVM_Color_Utilities.ViewModel.Helper_Classes

public abstract class ObservableObject : INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(string propertyName)

var handler = PropertyChanged;
if (PropertyChanged != null)

handler(this, new PropertyChangedEventArgs(propertyName));







RelayCommand (not my code)
:



using System;
using System.Diagnostics;
using System.Windows.Input;

namespace MVVM_Color_Utilities.ViewModel.Helper_Classes

public class RelayCommand : ICommand

#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields

#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)


public RelayCommand(Action<object> execute, Predicate<object> canExecute)

if (execute == null)

throw new ArgumentNullException("execute");

_execute = execute;
_canExecute = canExecute;


#endregion // Constructors

#region ICommand Members

[DebuggerStepThrough]
public bool CanExecute(object parameters)

return _canExecute == null ? true : _canExecute(parameters);


public event EventHandler CanExecuteChanged

add CommandManager.RequerySuggested += value;
remove CommandManager.RequerySuggested -= value;


public void Execute(object parameters)

_execute(parameters);


#endregion // ICommand Members





ColorList_Tab Model:



using System.IO;
using System.Collections.ObjectModel;
using System.Windows.Media;
using Newtonsoft.Json;

namespace MVVM_Color_Utilities.ColorsList_Tab

public class ColorListModel

#region Fields
private readonly static string projectPath = Directory.GetParent(Directory.GetCurrentDirectory()).Parent.FullName; //Get Path of ColorItems file
private readonly static string colorsFilePath = projectPath + "/Resources/ColorItemsList.txt";
#endregion

#region Properties
public ObservableCollection<ColorClass> ColorClassList get;
=JsonConvert.DeserializeObject<ObservableCollection<ColorClass>>(File.ReadAllText(colorsFilePath));

public int NextID

get

return ColorClassList.Count > 0 ? ColorClassList[0].ID + 1 : 0;


#endregion

#region Methods
private void SaveColorsList()

try

File.WriteAllText(colorsFilePath, JsonConvert.SerializeObject(ColorClassList));

catch

public void AddColorItem(int index,string hexString, string nameString)

if(ColorClassList.Count > index
public void EditColorItem(int index,string hexString, string nameString)

if (ColorClassList.Count > index && ColorClassList.Count > 0)

ColorClassList[index] = new ColorClass(NextID, hexString, nameString);
SaveColorsList();


public void DeleteColorItem(int index)

if (ColorClassList.Count > index &&ColorClassList.Count>0)

ColorClassList.RemoveAt(index);
SaveColorsList();


#endregion




ColorClass:



 public class ColorClass

public ColorClass(int id, string hex, string name)

ID = id;
Name = name;
Hex = hex;


public int ID get; set;
public string Hex get; set;
public string Name get; set;

public SolidColorBrush SampleBrush

get

Color color;
try

color = (Color)ColorConverter.ConvertFromString(Hex);

catch//Invalid hex defaults to white.

color = (Color)ColorConverter.ConvertFromString("#FFFF");
Hex = "#FFFF";

return new SolidColorBrush(color);












share|improve this question









New contributor



DaemonFire is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.






$endgroup$













  • $begingroup$
    Do you happen to have this on GitHub too?
    $endgroup$
    – t3chb0t
    6 hours ago






  • 1




    $begingroup$
    Yes, however it does have two additional unfinished tabs. github.com/TimothyMakkison/MVVM-Color-Utilities . Sorry for the long question.
    $endgroup$
    – DaemonFire
    6 hours ago







  • 1




    $begingroup$
    Don't worry about the length, the question is great and as a matter of fact I wanted to fork it ;-]
    $endgroup$
    – t3chb0t
    6 hours ago






  • 2




    $begingroup$
    Is RelayCommand your code or picked from the internet? It's ok to use third-party classes, it's just so we know whether we should review that class.
    $endgroup$
    – dfhwze
    5 hours ago






  • 1




    $begingroup$
    I did review these 2 classes to challenge you that maybe creating your own classes would be a better idea than using these standard snippet classes.
    $endgroup$
    – dfhwze
    5 hours ago

















5












$begingroup$


By hashing together various online tutorials I've constructed my first MVVM application that lets users navigate between "tabs" and a "tab" (ColorList_Tab) that performs basic list functions such as adding, editing, deleting and saving colors.



Application running



A new tab can be addded by adding a new data context to the MainWindow resources and then adding the viewmodel to PageViewModels list in the MainWindowViewModel. This tab will then be represented by its Icon property from inheriting from IPageViewModel.



ColorList_Tab supports adding, editing, deleting and saving colors to a text file in a Json format. Two textboxes are used to input the color name and hex, add and edit mode can be switched between by clicking on the AddSwitchCommand and EditSwitchCommand buttons. The execute command then executes the selected mode. Edit and delete will perform the function upon the currently selected item in the Color List, with every change being saved to a text file in Json form. SampleColorCommand sets the InputHexString to the color of cursor on screen.



Besides general critique and corrections, I was hoping a few of my questions could be addressed:



  1. Is MainWindowViewModel a good way of implementing navigation in MVVM?


  2. Currently my App.Xaml.Cs is unused however I've seen examples where it is used to bind the viewmodel, which should I use?


  3. Have I correctly placed the AddColorItem, EditColorItem and DeleteColorItem methods in the Model?


  4. Should ColorClass use try and catch to create the brush or should an if else statement be used with a regex check on the hex code?


MainWindow:



<Window x:Class="MVVM_Color_Utilities.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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:local="clr-namespace:MVVM_Color_Utilities"
mc:Ignorable="d"

xmlns:ViewModel ="clr-namespace:MVVM_Color_Utilities.ViewModel"
xmlns:ColorList="clr-namespace:MVVM_Color_Utilities.ColorsList_Tab"

Title="MainWindow" Height="600" Width="1000"
WindowStartupLocation="CenterScreen" Background="LightGray"
WindowStyle="None" AllowsTransparency="True">

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

<Window.Resources>
<DataTemplate DataType="x:Type ColorList:ColorListViewModel">
<ColorList:ColorListView/>
</DataTemplate>
</Window.Resources>

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<Rectangle Grid.Column="1" Fill="StaticResource BurgundyBrush" />
<Rectangle Grid.RowSpan="2" Fill="StaticResource CyanTerracottaBrush" Margin="0,0,-0.5,0"/>

<ItemsControl Grid.Row="1" ItemsSource="Binding PageViewModels" HorizontalAlignment="Stretch">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Height="70"
Command="Binding DataContext.ChangePageCommand, RelativeSource=RelativeSource AncestorType=x:Type Window"
CommandParameter="Binding ">
<materialDesign:PackIcon Kind="Binding Icon" Height="30" Width="30" />
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

<Rectangle Grid.Column="1" Fill="StaticResource BurgundyBrush" Name="DragWindow" MouseDown="DragWindow_MouseDown"/>
<ContentControl Grid.Column="1" Grid.RowSpan="2" Content="Binding CurrentPageViewModel" />

<!--#region Window Controls-->
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top" Height="30" x:Name="Stack" Margin="7,0,0,0">
<!--Minimise-->
<Button Click="MinimizeWindowButton_Click" x:Name="MinimizeWindowButton">
<materialDesign:PackIcon Kind="Remove" Background="x:Null" BorderBrush="x:Null"/>
</Button>
<!--Toggle Window State-->
<Button Click="ChangeWindowState_Click" x:Name="ChangeWindowState" >
<materialDesign:PackIcon Kind="CropSquare" Background="x:Null" BorderBrush="x:Null"/>
</Button>
<!--Close Window-->
<Button Click="CloseWindow_Click" Height="Auto" x:Name="CloseWindow" >
<materialDesign:PackIcon Kind="Close" Background="x:Null" BorderBrush="x:Null"/>
</Button>
</StackPanel>
<!--#endregion-->
</Grid>
</Window>


MainWindowViewModel:



using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using MVVM_Color_Utilities.ViewModel.Helper_Classes;

namespace MVVM_Color_Utilities.ViewModel

public class MainWindowViewModel : ObservableObject

#region Fields
private ICommand _changePageCommand;
private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;
#endregion

#region Constructors
public MainWindowViewModel()

PageViewModels.Add(new ColorsList_Tab.ColorListViewModel());
//PageViewModels.Add(new ImageQuantizer_Tab.ImageQuantizerViewModel());

CurrentPageViewModel = PageViewModels[0];

#endregion
public ICommand ChangePageCommand

get

if (_changePageCommand == null)

_changePageCommand = new RelayCommand(
p => ChangeViewModel((IPageViewModel)p),
p => p is IPageViewModel);

return _changePageCommand;


public List<IPageViewModel> PageViewModels

get

if (_pageViewModels == null)
_pageViewModels = new List<IPageViewModel>();
return _pageViewModels;


public IPageViewModel CurrentPageViewModel

get

return _currentPageViewModel;

set

if (_currentPageViewModel != value)

_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");



private void ChangeViewModel(IPageViewModel viewModel)

if (!PageViewModels.Contains(viewModel))

PageViewModels.Add(viewModel);


CurrentPageViewModel = PageViewModels
.FirstOrDefault(vm => vm == viewModel);





IPageViewModel Interface
(Sets the icon for each viewmodel for display in the main window):



using MaterialDesignThemes.Wpf;

namespace MVVM_Color_Utilities.ViewModel.Helper_Classes

public interface IPageViewModel

PackIconKind Icon get;




ColorList_Tab View:



<UserControl x:Class="MVVM_Color_Utilities.ColorsList_Tab.ColorListView"
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:MVVM_Color_Utilities.ColorsList_Tab"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
>
<UserControl.InputBindings>
<KeyBinding Key="Enter" Command="Binding ExecuteCommand"/>
<KeyBinding Modifiers="Ctrl" Key="D" Command="Binding DeleteItem"/>
<KeyBinding Modifiers="Ctrl" Key="A" Command="Binding AddSwitchCommand"/>
<KeyBinding Modifiers="Ctrl" Key="E" Command="Binding EditSwitchCommand"/>
<KeyBinding Modifiers="Ctrl" Key="Q" Command="Binding SampleColorCommand"/>
</UserControl.InputBindings>

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<Grid Width="270" HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>

<!--This changes the "background" of the buttons as setting the background of buttons
while using material design causes an ugly shadow effect-->
<Rectangle Grid.Column="0" >
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="StaticResource BurgundyBrush"/>
<Style.Triggers>
<DataTrigger Binding="Binding AddingModeBool" Value="True">
<Setter Property="Fill" Value="StaticResource BurgundyLightBrush"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>

<Rectangle Grid.Column="1">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="StaticResource BurgundyLightBrush"/>
<Style.Triggers>
<DataTrigger Binding="Binding AddingModeBool" Value="True">
<Setter Property="Fill" Value="StaticResource BurgundyBrush"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>

<Button Grid.Column="0" Command="Binding Path=AddSwitchCommand">
<materialDesign:PackIcon Kind="Add"/>
</Button>
<Button Grid.Column="1" Command="Binding Path=EditSwitchCommand" >
<materialDesign:PackIcon Kind="Edit" />
</Button>
<Button Grid.Column="2" Command="Binding Path=DeleteItem">
<materialDesign:PackIcon Kind="Delete" VerticalAlignment="Center"/>
</Button>
</Grid>

<!--#region ListBox Header-->
<Grid Grid.Row="1" Background="StaticResource BurgundyLightBrush" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.8*"/>
<ColumnDefinition Width="1.5*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0.7*"/>
<ColumnDefinition Width="1.2*"/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<Style TargetType="TextBox">
<Setter Property="Background" Value="StaticResource BurgundyFaintBrush"/>
<Setter Property="Height" Value="20"/>
</Style>
</Grid.Resources>

<Rectangle Height="18" Width="60" Fill="White"/>
<Rectangle Height="18" Width="60" Fill="Binding IndicatorBrush" Stroke="Gray" StrokeThickness="0.4"/>

<TextBox Grid.Column="1" Width="160" Text="Binding InputName, UpdateSourceTrigger=PropertyChanged" />
<TextBox Grid.Column="2" Width="80" Text="Binding InputHex, UpdateSourceTrigger=PropertyChanged"/>

<Button Grid.Column="4" Command="Binding Path=SampleColorCommand">
<materialDesign:PackIcon Kind="Colorize" Width="40"/>
</Button>
<Button Grid.Column="5" Command="Binding Path=ExecuteCommand">
<materialDesign:PackIcon Kind="done" Width="100"/>
</Button>
</Grid>
<!--#endregion-->

<!--#region ListBox-->
<ListBox Grid.Row="2" ItemsSource="Binding ColorListSource" SelectedIndex="Binding SelectedItemIndex">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="StaticResource x:Type ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Property="Selector.IsSelectionActive" Value="true"/>
</MultiTrigger.Conditions>
<Setter Property="Background" Value="Gray"/>
</MultiTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid
>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.8*"/>
<ColumnDefinition Width="1.5*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0.7*"/>
<ColumnDefinition Width="1.2*"/>
</Grid.ColumnDefinitions>
<Rectangle Height="18" Width="60" Fill="Binding SampleBrush" Stroke="Gray" StrokeThickness="0.4"/>
<TextBlock Text="Binding Name" Width="160" Grid.Column="1"/>
<TextBlock Text="Binding Hex" Width="70" Grid.Column="2"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!--#endregion-->
</Grid>
</UserControl>



ColorList_Tab ViewModel:



using System.Collections.ObjectModel; 
using MVVM_Color_Utilities.ViewModel.Helper_Classes;
using MaterialDesignThemes.Wpf;
using System.Windows.Media;
using System.Windows.Input;
using MVVM_Color_Utilities.Helpers;
using System.Text.RegularExpressions;

namespace MVVM_Color_Utilities.ColorsList_Tab

class ColorListViewModel : ObservableObject, IPageViewModel

private readonly ColorListModel model = new ColorListModel();
private Regex _hexReg = new Regex("^#([0-9a-fA-F]0,8)?$"); //"^#(?:(?:[0-9a-fA-F]3)1,2




Observable Objects (not my code)



using System.ComponentModel;

namespace MVVM_Color_Utilities.ViewModel.Helper_Classes

public abstract class ObservableObject : INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(string propertyName)

var handler = PropertyChanged;
if (PropertyChanged != null)

handler(this, new PropertyChangedEventArgs(propertyName));







RelayCommand (not my code)
:



using System;
using System.Diagnostics;
using System.Windows.Input;

namespace MVVM_Color_Utilities.ViewModel.Helper_Classes

public class RelayCommand : ICommand

#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields

#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)


public RelayCommand(Action<object> execute, Predicate<object> canExecute)

if (execute == null)

throw new ArgumentNullException("execute");

_execute = execute;
_canExecute = canExecute;


#endregion // Constructors

#region ICommand Members

[DebuggerStepThrough]
public bool CanExecute(object parameters)

return _canExecute == null ? true : _canExecute(parameters);


public event EventHandler CanExecuteChanged

add CommandManager.RequerySuggested += value;
remove CommandManager.RequerySuggested -= value;


public void Execute(object parameters)

_execute(parameters);


#endregion // ICommand Members





ColorList_Tab Model:



using System.IO;
using System.Collections.ObjectModel;
using System.Windows.Media;
using Newtonsoft.Json;

namespace MVVM_Color_Utilities.ColorsList_Tab

public class ColorListModel

#region Fields
private readonly static string projectPath = Directory.GetParent(Directory.GetCurrentDirectory()).Parent.FullName; //Get Path of ColorItems file
private readonly static string colorsFilePath = projectPath + "/Resources/ColorItemsList.txt";
#endregion

#region Properties
public ObservableCollection<ColorClass> ColorClassList get;
=JsonConvert.DeserializeObject<ObservableCollection<ColorClass>>(File.ReadAllText(colorsFilePath));

public int NextID

get

return ColorClassList.Count > 0 ? ColorClassList[0].ID + 1 : 0;


#endregion

#region Methods
private void SaveColorsList()

try

File.WriteAllText(colorsFilePath, JsonConvert.SerializeObject(ColorClassList));

catch

public void AddColorItem(int index,string hexString, string nameString)

if(ColorClassList.Count > index
public void EditColorItem(int index,string hexString, string nameString)

if (ColorClassList.Count > index && ColorClassList.Count > 0)

ColorClassList[index] = new ColorClass(NextID, hexString, nameString);
SaveColorsList();


public void DeleteColorItem(int index)

if (ColorClassList.Count > index &&ColorClassList.Count>0)

ColorClassList.RemoveAt(index);
SaveColorsList();


#endregion




ColorClass:



 public class ColorClass

public ColorClass(int id, string hex, string name)

ID = id;
Name = name;
Hex = hex;


public int ID get; set;
public string Hex get; set;
public string Name get; set;

public SolidColorBrush SampleBrush

get

Color color;
try

color = (Color)ColorConverter.ConvertFromString(Hex);

catch//Invalid hex defaults to white.

color = (Color)ColorConverter.ConvertFromString("#FFFF");
Hex = "#FFFF";

return new SolidColorBrush(color);












share|improve this question









New contributor



DaemonFire is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.






$endgroup$













  • $begingroup$
    Do you happen to have this on GitHub too?
    $endgroup$
    – t3chb0t
    6 hours ago






  • 1




    $begingroup$
    Yes, however it does have two additional unfinished tabs. github.com/TimothyMakkison/MVVM-Color-Utilities . Sorry for the long question.
    $endgroup$
    – DaemonFire
    6 hours ago







  • 1




    $begingroup$
    Don't worry about the length, the question is great and as a matter of fact I wanted to fork it ;-]
    $endgroup$
    – t3chb0t
    6 hours ago






  • 2




    $begingroup$
    Is RelayCommand your code or picked from the internet? It's ok to use third-party classes, it's just so we know whether we should review that class.
    $endgroup$
    – dfhwze
    5 hours ago






  • 1




    $begingroup$
    I did review these 2 classes to challenge you that maybe creating your own classes would be a better idea than using these standard snippet classes.
    $endgroup$
    – dfhwze
    5 hours ago













5












5








5


1



$begingroup$


By hashing together various online tutorials I've constructed my first MVVM application that lets users navigate between "tabs" and a "tab" (ColorList_Tab) that performs basic list functions such as adding, editing, deleting and saving colors.



Application running



A new tab can be addded by adding a new data context to the MainWindow resources and then adding the viewmodel to PageViewModels list in the MainWindowViewModel. This tab will then be represented by its Icon property from inheriting from IPageViewModel.



ColorList_Tab supports adding, editing, deleting and saving colors to a text file in a Json format. Two textboxes are used to input the color name and hex, add and edit mode can be switched between by clicking on the AddSwitchCommand and EditSwitchCommand buttons. The execute command then executes the selected mode. Edit and delete will perform the function upon the currently selected item in the Color List, with every change being saved to a text file in Json form. SampleColorCommand sets the InputHexString to the color of cursor on screen.



Besides general critique and corrections, I was hoping a few of my questions could be addressed:



  1. Is MainWindowViewModel a good way of implementing navigation in MVVM?


  2. Currently my App.Xaml.Cs is unused however I've seen examples where it is used to bind the viewmodel, which should I use?


  3. Have I correctly placed the AddColorItem, EditColorItem and DeleteColorItem methods in the Model?


  4. Should ColorClass use try and catch to create the brush or should an if else statement be used with a regex check on the hex code?


MainWindow:



<Window x:Class="MVVM_Color_Utilities.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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:local="clr-namespace:MVVM_Color_Utilities"
mc:Ignorable="d"

xmlns:ViewModel ="clr-namespace:MVVM_Color_Utilities.ViewModel"
xmlns:ColorList="clr-namespace:MVVM_Color_Utilities.ColorsList_Tab"

Title="MainWindow" Height="600" Width="1000"
WindowStartupLocation="CenterScreen" Background="LightGray"
WindowStyle="None" AllowsTransparency="True">

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

<Window.Resources>
<DataTemplate DataType="x:Type ColorList:ColorListViewModel">
<ColorList:ColorListView/>
</DataTemplate>
</Window.Resources>

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<Rectangle Grid.Column="1" Fill="StaticResource BurgundyBrush" />
<Rectangle Grid.RowSpan="2" Fill="StaticResource CyanTerracottaBrush" Margin="0,0,-0.5,0"/>

<ItemsControl Grid.Row="1" ItemsSource="Binding PageViewModels" HorizontalAlignment="Stretch">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Height="70"
Command="Binding DataContext.ChangePageCommand, RelativeSource=RelativeSource AncestorType=x:Type Window"
CommandParameter="Binding ">
<materialDesign:PackIcon Kind="Binding Icon" Height="30" Width="30" />
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

<Rectangle Grid.Column="1" Fill="StaticResource BurgundyBrush" Name="DragWindow" MouseDown="DragWindow_MouseDown"/>
<ContentControl Grid.Column="1" Grid.RowSpan="2" Content="Binding CurrentPageViewModel" />

<!--#region Window Controls-->
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top" Height="30" x:Name="Stack" Margin="7,0,0,0">
<!--Minimise-->
<Button Click="MinimizeWindowButton_Click" x:Name="MinimizeWindowButton">
<materialDesign:PackIcon Kind="Remove" Background="x:Null" BorderBrush="x:Null"/>
</Button>
<!--Toggle Window State-->
<Button Click="ChangeWindowState_Click" x:Name="ChangeWindowState" >
<materialDesign:PackIcon Kind="CropSquare" Background="x:Null" BorderBrush="x:Null"/>
</Button>
<!--Close Window-->
<Button Click="CloseWindow_Click" Height="Auto" x:Name="CloseWindow" >
<materialDesign:PackIcon Kind="Close" Background="x:Null" BorderBrush="x:Null"/>
</Button>
</StackPanel>
<!--#endregion-->
</Grid>
</Window>


MainWindowViewModel:



using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using MVVM_Color_Utilities.ViewModel.Helper_Classes;

namespace MVVM_Color_Utilities.ViewModel

public class MainWindowViewModel : ObservableObject

#region Fields
private ICommand _changePageCommand;
private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;
#endregion

#region Constructors
public MainWindowViewModel()

PageViewModels.Add(new ColorsList_Tab.ColorListViewModel());
//PageViewModels.Add(new ImageQuantizer_Tab.ImageQuantizerViewModel());

CurrentPageViewModel = PageViewModels[0];

#endregion
public ICommand ChangePageCommand

get

if (_changePageCommand == null)

_changePageCommand = new RelayCommand(
p => ChangeViewModel((IPageViewModel)p),
p => p is IPageViewModel);

return _changePageCommand;


public List<IPageViewModel> PageViewModels

get

if (_pageViewModels == null)
_pageViewModels = new List<IPageViewModel>();
return _pageViewModels;


public IPageViewModel CurrentPageViewModel

get

return _currentPageViewModel;

set

if (_currentPageViewModel != value)

_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");



private void ChangeViewModel(IPageViewModel viewModel)

if (!PageViewModels.Contains(viewModel))

PageViewModels.Add(viewModel);


CurrentPageViewModel = PageViewModels
.FirstOrDefault(vm => vm == viewModel);





IPageViewModel Interface
(Sets the icon for each viewmodel for display in the main window):



using MaterialDesignThemes.Wpf;

namespace MVVM_Color_Utilities.ViewModel.Helper_Classes

public interface IPageViewModel

PackIconKind Icon get;




ColorList_Tab View:



<UserControl x:Class="MVVM_Color_Utilities.ColorsList_Tab.ColorListView"
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:MVVM_Color_Utilities.ColorsList_Tab"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
>
<UserControl.InputBindings>
<KeyBinding Key="Enter" Command="Binding ExecuteCommand"/>
<KeyBinding Modifiers="Ctrl" Key="D" Command="Binding DeleteItem"/>
<KeyBinding Modifiers="Ctrl" Key="A" Command="Binding AddSwitchCommand"/>
<KeyBinding Modifiers="Ctrl" Key="E" Command="Binding EditSwitchCommand"/>
<KeyBinding Modifiers="Ctrl" Key="Q" Command="Binding SampleColorCommand"/>
</UserControl.InputBindings>

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<Grid Width="270" HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>

<!--This changes the "background" of the buttons as setting the background of buttons
while using material design causes an ugly shadow effect-->
<Rectangle Grid.Column="0" >
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="StaticResource BurgundyBrush"/>
<Style.Triggers>
<DataTrigger Binding="Binding AddingModeBool" Value="True">
<Setter Property="Fill" Value="StaticResource BurgundyLightBrush"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>

<Rectangle Grid.Column="1">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="StaticResource BurgundyLightBrush"/>
<Style.Triggers>
<DataTrigger Binding="Binding AddingModeBool" Value="True">
<Setter Property="Fill" Value="StaticResource BurgundyBrush"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>

<Button Grid.Column="0" Command="Binding Path=AddSwitchCommand">
<materialDesign:PackIcon Kind="Add"/>
</Button>
<Button Grid.Column="1" Command="Binding Path=EditSwitchCommand" >
<materialDesign:PackIcon Kind="Edit" />
</Button>
<Button Grid.Column="2" Command="Binding Path=DeleteItem">
<materialDesign:PackIcon Kind="Delete" VerticalAlignment="Center"/>
</Button>
</Grid>

<!--#region ListBox Header-->
<Grid Grid.Row="1" Background="StaticResource BurgundyLightBrush" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.8*"/>
<ColumnDefinition Width="1.5*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0.7*"/>
<ColumnDefinition Width="1.2*"/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<Style TargetType="TextBox">
<Setter Property="Background" Value="StaticResource BurgundyFaintBrush"/>
<Setter Property="Height" Value="20"/>
</Style>
</Grid.Resources>

<Rectangle Height="18" Width="60" Fill="White"/>
<Rectangle Height="18" Width="60" Fill="Binding IndicatorBrush" Stroke="Gray" StrokeThickness="0.4"/>

<TextBox Grid.Column="1" Width="160" Text="Binding InputName, UpdateSourceTrigger=PropertyChanged" />
<TextBox Grid.Column="2" Width="80" Text="Binding InputHex, UpdateSourceTrigger=PropertyChanged"/>

<Button Grid.Column="4" Command="Binding Path=SampleColorCommand">
<materialDesign:PackIcon Kind="Colorize" Width="40"/>
</Button>
<Button Grid.Column="5" Command="Binding Path=ExecuteCommand">
<materialDesign:PackIcon Kind="done" Width="100"/>
</Button>
</Grid>
<!--#endregion-->

<!--#region ListBox-->
<ListBox Grid.Row="2" ItemsSource="Binding ColorListSource" SelectedIndex="Binding SelectedItemIndex">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="StaticResource x:Type ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Property="Selector.IsSelectionActive" Value="true"/>
</MultiTrigger.Conditions>
<Setter Property="Background" Value="Gray"/>
</MultiTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid
>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.8*"/>
<ColumnDefinition Width="1.5*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0.7*"/>
<ColumnDefinition Width="1.2*"/>
</Grid.ColumnDefinitions>
<Rectangle Height="18" Width="60" Fill="Binding SampleBrush" Stroke="Gray" StrokeThickness="0.4"/>
<TextBlock Text="Binding Name" Width="160" Grid.Column="1"/>
<TextBlock Text="Binding Hex" Width="70" Grid.Column="2"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!--#endregion-->
</Grid>
</UserControl>



ColorList_Tab ViewModel:



using System.Collections.ObjectModel; 
using MVVM_Color_Utilities.ViewModel.Helper_Classes;
using MaterialDesignThemes.Wpf;
using System.Windows.Media;
using System.Windows.Input;
using MVVM_Color_Utilities.Helpers;
using System.Text.RegularExpressions;

namespace MVVM_Color_Utilities.ColorsList_Tab

class ColorListViewModel : ObservableObject, IPageViewModel

private readonly ColorListModel model = new ColorListModel();
private Regex _hexReg = new Regex("^#([0-9a-fA-F]0,8)?$"); //"^#(?:(?:[0-9a-fA-F]3)1,2




Observable Objects (not my code)



using System.ComponentModel;

namespace MVVM_Color_Utilities.ViewModel.Helper_Classes

public abstract class ObservableObject : INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(string propertyName)

var handler = PropertyChanged;
if (PropertyChanged != null)

handler(this, new PropertyChangedEventArgs(propertyName));







RelayCommand (not my code)
:



using System;
using System.Diagnostics;
using System.Windows.Input;

namespace MVVM_Color_Utilities.ViewModel.Helper_Classes

public class RelayCommand : ICommand

#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields

#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)


public RelayCommand(Action<object> execute, Predicate<object> canExecute)

if (execute == null)

throw new ArgumentNullException("execute");

_execute = execute;
_canExecute = canExecute;


#endregion // Constructors

#region ICommand Members

[DebuggerStepThrough]
public bool CanExecute(object parameters)

return _canExecute == null ? true : _canExecute(parameters);


public event EventHandler CanExecuteChanged

add CommandManager.RequerySuggested += value;
remove CommandManager.RequerySuggested -= value;


public void Execute(object parameters)

_execute(parameters);


#endregion // ICommand Members





ColorList_Tab Model:



using System.IO;
using System.Collections.ObjectModel;
using System.Windows.Media;
using Newtonsoft.Json;

namespace MVVM_Color_Utilities.ColorsList_Tab

public class ColorListModel

#region Fields
private readonly static string projectPath = Directory.GetParent(Directory.GetCurrentDirectory()).Parent.FullName; //Get Path of ColorItems file
private readonly static string colorsFilePath = projectPath + "/Resources/ColorItemsList.txt";
#endregion

#region Properties
public ObservableCollection<ColorClass> ColorClassList get;
=JsonConvert.DeserializeObject<ObservableCollection<ColorClass>>(File.ReadAllText(colorsFilePath));

public int NextID

get

return ColorClassList.Count > 0 ? ColorClassList[0].ID + 1 : 0;


#endregion

#region Methods
private void SaveColorsList()

try

File.WriteAllText(colorsFilePath, JsonConvert.SerializeObject(ColorClassList));

catch

public void AddColorItem(int index,string hexString, string nameString)

if(ColorClassList.Count > index
public void EditColorItem(int index,string hexString, string nameString)

if (ColorClassList.Count > index && ColorClassList.Count > 0)

ColorClassList[index] = new ColorClass(NextID, hexString, nameString);
SaveColorsList();


public void DeleteColorItem(int index)

if (ColorClassList.Count > index &&ColorClassList.Count>0)

ColorClassList.RemoveAt(index);
SaveColorsList();


#endregion




ColorClass:



 public class ColorClass

public ColorClass(int id, string hex, string name)

ID = id;
Name = name;
Hex = hex;


public int ID get; set;
public string Hex get; set;
public string Name get; set;

public SolidColorBrush SampleBrush

get

Color color;
try

color = (Color)ColorConverter.ConvertFromString(Hex);

catch//Invalid hex defaults to white.

color = (Color)ColorConverter.ConvertFromString("#FFFF");
Hex = "#FFFF";

return new SolidColorBrush(color);












share|improve this question









New contributor



DaemonFire is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.






$endgroup$




By hashing together various online tutorials I've constructed my first MVVM application that lets users navigate between "tabs" and a "tab" (ColorList_Tab) that performs basic list functions such as adding, editing, deleting and saving colors.



Application running



A new tab can be addded by adding a new data context to the MainWindow resources and then adding the viewmodel to PageViewModels list in the MainWindowViewModel. This tab will then be represented by its Icon property from inheriting from IPageViewModel.



ColorList_Tab supports adding, editing, deleting and saving colors to a text file in a Json format. Two textboxes are used to input the color name and hex, add and edit mode can be switched between by clicking on the AddSwitchCommand and EditSwitchCommand buttons. The execute command then executes the selected mode. Edit and delete will perform the function upon the currently selected item in the Color List, with every change being saved to a text file in Json form. SampleColorCommand sets the InputHexString to the color of cursor on screen.



Besides general critique and corrections, I was hoping a few of my questions could be addressed:



  1. Is MainWindowViewModel a good way of implementing navigation in MVVM?


  2. Currently my App.Xaml.Cs is unused however I've seen examples where it is used to bind the viewmodel, which should I use?


  3. Have I correctly placed the AddColorItem, EditColorItem and DeleteColorItem methods in the Model?


  4. Should ColorClass use try and catch to create the brush or should an if else statement be used with a regex check on the hex code?


MainWindow:



<Window x:Class="MVVM_Color_Utilities.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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:local="clr-namespace:MVVM_Color_Utilities"
mc:Ignorable="d"

xmlns:ViewModel ="clr-namespace:MVVM_Color_Utilities.ViewModel"
xmlns:ColorList="clr-namespace:MVVM_Color_Utilities.ColorsList_Tab"

Title="MainWindow" Height="600" Width="1000"
WindowStartupLocation="CenterScreen" Background="LightGray"
WindowStyle="None" AllowsTransparency="True">

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

<Window.Resources>
<DataTemplate DataType="x:Type ColorList:ColorListViewModel">
<ColorList:ColorListView/>
</DataTemplate>
</Window.Resources>

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<Rectangle Grid.Column="1" Fill="StaticResource BurgundyBrush" />
<Rectangle Grid.RowSpan="2" Fill="StaticResource CyanTerracottaBrush" Margin="0,0,-0.5,0"/>

<ItemsControl Grid.Row="1" ItemsSource="Binding PageViewModels" HorizontalAlignment="Stretch">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Height="70"
Command="Binding DataContext.ChangePageCommand, RelativeSource=RelativeSource AncestorType=x:Type Window"
CommandParameter="Binding ">
<materialDesign:PackIcon Kind="Binding Icon" Height="30" Width="30" />
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

<Rectangle Grid.Column="1" Fill="StaticResource BurgundyBrush" Name="DragWindow" MouseDown="DragWindow_MouseDown"/>
<ContentControl Grid.Column="1" Grid.RowSpan="2" Content="Binding CurrentPageViewModel" />

<!--#region Window Controls-->
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top" Height="30" x:Name="Stack" Margin="7,0,0,0">
<!--Minimise-->
<Button Click="MinimizeWindowButton_Click" x:Name="MinimizeWindowButton">
<materialDesign:PackIcon Kind="Remove" Background="x:Null" BorderBrush="x:Null"/>
</Button>
<!--Toggle Window State-->
<Button Click="ChangeWindowState_Click" x:Name="ChangeWindowState" >
<materialDesign:PackIcon Kind="CropSquare" Background="x:Null" BorderBrush="x:Null"/>
</Button>
<!--Close Window-->
<Button Click="CloseWindow_Click" Height="Auto" x:Name="CloseWindow" >
<materialDesign:PackIcon Kind="Close" Background="x:Null" BorderBrush="x:Null"/>
</Button>
</StackPanel>
<!--#endregion-->
</Grid>
</Window>


MainWindowViewModel:



using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using MVVM_Color_Utilities.ViewModel.Helper_Classes;

namespace MVVM_Color_Utilities.ViewModel

public class MainWindowViewModel : ObservableObject

#region Fields
private ICommand _changePageCommand;
private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;
#endregion

#region Constructors
public MainWindowViewModel()

PageViewModels.Add(new ColorsList_Tab.ColorListViewModel());
//PageViewModels.Add(new ImageQuantizer_Tab.ImageQuantizerViewModel());

CurrentPageViewModel = PageViewModels[0];

#endregion
public ICommand ChangePageCommand

get

if (_changePageCommand == null)

_changePageCommand = new RelayCommand(
p => ChangeViewModel((IPageViewModel)p),
p => p is IPageViewModel);

return _changePageCommand;


public List<IPageViewModel> PageViewModels

get

if (_pageViewModels == null)
_pageViewModels = new List<IPageViewModel>();
return _pageViewModels;


public IPageViewModel CurrentPageViewModel

get

return _currentPageViewModel;

set

if (_currentPageViewModel != value)

_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");



private void ChangeViewModel(IPageViewModel viewModel)

if (!PageViewModels.Contains(viewModel))

PageViewModels.Add(viewModel);


CurrentPageViewModel = PageViewModels
.FirstOrDefault(vm => vm == viewModel);





IPageViewModel Interface
(Sets the icon for each viewmodel for display in the main window):



using MaterialDesignThemes.Wpf;

namespace MVVM_Color_Utilities.ViewModel.Helper_Classes

public interface IPageViewModel

PackIconKind Icon get;




ColorList_Tab View:



<UserControl x:Class="MVVM_Color_Utilities.ColorsList_Tab.ColorListView"
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:MVVM_Color_Utilities.ColorsList_Tab"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
>
<UserControl.InputBindings>
<KeyBinding Key="Enter" Command="Binding ExecuteCommand"/>
<KeyBinding Modifiers="Ctrl" Key="D" Command="Binding DeleteItem"/>
<KeyBinding Modifiers="Ctrl" Key="A" Command="Binding AddSwitchCommand"/>
<KeyBinding Modifiers="Ctrl" Key="E" Command="Binding EditSwitchCommand"/>
<KeyBinding Modifiers="Ctrl" Key="Q" Command="Binding SampleColorCommand"/>
</UserControl.InputBindings>

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<Grid Width="270" HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>

<!--This changes the "background" of the buttons as setting the background of buttons
while using material design causes an ugly shadow effect-->
<Rectangle Grid.Column="0" >
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="StaticResource BurgundyBrush"/>
<Style.Triggers>
<DataTrigger Binding="Binding AddingModeBool" Value="True">
<Setter Property="Fill" Value="StaticResource BurgundyLightBrush"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>

<Rectangle Grid.Column="1">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="StaticResource BurgundyLightBrush"/>
<Style.Triggers>
<DataTrigger Binding="Binding AddingModeBool" Value="True">
<Setter Property="Fill" Value="StaticResource BurgundyBrush"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>

<Button Grid.Column="0" Command="Binding Path=AddSwitchCommand">
<materialDesign:PackIcon Kind="Add"/>
</Button>
<Button Grid.Column="1" Command="Binding Path=EditSwitchCommand" >
<materialDesign:PackIcon Kind="Edit" />
</Button>
<Button Grid.Column="2" Command="Binding Path=DeleteItem">
<materialDesign:PackIcon Kind="Delete" VerticalAlignment="Center"/>
</Button>
</Grid>

<!--#region ListBox Header-->
<Grid Grid.Row="1" Background="StaticResource BurgundyLightBrush" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.8*"/>
<ColumnDefinition Width="1.5*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0.7*"/>
<ColumnDefinition Width="1.2*"/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<Style TargetType="TextBox">
<Setter Property="Background" Value="StaticResource BurgundyFaintBrush"/>
<Setter Property="Height" Value="20"/>
</Style>
</Grid.Resources>

<Rectangle Height="18" Width="60" Fill="White"/>
<Rectangle Height="18" Width="60" Fill="Binding IndicatorBrush" Stroke="Gray" StrokeThickness="0.4"/>

<TextBox Grid.Column="1" Width="160" Text="Binding InputName, UpdateSourceTrigger=PropertyChanged" />
<TextBox Grid.Column="2" Width="80" Text="Binding InputHex, UpdateSourceTrigger=PropertyChanged"/>

<Button Grid.Column="4" Command="Binding Path=SampleColorCommand">
<materialDesign:PackIcon Kind="Colorize" Width="40"/>
</Button>
<Button Grid.Column="5" Command="Binding Path=ExecuteCommand">
<materialDesign:PackIcon Kind="done" Width="100"/>
</Button>
</Grid>
<!--#endregion-->

<!--#region ListBox-->
<ListBox Grid.Row="2" ItemsSource="Binding ColorListSource" SelectedIndex="Binding SelectedItemIndex">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="StaticResource x:Type ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Property="Selector.IsSelectionActive" Value="true"/>
</MultiTrigger.Conditions>
<Setter Property="Background" Value="Gray"/>
</MultiTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid
>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.8*"/>
<ColumnDefinition Width="1.5*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0.7*"/>
<ColumnDefinition Width="1.2*"/>
</Grid.ColumnDefinitions>
<Rectangle Height="18" Width="60" Fill="Binding SampleBrush" Stroke="Gray" StrokeThickness="0.4"/>
<TextBlock Text="Binding Name" Width="160" Grid.Column="1"/>
<TextBlock Text="Binding Hex" Width="70" Grid.Column="2"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!--#endregion-->
</Grid>
</UserControl>



ColorList_Tab ViewModel:



using System.Collections.ObjectModel; 
using MVVM_Color_Utilities.ViewModel.Helper_Classes;
using MaterialDesignThemes.Wpf;
using System.Windows.Media;
using System.Windows.Input;
using MVVM_Color_Utilities.Helpers;
using System.Text.RegularExpressions;

namespace MVVM_Color_Utilities.ColorsList_Tab

class ColorListViewModel : ObservableObject, IPageViewModel

private readonly ColorListModel model = new ColorListModel();
private Regex _hexReg = new Regex("^#([0-9a-fA-F]0,8)?$"); //"^#(?:(?:[0-9a-fA-F]3)1,2




Observable Objects (not my code)



using System.ComponentModel;

namespace MVVM_Color_Utilities.ViewModel.Helper_Classes

public abstract class ObservableObject : INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(string propertyName)

var handler = PropertyChanged;
if (PropertyChanged != null)

handler(this, new PropertyChangedEventArgs(propertyName));







RelayCommand (not my code)
:



using System;
using System.Diagnostics;
using System.Windows.Input;

namespace MVVM_Color_Utilities.ViewModel.Helper_Classes

public class RelayCommand : ICommand

#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields

#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)


public RelayCommand(Action<object> execute, Predicate<object> canExecute)

if (execute == null)

throw new ArgumentNullException("execute");

_execute = execute;
_canExecute = canExecute;


#endregion // Constructors

#region ICommand Members

[DebuggerStepThrough]
public bool CanExecute(object parameters)

return _canExecute == null ? true : _canExecute(parameters);


public event EventHandler CanExecuteChanged

add CommandManager.RequerySuggested += value;
remove CommandManager.RequerySuggested -= value;


public void Execute(object parameters)

_execute(parameters);


#endregion // ICommand Members





ColorList_Tab Model:



using System.IO;
using System.Collections.ObjectModel;
using System.Windows.Media;
using Newtonsoft.Json;

namespace MVVM_Color_Utilities.ColorsList_Tab

public class ColorListModel

#region Fields
private readonly static string projectPath = Directory.GetParent(Directory.GetCurrentDirectory()).Parent.FullName; //Get Path of ColorItems file
private readonly static string colorsFilePath = projectPath + "/Resources/ColorItemsList.txt";
#endregion

#region Properties
public ObservableCollection<ColorClass> ColorClassList get;
=JsonConvert.DeserializeObject<ObservableCollection<ColorClass>>(File.ReadAllText(colorsFilePath));

public int NextID

get

return ColorClassList.Count > 0 ? ColorClassList[0].ID + 1 : 0;


#endregion

#region Methods
private void SaveColorsList()

try

File.WriteAllText(colorsFilePath, JsonConvert.SerializeObject(ColorClassList));

catch

public void AddColorItem(int index,string hexString, string nameString)

if(ColorClassList.Count > index
public void EditColorItem(int index,string hexString, string nameString)

if (ColorClassList.Count > index && ColorClassList.Count > 0)

ColorClassList[index] = new ColorClass(NextID, hexString, nameString);
SaveColorsList();


public void DeleteColorItem(int index)

if (ColorClassList.Count > index &&ColorClassList.Count>0)

ColorClassList.RemoveAt(index);
SaveColorsList();


#endregion




ColorClass:



 public class ColorClass

public ColorClass(int id, string hex, string name)

ID = id;
Name = name;
Hex = hex;


public int ID get; set;
public string Hex get; set;
public string Name get; set;

public SolidColorBrush SampleBrush

get

Color color;
try

color = (Color)ColorConverter.ConvertFromString(Hex);

catch//Invalid hex defaults to white.

color = (Color)ColorConverter.ConvertFromString("#FFFF");
Hex = "#FFFF";

return new SolidColorBrush(color);









c# wpf mvvm






share|improve this question









New contributor



DaemonFire is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.










share|improve this question









New contributor



DaemonFire is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.








share|improve this question




share|improve this question








edited 5 hours ago









dfhwze

11.3k2 gold badges22 silver badges74 bronze badges




11.3k2 gold badges22 silver badges74 bronze badges






New contributor



DaemonFire is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.








asked 8 hours ago









DaemonFireDaemonFire

264 bronze badges




264 bronze badges




New contributor



DaemonFire is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.




New contributor




DaemonFire is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
















  • $begingroup$
    Do you happen to have this on GitHub too?
    $endgroup$
    – t3chb0t
    6 hours ago






  • 1




    $begingroup$
    Yes, however it does have two additional unfinished tabs. github.com/TimothyMakkison/MVVM-Color-Utilities . Sorry for the long question.
    $endgroup$
    – DaemonFire
    6 hours ago







  • 1




    $begingroup$
    Don't worry about the length, the question is great and as a matter of fact I wanted to fork it ;-]
    $endgroup$
    – t3chb0t
    6 hours ago






  • 2




    $begingroup$
    Is RelayCommand your code or picked from the internet? It's ok to use third-party classes, it's just so we know whether we should review that class.
    $endgroup$
    – dfhwze
    5 hours ago






  • 1




    $begingroup$
    I did review these 2 classes to challenge you that maybe creating your own classes would be a better idea than using these standard snippet classes.
    $endgroup$
    – dfhwze
    5 hours ago
















  • $begingroup$
    Do you happen to have this on GitHub too?
    $endgroup$
    – t3chb0t
    6 hours ago






  • 1




    $begingroup$
    Yes, however it does have two additional unfinished tabs. github.com/TimothyMakkison/MVVM-Color-Utilities . Sorry for the long question.
    $endgroup$
    – DaemonFire
    6 hours ago







  • 1




    $begingroup$
    Don't worry about the length, the question is great and as a matter of fact I wanted to fork it ;-]
    $endgroup$
    – t3chb0t
    6 hours ago






  • 2




    $begingroup$
    Is RelayCommand your code or picked from the internet? It's ok to use third-party classes, it's just so we know whether we should review that class.
    $endgroup$
    – dfhwze
    5 hours ago






  • 1




    $begingroup$
    I did review these 2 classes to challenge you that maybe creating your own classes would be a better idea than using these standard snippet classes.
    $endgroup$
    – dfhwze
    5 hours ago















$begingroup$
Do you happen to have this on GitHub too?
$endgroup$
– t3chb0t
6 hours ago




$begingroup$
Do you happen to have this on GitHub too?
$endgroup$
– t3chb0t
6 hours ago




1




1




$begingroup$
Yes, however it does have two additional unfinished tabs. github.com/TimothyMakkison/MVVM-Color-Utilities . Sorry for the long question.
$endgroup$
– DaemonFire
6 hours ago





$begingroup$
Yes, however it does have two additional unfinished tabs. github.com/TimothyMakkison/MVVM-Color-Utilities . Sorry for the long question.
$endgroup$
– DaemonFire
6 hours ago





1




1




$begingroup$
Don't worry about the length, the question is great and as a matter of fact I wanted to fork it ;-]
$endgroup$
– t3chb0t
6 hours ago




$begingroup$
Don't worry about the length, the question is great and as a matter of fact I wanted to fork it ;-]
$endgroup$
– t3chb0t
6 hours ago




2




2




$begingroup$
Is RelayCommand your code or picked from the internet? It's ok to use third-party classes, it's just so we know whether we should review that class.
$endgroup$
– dfhwze
5 hours ago




$begingroup$
Is RelayCommand your code or picked from the internet? It's ok to use third-party classes, it's just so we know whether we should review that class.
$endgroup$
– dfhwze
5 hours ago




1




1




$begingroup$
I did review these 2 classes to challenge you that maybe creating your own classes would be a better idea than using these standard snippet classes.
$endgroup$
– dfhwze
5 hours ago




$begingroup$
I did review these 2 classes to challenge you that maybe creating your own classes would be a better idea than using these standard snippet classes.
$endgroup$
– dfhwze
5 hours ago










1 Answer
1






active

oldest

votes


















3














$begingroup$

Preface



There is a lot to review. This review is focused on the parts that are not related to the View or ViewModel.



Note: You have also edited the question concurrently with me making this review to state ObservableObject and RelayCommand are third-party classes. That's fine by me, since I still wanted to point out to you you shouldn't just copy these classes from internet and use them without any changes or proper consideration.




ColorListModel



You cannot reuse this class in scenarios where the paths are different than below. Also, when changing paths, you'd have to update this code with it. Consider reading these paths from a settings or configuration file.




private readonly static string projectPath 
= Directory.GetParent(Directory.GetCurrentDirectory())...
private readonly static string colorsFilePath
= projectPath + "/Resources/ColorItemsList.txt";



Consider using the lazy pattern to load properties on first demand to avoid unnecessary resources when not required: property ColorClassList.



SaveColorsList swallows all exceptions. At least log something or return a bool in sandbox mode if you don't like this method to throw exceptions.



AddColorItem, EditColorItem and DeleteColorItem only execute when the index is in bounds. The caller does not get feedback about out-of-bounds. Throw an exception or return a bool to let caller handle edge cases. Furthermore, AddColorItem does not use index as it stores on index 0 instead. Is this as designed?




(third-party code)



ObservableObject



This class is a design decision that I would challenge. It provides insufficient context to be a good candidate for a base class for other classes. I put it in the list of classes as ComparableBase, EquatableBase, DisposableBase. Think about whether a common base clase is really helpful here.



In addition, this class provides a public event PropertyChanged but never disposes it. Even if the WPF framework is able to subscribe and unsubscribe correctly from it, your own code-behind and other application logic is also allowed to subscribe. Classes that provide events should in my opinion always implement IDisposable.



RelayCommand



This is a famous allround command. I think it originates from Telerik, but several other variants are out there as well (DelegateCommand for instance). I would mention the use of third-party code in a question so we know how to review it.



This pattern with Action<object> execute is contravariant, but since object is the deepest base class, it's not that useful. For instance, you cannot exchange execute with an Action<string>. For this reason, consider creating also a RelayCommand<T> that accepts Action<T> execute. This class is more usable for code-behind and other application logic.



The predicate parameter should be made optional. You might also like to create an AsyncRelayCommand<T> with a composite CancelCommand (Example).






share|improve this answer











$endgroup$

















    Your Answer






    StackExchange.ifUsing("editor", function ()
    StackExchange.using("externalEditor", function ()
    StackExchange.using("snippets", function ()
    StackExchange.snippets.init();
    );
    );
    , "code-snippets");

    StackExchange.ready(function()
    var channelOptions =
    tags: "".split(" "),
    id: "196"
    ;
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function()
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled)
    StackExchange.using("snippets", function()
    createEditor();
    );

    else
    createEditor();

    );

    function createEditor()
    StackExchange.prepareEditor(
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: false,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    imageUploader:
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/4.0/"u003ecc by-sa 4.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    ,
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    );



    );







    DaemonFire is a new contributor. Be nice, and check out our Code of Conduct.









    draft saved

    draft discarded
















    StackExchange.ready(
    function ()
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f227661%2fwpf-mvvm-colorlister-with-navigation%23new-answer', 'question_page');

    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    3














    $begingroup$

    Preface



    There is a lot to review. This review is focused on the parts that are not related to the View or ViewModel.



    Note: You have also edited the question concurrently with me making this review to state ObservableObject and RelayCommand are third-party classes. That's fine by me, since I still wanted to point out to you you shouldn't just copy these classes from internet and use them without any changes or proper consideration.




    ColorListModel



    You cannot reuse this class in scenarios where the paths are different than below. Also, when changing paths, you'd have to update this code with it. Consider reading these paths from a settings or configuration file.




    private readonly static string projectPath 
    = Directory.GetParent(Directory.GetCurrentDirectory())...
    private readonly static string colorsFilePath
    = projectPath + "/Resources/ColorItemsList.txt";



    Consider using the lazy pattern to load properties on first demand to avoid unnecessary resources when not required: property ColorClassList.



    SaveColorsList swallows all exceptions. At least log something or return a bool in sandbox mode if you don't like this method to throw exceptions.



    AddColorItem, EditColorItem and DeleteColorItem only execute when the index is in bounds. The caller does not get feedback about out-of-bounds. Throw an exception or return a bool to let caller handle edge cases. Furthermore, AddColorItem does not use index as it stores on index 0 instead. Is this as designed?




    (third-party code)



    ObservableObject



    This class is a design decision that I would challenge. It provides insufficient context to be a good candidate for a base class for other classes. I put it in the list of classes as ComparableBase, EquatableBase, DisposableBase. Think about whether a common base clase is really helpful here.



    In addition, this class provides a public event PropertyChanged but never disposes it. Even if the WPF framework is able to subscribe and unsubscribe correctly from it, your own code-behind and other application logic is also allowed to subscribe. Classes that provide events should in my opinion always implement IDisposable.



    RelayCommand



    This is a famous allround command. I think it originates from Telerik, but several other variants are out there as well (DelegateCommand for instance). I would mention the use of third-party code in a question so we know how to review it.



    This pattern with Action<object> execute is contravariant, but since object is the deepest base class, it's not that useful. For instance, you cannot exchange execute with an Action<string>. For this reason, consider creating also a RelayCommand<T> that accepts Action<T> execute. This class is more usable for code-behind and other application logic.



    The predicate parameter should be made optional. You might also like to create an AsyncRelayCommand<T> with a composite CancelCommand (Example).






    share|improve this answer











    $endgroup$



















      3














      $begingroup$

      Preface



      There is a lot to review. This review is focused on the parts that are not related to the View or ViewModel.



      Note: You have also edited the question concurrently with me making this review to state ObservableObject and RelayCommand are third-party classes. That's fine by me, since I still wanted to point out to you you shouldn't just copy these classes from internet and use them without any changes or proper consideration.




      ColorListModel



      You cannot reuse this class in scenarios where the paths are different than below. Also, when changing paths, you'd have to update this code with it. Consider reading these paths from a settings or configuration file.




      private readonly static string projectPath 
      = Directory.GetParent(Directory.GetCurrentDirectory())...
      private readonly static string colorsFilePath
      = projectPath + "/Resources/ColorItemsList.txt";



      Consider using the lazy pattern to load properties on first demand to avoid unnecessary resources when not required: property ColorClassList.



      SaveColorsList swallows all exceptions. At least log something or return a bool in sandbox mode if you don't like this method to throw exceptions.



      AddColorItem, EditColorItem and DeleteColorItem only execute when the index is in bounds. The caller does not get feedback about out-of-bounds. Throw an exception or return a bool to let caller handle edge cases. Furthermore, AddColorItem does not use index as it stores on index 0 instead. Is this as designed?




      (third-party code)



      ObservableObject



      This class is a design decision that I would challenge. It provides insufficient context to be a good candidate for a base class for other classes. I put it in the list of classes as ComparableBase, EquatableBase, DisposableBase. Think about whether a common base clase is really helpful here.



      In addition, this class provides a public event PropertyChanged but never disposes it. Even if the WPF framework is able to subscribe and unsubscribe correctly from it, your own code-behind and other application logic is also allowed to subscribe. Classes that provide events should in my opinion always implement IDisposable.



      RelayCommand



      This is a famous allround command. I think it originates from Telerik, but several other variants are out there as well (DelegateCommand for instance). I would mention the use of third-party code in a question so we know how to review it.



      This pattern with Action<object> execute is contravariant, but since object is the deepest base class, it's not that useful. For instance, you cannot exchange execute with an Action<string>. For this reason, consider creating also a RelayCommand<T> that accepts Action<T> execute. This class is more usable for code-behind and other application logic.



      The predicate parameter should be made optional. You might also like to create an AsyncRelayCommand<T> with a composite CancelCommand (Example).






      share|improve this answer











      $endgroup$

















        3














        3










        3







        $begingroup$

        Preface



        There is a lot to review. This review is focused on the parts that are not related to the View or ViewModel.



        Note: You have also edited the question concurrently with me making this review to state ObservableObject and RelayCommand are third-party classes. That's fine by me, since I still wanted to point out to you you shouldn't just copy these classes from internet and use them without any changes or proper consideration.




        ColorListModel



        You cannot reuse this class in scenarios where the paths are different than below. Also, when changing paths, you'd have to update this code with it. Consider reading these paths from a settings or configuration file.




        private readonly static string projectPath 
        = Directory.GetParent(Directory.GetCurrentDirectory())...
        private readonly static string colorsFilePath
        = projectPath + "/Resources/ColorItemsList.txt";



        Consider using the lazy pattern to load properties on first demand to avoid unnecessary resources when not required: property ColorClassList.



        SaveColorsList swallows all exceptions. At least log something or return a bool in sandbox mode if you don't like this method to throw exceptions.



        AddColorItem, EditColorItem and DeleteColorItem only execute when the index is in bounds. The caller does not get feedback about out-of-bounds. Throw an exception or return a bool to let caller handle edge cases. Furthermore, AddColorItem does not use index as it stores on index 0 instead. Is this as designed?




        (third-party code)



        ObservableObject



        This class is a design decision that I would challenge. It provides insufficient context to be a good candidate for a base class for other classes. I put it in the list of classes as ComparableBase, EquatableBase, DisposableBase. Think about whether a common base clase is really helpful here.



        In addition, this class provides a public event PropertyChanged but never disposes it. Even if the WPF framework is able to subscribe and unsubscribe correctly from it, your own code-behind and other application logic is also allowed to subscribe. Classes that provide events should in my opinion always implement IDisposable.



        RelayCommand



        This is a famous allround command. I think it originates from Telerik, but several other variants are out there as well (DelegateCommand for instance). I would mention the use of third-party code in a question so we know how to review it.



        This pattern with Action<object> execute is contravariant, but since object is the deepest base class, it's not that useful. For instance, you cannot exchange execute with an Action<string>. For this reason, consider creating also a RelayCommand<T> that accepts Action<T> execute. This class is more usable for code-behind and other application logic.



        The predicate parameter should be made optional. You might also like to create an AsyncRelayCommand<T> with a composite CancelCommand (Example).






        share|improve this answer











        $endgroup$



        Preface



        There is a lot to review. This review is focused on the parts that are not related to the View or ViewModel.



        Note: You have also edited the question concurrently with me making this review to state ObservableObject and RelayCommand are third-party classes. That's fine by me, since I still wanted to point out to you you shouldn't just copy these classes from internet and use them without any changes or proper consideration.




        ColorListModel



        You cannot reuse this class in scenarios where the paths are different than below. Also, when changing paths, you'd have to update this code with it. Consider reading these paths from a settings or configuration file.




        private readonly static string projectPath 
        = Directory.GetParent(Directory.GetCurrentDirectory())...
        private readonly static string colorsFilePath
        = projectPath + "/Resources/ColorItemsList.txt";



        Consider using the lazy pattern to load properties on first demand to avoid unnecessary resources when not required: property ColorClassList.



        SaveColorsList swallows all exceptions. At least log something or return a bool in sandbox mode if you don't like this method to throw exceptions.



        AddColorItem, EditColorItem and DeleteColorItem only execute when the index is in bounds. The caller does not get feedback about out-of-bounds. Throw an exception or return a bool to let caller handle edge cases. Furthermore, AddColorItem does not use index as it stores on index 0 instead. Is this as designed?




        (third-party code)



        ObservableObject



        This class is a design decision that I would challenge. It provides insufficient context to be a good candidate for a base class for other classes. I put it in the list of classes as ComparableBase, EquatableBase, DisposableBase. Think about whether a common base clase is really helpful here.



        In addition, this class provides a public event PropertyChanged but never disposes it. Even if the WPF framework is able to subscribe and unsubscribe correctly from it, your own code-behind and other application logic is also allowed to subscribe. Classes that provide events should in my opinion always implement IDisposable.



        RelayCommand



        This is a famous allround command. I think it originates from Telerik, but several other variants are out there as well (DelegateCommand for instance). I would mention the use of third-party code in a question so we know how to review it.



        This pattern with Action<object> execute is contravariant, but since object is the deepest base class, it's not that useful. For instance, you cannot exchange execute with an Action<string>. For this reason, consider creating also a RelayCommand<T> that accepts Action<T> execute. This class is more usable for code-behind and other application logic.



        The predicate parameter should be made optional. You might also like to create an AsyncRelayCommand<T> with a composite CancelCommand (Example).







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited 4 hours ago

























        answered 5 hours ago









        dfhwzedfhwze

        11.3k2 gold badges22 silver badges74 bronze badges




        11.3k2 gold badges22 silver badges74 bronze badges
























            DaemonFire is a new contributor. Be nice, and check out our Code of Conduct.









            draft saved

            draft discarded

















            DaemonFire is a new contributor. Be nice, and check out our Code of Conduct.












            DaemonFire is a new contributor. Be nice, and check out our Code of Conduct.











            DaemonFire is a new contributor. Be nice, and check out our Code of Conduct.














            Thanks for contributing an answer to Code Review Stack Exchange!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid


            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.

            Use MathJax to format equations. MathJax reference.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f227661%2fwpf-mvvm-colorlister-with-navigation%23new-answer', 'question_page');

            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Invision Community Contents History See also References External links Navigation menuProprietaryinvisioncommunity.comIPS Community ForumsIPS Community Forumsthis blog entry"License Changes, IP.Board 3.4, and the Future""Interview -- Matt Mecham of Ibforums""CEO Invision Power Board, Matt Mecham Is a Liar, Thief!"IPB License Explanation 1.3, 1.3.1, 2.0, and 2.1ArchivedSecurity Fixes, Updates And Enhancements For IPB 1.3.1Archived"New Demo Accounts - Invision Power Services"the original"New Default Skin"the original"Invision Power Board 3.0.0 and Applications Released"the original"Archived copy"the original"Perpetual licenses being done away with""Release Notes - Invision Power Services""Introducing: IPS Community Suite 4!"Invision Community Release Notes

            Canceling a color specificationRandomly assigning color to Graphics3D objects?Default color for Filling in Mathematica 9Coloring specific elements of sets with a prime modified order in an array plotHow to pick a color differing significantly from the colors already in a given color list?Detection of the text colorColor numbers based on their valueCan color schemes for use with ColorData include opacity specification?My dynamic color schemes

            Tom Holland Mục lục Đầu đời và giáo dục | Sự nghiệp | Cuộc sống cá nhân | Phim tham gia | Giải thưởng và đề cử | Chú thích | Liên kết ngoài | Trình đơn chuyển hướngProfile“Person Details for Thomas Stanley Holland, "England and Wales Birth Registration Index, 1837-2008" — FamilySearch.org”"Meet Tom Holland... the 16-year-old star of The Impossible""Schoolboy actor Tom Holland finds himself in Oscar contention for role in tsunami drama"“Naomi Watts on the Prince William and Harry's reaction to her film about the late Princess Diana”lưu trữ"Holland and Pflueger Are West End's Two New 'Billy Elliots'""I'm so envious of my son, the movie star! British writer Dominic Holland's spent 20 years trying to crack Hollywood - but he's been beaten to it by a very unlikely rival"“Richard and Margaret Povey of Jersey, Channel Islands, UK: Information about Thomas Stanley Holland”"Tom Holland to play Billy Elliot""New Billy Elliot leaving the garage"Billy Elliot the Musical - Tom Holland - Billy"A Tale of four Billys: Tom Holland""The Feel Good Factor""Thames Christian College schoolboys join Myleene Klass for The Feelgood Factor""Government launches £600,000 arts bursaries pilot""BILLY's Chapman, Holland, Gardner & Jackson-Keen Visit Prime Minister""Elton John 'blown away' by Billy Elliot fifth birthday" (video with John's interview and fragments of Holland's performance)"First News interviews Arrietty's Tom Holland"“33rd Critics' Circle Film Awards winners”“National Board of Review Current Awards”Bản gốc"Ron Howard Whaling Tale 'In The Heart Of The Sea' Casts Tom Holland"“'Spider-Man' Finds Tom Holland to Star as New Web-Slinger”lưu trữ“Captain America: Civil War (2016)”“Film Review: ‘Captain America: Civil War’”lưu trữ“‘Captain America: Civil War’ review: Choose your own avenger”lưu trữ“The Lost City of Z reviews”“Sony Pictures and Marvel Studios Find Their 'Spider-Man' Star and Director”“‘Mary Magdalene’, ‘Current War’ & ‘Wind River’ Get 2017 Release Dates From Weinstein”“Lionsgate Unleashing Daisy Ridley & Tom Holland Starrer ‘Chaos Walking’ In Cannes”“PTA's 'Master' Leads Chicago Film Critics Nominations, UPDATED: Houston and Indiana Critics Nominations”“Nominaciones Goya 2013 Telecinco Cinema – ENG”“Jameson Empire Film Awards: Martin Freeman wins best actor for performance in The Hobbit”“34th Annual Young Artist Awards”Bản gốc“Teen Choice Awards 2016—Captain America: Civil War Leads Second Wave of Nominations”“BAFTA Film Award Nominations: ‘La La Land’ Leads Race”“Saturn Awards Nominations 2017: 'Rogue One,' 'Walking Dead' Lead”Tom HollandTom HollandTom HollandTom Hollandmedia.gettyimages.comWorldCat Identities300279794no20130442900000 0004 0355 42791085670554170004732cb16706349t(data)XX5557367