As the best way to learn something is to play with it, so I build a little mvvm data system. Actually I’ve got similar with winforms (MVP with passive views), so I try to add another UI to the system.
A common data from contains three components:
- filter
- grid view and
- detail view (of one record)
Because grid view is the same for all data objects (with the difference of the columns), I decided to keep it as a one user control and only configure columns.
And so is my solution:
GridController.cs:
[NotifyPropertyChanged]
public abstract class GridController : ViewModelBase, IGridController, IGridConfiguator
{
private IDictionary<string, object> m_Criteria;
protected Type m_CurrentType;
private IPager m_Pager;
public bool AllowMultiselect { get; set; }
public object CurrentItem { get; set; }
public IMultidataRepository DataRepository { get; set; }
/// <summary>
/// Gets or sets the grid data. Data can be aded by that property, or by ShowData function
/// </summary>
/// <value>The grid data.</value>
public IList GridData { get; protected set; }
public IPager Pager
{
get
{
return m_Pager;
}
set
{
m_Pager = value;
m_Pager.PagesChanged += Pager_PagesChanged;
}
}
public event EventHandler GoToFilter;
public event EventHandler ShowDetail;
/// <summary>
/// Loads the data. If m_Criteria are null or empty then it should equal as GetAll()
/// </summary>
public void LoadData()
{
GridData.Clear();
using (new Workspace())
{
var x = DataRepository.FilterByProperties(m_CurrentType, m_Criteria, null, Pager.RecordFrom, Pager.PageSize);
foreach (var item in x)
{
GridData.Add(item);
}
}
}
public void OnGoToFilter()
{
if (GoToFilter != null)
{
GoToFilter(this, EventArgs.Empty);
}
}
public void OnShowDetail()
{
if (ShowDetail != null)
{
ShowDetail(this, EventArgs.Empty);
}
}
/// <summary>
/// Shows the data. Data will be loaded form the storage and displayed in a grid
/// </summary>
///
<param name="criteria">The criteria.</param>
public void ShowData(IDictionary<string, object> criteria)
{
m_Criteria = criteria;
LoadData();
}
private void Pager_PagesChanged(object sender, EventArgs e)
{
LoadData();
}
public virtual void Configure(DataGrid gridToConfigure)
{
}
}
It is a ModeView class which contains IPager class for switching between pages of records.
NotifyPropertyChanged class attribute is for Postsharp to add required code for properties.
And the GenericView:
<StackPanel>
<local:GridToolbar></local:GridToolbar>
<wt:DataGrid x:Name="GridWithData" ItemsSource="{Binding GridData, Mode=OneWay}" SelectedItem="{Binding CurrentItem}">
</wt:DataGrid>
</StackPanel>
Generic grid view contains grid toolbar and a grid wich is common for every data object. And additionally it requires code behind file:
public partial class GenericGridView : UserControl
{
public GenericGridView()
{
InitializeComponent();
Loaded += GenericGridView_Loaded;
}
void GenericGridView_Loaded(object sender, RoutedEventArgs e)
{
ConfigureGrid();
}
private void ConfigureGrid()
{
IGridConfiguator c = DataContext as IGridConfiguator;
if (c != null)
{
GridWithData.AutoGenerateColumns = false;
c.Configure(GridWithData);
}
else
{
GridWithData.AutoGenerateColumns = true;
}
}
}
Of course Configure function should be implemented in concrete class eg. PersonGridViewModel:
[NotifyPropertyChanged]
public class PersonGridViewModel : GridController
{
public PersonGridViewModel()
{
m_CurrentType = typeof(PersonForGrid);
GridData = new ObservableCollection<PersonForGrid>();
}
public override void Configure(DataGrid gridToConfigure)
{
gridToConfigure.Columns.Add(
new DataGridTextColumn()
{
Header = "First name",
IsReadOnly = true,
Binding = new Binding(PersonForGrid.FIRSTNAME)
});
gridToConfigure.Columns.Add(
new DataGridTextColumn()
{
Header = "Surename",
IsReadOnly = true,
Binding = new Binding(PersonForGrid.LASTNAME)
});
gridToConfigure.Columns.Add(
new DataGridTextColumn()
{
Header = "Birth date",
IsReadOnly = true,
Binding = new Binding(PersonForGrid.BIRTHDATY)
{
StringFormat = "d"
}
});
}
}
Actually that solution fits me, but probably it not the last word