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.