WPF Layer Cake C#, C++/CLI and C Andy Dent www.andydent.com May 2009
Background Clunky old C, must be preserved with strong OO design and lots of code coupled to data. New GUI required. Rich Internet App is appealing for future. Long-term change – core IP of company.
Clunky old C, must be preserved with strong OO design and lots of code coupled to data. New GUI required. Rich Internet App is appealing for future. Long-term change – core IP of company. ViewModel = all state of the GUI preserved in an external object so GUI is very lightweight, makes testing possible as can invoke all state changes through ViewModel interface.
Wandering in WPF Continuum of structuring a WPF program: Ain’t it cool – do as much in XAML as possible, binding data and all commands/events. Events bound in XAML, commands in code. Bind data only in XAML. XAML for layout, code for all bindings. Silly opposite extreme – no XAML I have a natural tendency to using a declarative style and there is a certain geeky appeal to doing as much as possible in XAML.
<Window.CommandBindings> Command="{x:Static w:TestDespatchWindow.cmdShow2}" Executed="OnShow2" /> </Window.CommandBindings> <Window.InputBindings> <KeyBinding Key="T" Modifiers="Control" Command="{x:Static w:TestDespatchWindow.cmdShow2}" /> </Window.InputBindings> <DockPanel> <Menu DockPanel.Dock="Top"> <MenuItem Header="_File" > <MenuItem Header="E_xit" Command="Close" /> </MenuItem> <MenuItem Header="Test"> <MenuItem </Menu> <StackPanel Margin="0,8,0,0"> <Button x:Name="Show2EventBased" Margin="10,2,10,2" Click="OnShow2" Content="Show2 via WPF Event"/> <Button x:Name="Show2Command" Margin="10,2,10,2" Content="Show2 via WPF"/> <Button x:Name="Show2IndirectCommand" Margin="10,2,10,2" Content="Show2 via DLL" />
public partial class WindowWithCommandVM : Window { public WindowWithCommandVM() InitializeComponent(); mBackend = new BackedCommandViewModel( "Fred Nerk", "10 Bloggs Ave\nToon Town", true); ... DataContext = mBackend; NameEntry.SetBinding( TextBox.TextProperty, new Binding("Name") {Mode = BindingMode.TwoWay} ); CNTL_Lower.Command = mBackend.cmdLower; CommandBindings.Add( new CommandBinding(mBackend.cmdLower, mBackend.CommandSingleEntryPoint) );
public ref class BackedCommandViewModel : public INotifyPropertyChanged { public: BackedCommandViewModel( String^ _name, String^ _address); ... void CommandSingleEntryPoint( Object^ sender, ExecutedRoutedEventArgs^ e); property String^ Name { String^ get(); void set(String^ s); }; property String^ Address { String^ get();... property RoutedCommand^ cmdLower { RoutedCommand^ get(); };... virtual event PropertyChangedEventHandler^ PropertyChanged; private: void OnPropertyChanged(String^ name) PropertyChanged(this, gcnew PropertyChangedEventArgs(name)); }
Lessons Learned Don’t fight C# - do hookups of properties and commands in code-behind files. C++/CLI can define straightforward Property objects, events, delegates and commands. Debugging all the way works if you tell your app it should debug unmanaged code. Beware of #includes in C++/CLI – use referenced assemblies C2011 'class' type redefinition The common error C2011 occurs normally because people forget to have ifndef/define guards or pragma once.Error 1 error C2011: 'CPPDemoViewModel::SimpleViewModel' : 'class' type redefinition However, the simplest initial version of CPPViewModelTest testing CPPDemoViewModel came up with the C2011 error. Because this is a common beginners error, it is virtually impossible to find an answer on Google. I did find one instance of someone triggering it in the same circumstances (in Sep 2008) but nobody had provided a useful answer. The key turned out to be the traditional include "CPPDemoViewModel.h" which brings the class definitions of namespace CPPViewModelTest into UnitTest_CPPViewModel.cpp as you would for any C++ compilation using a library. CPPViewModelTest is defined in the assembly CPPDemoViewModel and that assembly is explicitly recorded as a Reference of the project CPPViewModelTest. The reference imports the namespace into the list of available namespaces but does not trigger the pragma once protection (which I regard as a C++/CLI compilation bug). Hence the C2011 occurring as if the class definition wasn't guarded. Note that the project CPPViewModelTest also has a dependency on the project CPPDemoViewModel which is there to guarantee builds are triggered. Tests Fail with System.IO.FileNotFoundException This is the for specific circumstance when a pure C DLL is included in a C# assembly - it doesn't get propagated from the C++/CLI assembly which uses it, despite settings to copy dependents.It will build OK but get a runtime System.IO.FileNotFoundException as described here because of a failure to find BackingStore.dll. This was fixed by adding a test configuration (right-click Solution - Add) ConfigRequiringBackingStore which specifies that BackingStored.dll must be deployed.