I have two views for my application. Looking at the first View, UpdaterMainView, you can see that I create a Grid with two columns. The left column is split even further, into 5 rows. What I want to do is fill the five rows from a list of objects that are stored in the UpdaterMainViewModel, TaskList. I created a custom User Control called TaskView which is the layout of how I want to display the information of the list, a button, and a textbox.
When I instantiate the UpdaterMainViewModel, my application scans a directory for a few things and creates a list of tasks to complete. There are only five total possible tasks, hence why I created five rows. If the conditions aren't met and Task 2 doesn't need to be run, I don't want to show the UserControl and the one below it, should move up. I don't want to use the code-behind unless it can still meet the guidelines of MVVM.
For testing, I added <local:TaskView Grid.Column="0" Grid.Row="0"/>
to the UpdaterMainView and created a default constructor. But, what I need, is to add TaskView for each item in TaskList. To get this to work, I also have to create the UserControls with a different constructor. When you use DataContext, it uses a parameterless constructor. I somehow need to use the constructor with the enum parameter to fill the grid with each appropriate TaskView.
UpdaterMainView:
<UserControl x:Class="POSUpdaterGUI.View.UpdaterMainView"
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:POSUpdaterGUI.View" xmlns:local1="clr-namespace:POSUpdaterGUI.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<local1:UpdaterMainViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="60" />
<RowDefinition Height="60" />
<RowDefinition Height="60" />
<RowDefinition Height="60" />
<RowDefinition Height="60" />
</Grid.RowDefinitions>
<local:TaskView Grid.Column="0" Grid.Row="0"/>
<local:TaskView Grid.Column="0" Grid.Row="1"/>
<local:TaskView Grid.Column="0" Grid.Row="2"/>
<local:TaskView Grid.Column="0" Grid.Row="3"/>
</Grid>
<TextBox Grid.Column="1" Margin="10" TextWrapping="Wrap" Text="{Binding OutputText}"/>
</Grid>
</UserControl>
UpdaterMainViewModel:
using POSUpdaterLibrary;
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace POSUpdaterGUI.ViewModel
{
public class UpdaterMainViewModel : INotifyPropertyChanged
{
private string outputText;
public string OutputText
{
get => outputText;
set
{
outputText = value;
NotifyPropertyChanged("OutputText");
}
}
public UpdateTaskList TaskList { get; set; }
public UpdaterMainViewModel()
{
Log("Application Starting.");
TaskList = new UpdateTaskList();
TaskList.LoggerEvent = TaskList_LoggerEvent;
TaskList.GetList(AppContext.BaseDirectory);
}
private void TaskList_LoggerEvent(object sender, LoggerEventArgs e)
{
Log(e.LogString);
}
private void Log(string message)
{
if (OutputText == null)
{
OutputText = DateTime.Now.ToString() " - " message;
return;
}
OutputText = "\n" DateTime.Now.ToString() " - " message;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
TaskView:
<UserControl x:Class="POSUpdaterGUI.View.TaskView"
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:POSUpdaterGUI.View" xmlns:local1="clr-namespace:POSUpdaterGUI.ViewModel"
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="500">
<UserControl.DataContext>
<local1:TaskViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" HorizontalAlignment="Center" Margin="0,0,0,0" VerticalAlignment="Center" Height="50" Width="50" Command="{Binding RunTaskCommand}">
<Image Source="{Binding StatusImage}"/>
</Button>
<Label Grid.Column="1" Content="{Binding TaskName}" HorizontalAlignment="Left" Margin="15,0,0,0" VerticalAlignment="Center"/>
</Grid>
TaskViewModel:
using POSUpdaterLibrary;
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace POSUpdaterGUI.ViewModel
{
class TaskViewModel : INotifyPropertyChanged
{
public string TaskName { get; set; }
public ICommand RunTaskCommand { get; set; }
public string StatusImage { get; set; }
public TaskViewModel()
{
TaskName = "undefined";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
}
public TaskViewModel(UpdaterConstants.TaskType taskType)
{
switch (taskType)
{
case UpdaterConstants.TaskType.KillProcesses:
TaskName = "Processes Killed";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
case UpdaterConstants.TaskType.FileCopy:
TaskName = "Files Copied";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
case UpdaterConstants.TaskType.DatabaseUpdates:
TaskName = "Database Updated";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
case UpdaterConstants.TaskType.COMDLLsRegistered:
TaskName = "COM DLLs Registered";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
case UpdaterConstants.TaskType.FileCleanup:
TaskName = "Files Cleaned";
RunTaskCommand = new RelayCommand(new Action<object>(DefaultMethod));
StatusImage = STARTIMAGE;
break;
default:
break;
}
}
public enum Status
{
START,
COMPLETE,
FAILED,
WAIT
}
internal const string STARTIMAGE = "/data/start.png";
internal const string COMPLETEIMAGE = "/data/complete.png";
internal const string FAILEDIMAGE = "/data/failed.png";
internal const string WAITIMAGE = "/data/wait.png";
public event PropertyChangedEventHandler PropertyChanged;
private void DefaultMethod(object obj)
{
// Shouldn't be shown. Just a hold-over
throw new NotImplementedException();
}
}
}
The UpdaterMainView is the main view of the application and is always showing. To prove this, here is my MainWindow.xaml:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:POSUpdaterGUI"
xmlns:View="clr-namespace:POSUpdaterGUI.View" x:Class="POSUpdaterGUI.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<View:UpdaterMainView/>
</Grid>
CodePudding user response:
Thanks @Clemens for pointing me in the right direction! I did not need a separate View. I did need to use Data Templating.
Here is what I ended up with for the UpdaterMainView:
<UserControl x:Class="POSUpdaterGUI.View.UpdaterMainView"
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:POSUpdaterGUI.View" xmlns:local1="clr-namespace:POSUpdaterGUI.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<local1:UpdaterMainViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" Margin="10" ItemsSource="{Binding TaskList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" HorizontalAlignment="Center" Margin="0,0,0,0" VerticalAlignment="Center" Height="50" Width="50" Command="{Binding Command}">
<Image Source="{Binding StatusImage}"/>
</Button>
<Label Grid.Column="1" Content="{Binding TaskName}" HorizontalAlignment="Left" Margin="15,0,0,0" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Grid.Column="1" Margin="10" TextWrapping="Wrap" Text="{Binding OutputText}"/>
</Grid>
</UserControl>
Here is what I ended up with for the UpdaterMainViewModel:
using POSUpdaterGUI.Models;
using POSUpdaterLibrary;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace POSUpdaterGUI.ViewModel
{
public class UpdaterMainViewModel : INotifyPropertyChanged
{
private UpdaterLogger _logger;
private string outputText;
public string OutputText
{
get => outputText;
set
{
outputText = value;
NotifyPropertyChanged("OutputText");
}
}
private List<UpdateTaskWrapper> taskList;
public List<UpdateTaskWrapper> TaskList
{
get => taskList;
set
{
taskList = value;
NotifyPropertyChanged("TaskList");
}
}
public UpdaterMainViewModel()
{
_logger = UpdaterLogger.Instance;
_logger.LoggerEvent = Logger_LoggerEvent;
Log("Application Starting.");
TaskList = GetTaskList();
}
private void Logger_LoggerEvent(object sender, LoggerEventArgs e)
{
Log(e.LogString);
}
private List<UpdateTaskWrapper> GetTaskList()
{
UpdateTaskList list = new UpdateTaskList();
var myList = list.GetList(AppContext.BaseDirectory);
// Create one out of the Wrapper class.
List<UpdateTaskWrapper> wrappedList = new List<UpdateTaskWrapper>();
foreach (var task in myList)
{
wrappedList.Add(new UpdateTaskWrapper(task));
}
return wrappedList;
}
private void Log(string message)
{
if (OutputText == null)
{
OutputText = DateTime.Now.ToString() " - " message;
return;
}
OutputText = "\n" DateTime.Now.ToString() " - " message;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
The most important parts were that I removed the TaskView, Added Datatemplating (between the tags), and changed the List from a custom UpdaterTaskList to List.