I needed to write code in WPF for a client. The application is done with prism (not my decision, they already had people working on it), so I got a sparkling clean new module to work in. Since this was my first time writing WPF code going to production, I wanted to handle the code in a good manner. I decided to go MVVM.
I structured the module as follows (I do need to move the RelayCommand
and ViewModelBase
):
- Services (
IItemTypeService.cs
,ItemTypeService.cs
) - ViewModels (
ItemTypeViewModel.cs
,ItemTypeViewModel.cs
,RelayCommand.cs
,ViewModelBase.cs
) - Views (
ItemTypeAdminView.xaml
,ItemTypeDetailView.xaml
)
The ViewModelBase
implements INotifyPropertyChanged
.
The ItemTypeAdminView
is the main view. ItemTypeDetailView
is a user control and is put into the ItemTypeAdminView
. The ItemTypeAdminView
will in the future be expanded with other sections, and those sections will use the same information as the ItemTypeDetailView
.
Therefore, I made a ViewModel
for the ItemTypeAdminView
, not for the ItemTypeDetailView
. Every ItemType
is coming from Linq2SQL (yes, I know about EF - I wasn't involved in this part), and is wrapped in a ItemTypeViewModel
so it can implement the INotifyPropertyChanged
.
The code for the ItemTypeAdminViewModel
:
public class ItemTypeAdminViewModel : ViewModelBase
{
#region private fields
private ICollectionView collectionView;
private IItemTypeService itemTypeService;
#endregion
#region automatic properties
public ObservableCollection<ItemTypeViewModel> ItemTypes { get; private set; }
public IEnumerable<Company> Companies { get; private set; }
public IEnumerable<CompanyGTIN> CompanyGTINs { get; private set; }
private Company selectedCompany;
public Company SelectedCompany
{
get { return selectedCompany; }
set
{
selectedCompany = value;
LoadCompanyGTINs();
}
}
#endregion properties
#region constructors
public ItemTypeAdminViewModel(IItemTypeService itemTypeService)
{
this.itemTypeService = itemTypeService;
Initialize();
}
#endregion
#region private methods
private void Initialize()
{
//Should I wrap in Try/Catch? Try/Catch is expensive, must find something else...
ItemTypes = new ObservableCollection<ItemTypeViewModel>(itemTypeService.GetItemTypes());
Companies = itemTypeService.GetCompanies();
collectionView = CollectionViewSource.GetDefaultView(ItemTypes);
}
private void LoadCompanyGTINs()
{
CompanyGTINs = itemTypeService.GetCompanyGTINs(selectedCompany.ID);
OnPropertyChanged("CompanyGTINs");
}
#endregion
#region commands
public ICommand GoToFirstItemType
{
get
{
return new RelayCommand(() => collectionView.MoveCurrentToFirst(),
() => collectionView.CurrentPosition >= 1);
}
}
public ICommand GoToLastItemType
{
get
{
return new RelayCommand(() => collectionView.MoveCurrentToLast(),
() => collectionView.CurrentPosition < (ItemTypes.Count - 1));
}
}
public ICommand NextItemType
{
get
{
LoadCompanyGTINs();
return new RelayCommand(() => collectionView.MoveCurrentToNext(),
() => collectionView.CurrentPosition < (ItemTypes.Count - 1));
}
}
public ICommand PreviousItemType
{
get
{
return new RelayCommand(() => collectionView.MoveCurrentToPrevious(),
() => collectionView.CurrentPosition >= 1);
}
}
#endregion
}
And the code for the ItemTypeDetailView
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid DataContext="{Binding ItemTypes}">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="Name:" />
<TextBox Grid.Column="2" Text="{Binding Name}" />
<TextBlock Grid.Row="1" Text="Description:" />
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Description}" />
<TextBlock Grid.Row="2" Text="Manufacturer:" />
<ComboBox Grid.Column="1" Grid.Row="2"
DisplayMemberPath="Name"
ItemsSource="{Binding Path=DataContext.Companies, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl, AncestorLevel=2}}"
Name="ManufacturerComboBox"
SelectedItem="{Binding Mode=TwoWay, Path=DataContext.SelectedCompany, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl, AncestorLevel=2}}"
SelectedValue="{Binding ManufacturerID}"
SelectedValuePath="ID" />
<TextBlock Grid.Row="10" Text="Manufacturer Product Code:"/>
<TextBox Grid.Column="1" Grid.Row="10" Text="{Binding ManufacturerProductCode}" />
<TextBlock Grid.Row="11" Text="Manufacturer Product GS1 Code:"/>
<TextBox Grid.Column="1" Grid.Row="11" Text="{Binding ManufacturerProductGS1Code}" />
<TextBlock Grid.Row="12" Text="GS1GTINID:"/>
<ComboBox DisplayMemberPath="GTIN"
Grid.Column="1" Grid.Row="12"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=DataContext.CompanyGTINs, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl, AncestorLevel=2}}"
SelectedValue="{Binding GS1GTINID}"
SelectedValuePath="ID" />
</Grid>
<Grid Grid.Row="2">
<StackPanel Grid.Row="4" Orientation="Horizontal">
<Button Command="{Binding GoToFirstItemType}"><<</Button>
<TextBlock Margin="5,0,5,0" />
<Button Command="{Binding PreviousItemType}"><</Button>
<TextBlock Margin="5,0,5,0" />
<Button Command="{Binding NextItemType}">></Button>
<TextBlock Margin="5,0,5,0" />
<Button Command="{Binding GoToLastItemType}">>></Button>
<TextBlock Margin="5,0,5,0" />
<Button Command="{Binding New}">New</Button>
</StackPanel>
</Grid>
</Grid>
I am really curious about what could be improved structure/code wise and/or what should be left out.