There were only 2 of us on the project for the 1st 8 months (and my colleague
spent a lot of that time on the WCF end of things), so I do appreciate how
difficult it ends up being. My rule of thumb has always been to write what you
need when you need it, when you find yourself copying it for the 2nd or 3rd
time to use elsewhere, it's time to make yourself a baseclass and derive from
then on. Trying to second guess when it's really worth writing base classes &
what they may need to do in the future will drive you insane and lead to
endless refactoring unless you're amazingly prescient.
One thing which we do a little differently than most is to use
ObjectDataProviders as our data sources rather than binding directly through an
assigned DataContext (most of the time). What I found was confusing early on
with complex data sources, was telling from looking at the XAML exactly what on
earth any control was binding to exactly.
So when our IOC creates the View and ViewModel classes, it passes the VM
instance into the View via a method a bit like this :
/// <summary>
/// The view model associated with this view.
/// </summary>
public AccountWalletVM ViewModel
{
get
{
return _viewModel;
}
set
{
_viewModel = value;
ModelObjectDataProvider modp =
((ModelObjectDataProvider)this.FindResource("ViewModel"));
modp.ObjectInstance = _viewModel;
modp.Refresh();
}
}
private AccountWalletVM _viewModel = null;
Then in the XAML <UserControl.Resources> section :
<ss:ModelObjectDataProvider x:Key="ViewModel"
d:IsDataSource="True"
ObjectType="{x:Type
ViewModels:AccountWalletVM}" />
The ModelObjectDataProvider is just a class which derives from the standard
ObjectDataProvider, but does some deferring of the bindings until the data's
actually there. I've included the class definition at the bottom of the email
for reference (it's rather fat with comments, but simple in concept).
I can't entirely remember all the discussion and approaches we tried when
settling on this standard, but now most of our XAML binding looks like this
below. I remember one of the issues we had early on with Binding was that we
ended up with grid/itemscollections where we wanted some data from 1 data
source & then something like the Button Command or choices for a ComboBox to
come from another object.. Our early VMs got very complex because of a belief
we could only really bind to one thing. Being explicit about the main VM to
which the View is bound, allows you to leap out at any time to look at
something exposed from that VM, regardless of how many objects down a
collection/hierarchy of properties you are at that time... It adds some extra
boilerplate which goes into a lot of your bindings (though you can cheat and
stick the DataContext on a GroupBox to avoid the need to add the source for all
your child controls then), but saves much complex walking of Ancestors to find
the data source you want.
<DataGrid x:Name="dgWallets"
ItemsSource="{Binding Source={StaticResource ViewModel},
Path=Wallets, Mode=OneWay}">
<DataGrid.Columns>
<DataGridTextColumn Header="{Binding Source={StaticResource
ViewModel}, Path=Strings.V_ListHeadingWalletName, Mode=OneTime}"
Binding="{Binding Path=Name}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Style="{DynamicResource
DeleteButtonStyle}"
Command="{Binding
Source={StaticResource ViewModel}, Path=DeleteWalletCommand}"
CommandParameter="{Binding}"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
No idea if any of this is of interest to anyone, but figured I'd throw it out
there incase it helps someone get started.
Martin
/// <summary>
/// A custom ObjectDataProvider which overrides the ObjectType property such
that it can be
/// used at designtime to link an appropriate dummy data object, while
ignoring that XAML binding
/// value at runtime so that ObjectInstance can be used to set the data to
which the data provider
/// refers.
/// </summary>
/// <remarks>
/// This is necessary because the two ways of setting the content of an
ObjectDataProvider, ObjectType and
/// ObjectInstance are mutually exclusive once they've been called. The
ObjectType property is much more
/// convenient for gaining a design-time binding for design purposes, but
the ObjectInstance is more
/// suitable architecturally when the ViewModel is created or populated
elsewhere (or shared between views)
/// </remarks>
public class ModelObjectDataProvider : ObjectDataProvider
{
bool isInDesignMode =
(bool)DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue;
private IDisposable m_Defer;
public ModelObjectDataProvider()
{
// The class is not interested in the XAML declaration!<br/>
// IsInitialLoadEnabled is setted to true by the base (DataSourceProvider)
constructor.
// if you listen to the PropertyChanged event, you will see,
// that IsInitialLoadEnabled is setted to the XAML value after
BeginQuery has been called
IsInitialLoadEnabled = false;
}
protected override void BeginInit()
{
// increment DataSourceProvider._deferLevel
if (!IsInitialLoadEnabled)
{
m_Defer = this.DeferRefresh();
}
base.BeginInit();
}
protected override void EndInit()
{
// decrement DataSourceProvider._deferLevel
if (m_Defer != null)
{
m_Defer.Dispose();
m_Defer = null;
}
else
{
base.EndInit();
}
}
/// <summary>
/// Gets or sets the type of object to create an instance of.
/// </summary>
/// <value></value>
/// <returns>
/// This property is null when the <see
cref="T:System.Windows.Data.ObjectDataProvider"/> is uninitialized or
explicitly set to null. If <see
cref="P:System.Windows.Data.ObjectDataProvider.ObjectInstance"/> is assigned,
<see cref="P:System.Windows.Data.ObjectDataProvider.ObjectType"/> returns the
type of the object or null if the object is null. The default value is null.
/// </returns>
/// <exception cref="T:System.InvalidOperationException">
/// <see cref="T:System.Windows.Data.ObjectDataProvider"/> is
assigned both an <see
cref="P:System.Windows.Data.ObjectDataProvider.ObjectType"/> and an <see
cref="P:System.Windows.Data.ObjectDataProvider.ObjectInstance"/>; only one is
allowed.
/// </exception>
public new Type ObjectType
{
get { return base.ObjectType; }
set
{
if (!isInDesignMode)
{
value = null;
}
base.ObjectType = value;
if (isInDesignMode)
{
this.Refresh();
}
}
}
From: [email protected] [mailto:[email protected]] On
Behalf Of Greg Keogh
Sent: 25 November 2011 22:26
To: 'ozWPF'
Subject: RE: Getting up to speed in wpf
Yep, Thanks Martin for your time on that. You have confirmed a lot of my
experiences, and perhaps that I'm not mad. So far I have written all my WPF
alone without the help of a team to build comprehensive helper libraries of
base classes and utilities. I certainly do find myself copying and pasting
snippets out of other apps a lot. I also try to separate all the styling XAML
from the controls. I have been very unhappy with MVVM as it seems to stretch
the capabilities of binding beyond the breaking point as an academic exercise
and leads to bizarre quirks and workarounds. I have settled on a simple
View-Controller pattern which keeps the code behind quite thin and allows unit
testing of the controllers.
I am back to coding WPF today and I will be editing more XAML by hand.
Greg
Clarity Retail Systems Limited, Paterson House, Hatch Warren Farm, Hatch Warren
Lane, Hatch Warren, Basingstoke,
Hants, RG22 4RA, UK - Company Registration No: 02739937 - Registered in England
The contents of this email are intended for the named addressee(s) only. It
contains information which may be confidential and which may also be
privileged. If you are not the intended recipient(s) please note that any form
of disclosure, distribution, copying or use of this communication or the
information in it or in any attachments is strictly prohibited and may be
unlawful. If you have received it in error please notify the sender immediately
by return email or by calling +44(0)1256 365150 and ask for the sender. Where
the content of this email is personal or otherwise unconnected with Clarity
Retail Systems Limited or our clients' business, Clarity Retail Systems Limited
accepts no responsibility or liability for such content. It is your
responsibility to verify that this email and any attachments are free of
viruses, as we can take no responsibility for any computer viruses.
_______________________________________________
ozwpf mailing list
[email protected]
http://prdlxvm0001.codify.net/mailman/listinfo/ozwpf