Compiled Experience Windows Platform Development http://compiledexperience.com en Tue, 14 Nov 2017 01:09:05 +0000 Tue, 14 Nov 2017 01:09:05 +0000 Project references to multi-targeted projects <p>During the process of moving Caliburn.Micro to .NET Standard and the new <a href="https://caliburnmicro.com/announcements/net-standard">multi-targeting project format</a> I’ve encountered a number of issues in the tooling around intellisense and builds. This isn’t surprising given the relative newness of this approach, but I thought I’d share some of the issues over the next few weeks to help you out.</p> <p>One of the first things I did was move <code class="highlighter-rouge">Caliburn.Micro.Platform</code> from a number of projects (around five I believe) in the same folder (one for each platform) to the new “SDK style” project format which allows multiple outputs based on a series of target frameworks (rather than the normal singular framework).</p> <p>This worked out fine, but the unit tests in the solution started failing with compilation errors where certain classes were missing. In this case it was classes that aren’t present in the Xamarin.Forms platform. This unit test project was a .NET 4.5 project and was clearly picking the wrong output of the multi-targeted project. Instead of picking the “best” platform of .NET 4.5, it was picking the widest in .NET Standard 1.4.</p> <p>This is a known issue and you can see it being discussed on the GitHub repository for the new project system under <a href="https://github.com/dotnet/project-system/issues/1162">“P2P refs choose first tfm in multi-targting reference, not closest one in legacy project system”</a>.</p> <p>If you’ve read the above issue you’ll notice the way to solve this is to shift the other project to the new poject system. Once this is done the new project will pick the correct project output.</p> <p>As I run into more problems during this port (hopefully not too many) I’ll post them up here.</p> Tue, 14 Nov 2017 00:00:00 +0000 http://compiledexperience.com/blog/posts/project-references-multi-targeting http://compiledexperience.com/blog/posts/project-references-multi-targeting nigel.sampson@compiledexperience.com (Nigel Sampson) Techniques in creating great cross platform apps <p>My NDC Sydney 2017 talk “<a href="https://www.youtube.com/watch?v=S9kxokKuFAQ">Techniques in creating great cross platform apps</a>” is now available on Youtube.</p> <p>This talk covers a number of topics:</p> <ul> <li>A quick recap of Xamarin and the MVVM pattern.</li> <li>New approaches to share code with Visual Studio 2017.</li> <li>Composition of view models using view locators.</li> <li>Multiple views per view model to create master / details.</li> <li>View model lifecycle and conductors.</li> <li>Messanging and event aggregation.</li> </ul> <p>If you have any feedback and / or questions feel free to get in contact.</p> Fri, 29 Sep 2017 00:00:00 +0000 http://compiledexperience.com/blog/posts/ndc-sydney-2017-video http://compiledexperience.com/blog/posts/ndc-sydney-2017-video nigel.sampson@compiledexperience.com (Nigel Sampson) Auto subscription for Event Aggregator <p>In many of my talks I’ve recommended using a messenger style class. These help to reduce coupling between view models when decomposing from a single “view model per screen” to a tree of view models. This intermediary class is called by a few different names, <code class="highlighter-rouge">Messenger</code> in Xamarin.Forms, <code class="highlighter-rouge">Mediator</code> in some others and in Caliburn.Micro, <code class="highlighter-rouge">Event Aggregator</code>.</p> <p>One feature of the <code class="highlighter-rouge">Event Aggregator</code> in Caliburn.Micro is that you need to explicitly subscribe to it through <code class="highlighter-rouge">IEventAggregator.Subscribe</code> before you’ll receive events from it. This is by design to be able to integrate the aggreator with the life cycle of your view models. Typically most view models will only want to receive events while they’ve active, some however want to receive them all the time.</p> <p>Some developers would prefer an “auto subscription” style behavior where view models are automatically subscribed to the aggregator when they’re used. One benefit of this approach is that the view model doesn’t need to take a dependency on aggregator itself reducing some complexity.</p> <p>The best cut point to introduce this behavior in Caliburn.Micro is the <code class="highlighter-rouge">ViewModelBinder.Bind</code>, this is the part of the framework that once a view and view model are located they’re “bound” together. This <code class="highlighter-rouge">ViewModelBinder</code> is used by the <code class="highlighter-rouge">INavigationService</code> and the <code class="highlighter-rouge">View.Model</code> attached property and covers a few things:</p> <ol> <li>Sets the <code class="highlighter-rouge">DataContext / BindingContext</code> of the view to the view model.</li> <li>Applies the property conventions (not for Xamarin.Forms).</li> <li>Applies the method conventions (not for Xamarin.Forms).</li> </ol> <p>A lot of the extension points in Caliburn.Micro like this one are defined as static properties, in this case of type <code class="highlighter-rouge">Action&lt;object, DependencyObject, object&gt;</code>. The way we modify it is by setting it to a new action that includes the new behavior, we can preserve the existing functionality by taking a reference to the current action and calling it within the new action. This looks like the following:</p> <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">existingBind</span> <span class="p">=</span> <span class="n">ViewModelBinder</span><span class="p">.</span><span class="n">Bind</span><span class="p">;</span> <span class="n">ViewModelBinder</span><span class="p">.</span><span class="n">Bind</span> <span class="p">=</span> <span class="p">(</span><span class="n">viewModel</span><span class="p">,</span> <span class="n">view</span><span class="p">,</span> <span class="n">context</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="nf">existingBind</span><span class="p">(</span><span class="n">viewModel</span><span class="p">,</span> <span class="n">view</span><span class="p">,</span> <span class="n">context</span><span class="p">);</span> <span class="kt">var</span> <span class="n">handleInterfaces</span> <span class="p">=</span> <span class="k">new</span><span class="p">[]</span> <span class="p">{</span> <span class="k">typeof</span><span class="p">(</span><span class="n">IHandle</span><span class="p">&lt;&gt;),</span> <span class="k">typeof</span><span class="p">(</span><span class="n">IHandleWithCoroutine</span><span class="p">&lt;&gt;),</span> <span class="k">typeof</span><span class="p">(</span><span class="n">IHandleWithTask</span><span class="p">&lt;&gt;)</span> <span class="p">};</span> <span class="kt">var</span> <span class="n">subscribe</span> <span class="p">=</span> <span class="n">viewModel</span> <span class="p">.</span><span class="nf">GetType</span><span class="p">()</span> <span class="p">.</span><span class="nf">GetInterfaces</span><span class="p">()</span> <span class="p">.</span><span class="nf">Any</span><span class="p">(</span><span class="n">i</span> <span class="p">=&gt;</span> <span class="n">i</span><span class="p">.</span><span class="n">IsGenericType</span> <span class="p">&amp;&amp;</span> <span class="n">handleInterfaces</span><span class="p">.</span><span class="nf">Contains</span><span class="p">(</span><span class="n">i</span><span class="p">.</span><span class="nf">GetGenericTypeDefinition</span><span class="p">()));</span> <span class="k">if</span> <span class="p">(!</span><span class="n">subscribe</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span> <span class="kt">var</span> <span class="n">eventAggregator</span> <span class="p">=</span> <span class="n">container</span><span class="p">.</span><span class="n">GetInstance</span><span class="p">&lt;</span><span class="n">IEventAggregator</span><span class="p">&gt;();</span> <span class="n">eventAggregator</span><span class="p">.</span><span class="nf">Subscribe</span><span class="p">(</span><span class="n">viewModel</span><span class="p">);</span> <span class="kt">var</span> <span class="n">deactivate</span> <span class="p">=</span> <span class="n">viewModel</span> <span class="k">as</span> <span class="n">IDeactivate</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">deactivate</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span> <span class="n">deactivate</span><span class="p">.</span><span class="n">Deactivated</span> <span class="p">+=</span> <span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">WasClosed</span><span class="p">)</span> <span class="n">eventAggregator</span><span class="p">.</span><span class="nf">Unsubscribe</span><span class="p">(</span><span class="k">this</span><span class="p">);</span> <span class="p">};</span> <span class="p">}</span> <span class="p">};</span> </code></pre></div></div> <p>In the above code we’re checking whether the view model implements one of <code class="highlighter-rouge">IHandle*</code> interfaces that’s required by the event aggregator, if it doesn’t we can exit out otherwise we’ll subscribe the view model to the event aggregator. We’ll then check if the view model supports deactivation, if it does we’ll attach to the <code class="highlighter-rouge">Deactivated</code> event and if we’re closing the view model we’ll unsubscribe.</p> <p>Unsubscribing is important because although the event aggregator itself holds a weak reference to your view model, you have the possibility of discarded but not yet garbage collected view models receiving events and acting erroneously.</p> <p>This post should show how we can extend Caliburn.Micro to add automatic behavior to view models without too much extra complexity.</p> Mon, 11 Sep 2017 00:00:00 +0000 http://compiledexperience.com/blog/posts/event-aggregator-autosubscribe http://compiledexperience.com/blog/posts/event-aggregator-autosubscribe nigel.sampson@compiledexperience.com (Nigel Sampson) Command conventions in Caliburn.Micro <p>If you’ve ever spoken to me personally at a convention or a user group (I’ll be at <a href="http://ndcsydney.com/">NDC Sydney</a> if next week, comes say hello) then you may have heard me talk about disliking command objects in MVVM.</p> <p>In my opinion (and it’s just that, my opinion) most commands don’t add any value to the main goals for using MVVM (maintainability, readability and testability). Typically they’re just an object that wraps a method, sometimes also a predicate for CanExecute but that’s it. What they mostly add is ceremony to the view model and not much more, you typically see this in commands named <code class="highlighter-rouge">RelayCommand</code> or <code class="highlighter-rouge">DelegateCommand</code>.</p> <p>Some commands are really useful however, <code class="highlighter-rouge">ReactiveCommand</code> in <a href="https://reactiveui.net/">ReactiveUI</a> adds a lot of value when building that style of application and I highly recommend them. This is a great example where the command adds more then just the ceremony of passing execution to the method.</p> <p>How can we customize the conventions in Caliburn.Micro to make use of commands? Given that we’d be binding to the command property on the control we can simply modify the convention for controls such as Button.</p> <p>This change to the convention is set up during app initialisation. The important parameter is the first, this defines that when we find a property on the view model matching the <code class="highlighter-rouge">x:Name</code> of the button then this is the dependency property we’ll bind that property to.</p> <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ConventionManager</span><span class="p">.</span><span class="n">AddElementConvention</span><span class="p">&lt;</span><span class="n">Button</span><span class="p">&gt;(</span><span class="n">ButtonBase</span><span class="p">.</span><span class="n">CommandProperty</span><span class="p">,</span> <span class="s">"CommandParameter"</span><span class="p">,</span> <span class="s">"Click"</span><span class="p">);</span> </code></pre></div></div> <p>To keep our view model sample simple I’ll use a <code class="highlighter-rouge">DelegateCommand</code></p> <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="nf">LoginViewModel</span><span class="p">()</span> <span class="p">{</span> <span class="n">Login</span> <span class="p">=</span> <span class="k">new</span> <span class="n">DelegateCommand</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;(</span><span class="n">LoginImpl</span><span class="p">,</span> <span class="n">CanLogin</span><span class="p">);</span> <span class="p">}</span> <span class="k">public</span> <span class="n">ICommand</span> <span class="n">Login</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span> <span class="k">private</span> <span class="kt">bool</span> <span class="nf">CanLogin</span><span class="p">(</span><span class="kt">string</span> <span class="n">token</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">!</span><span class="n">String</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">token</span><span class="p">);</span> <span class="k">private</span> <span class="k">void</span> <span class="nf">LoginImpl</span><span class="p">(</span><span class="kt">string</span> <span class="n">token</span><span class="p">)</span> <span class="p">{</span> <span class="p">....</span> <span class="p">}</span> </code></pre></div></div> <p>Now with our convention in place we can simply give the <code class="highlighter-rouge">Button</code> the <code class="highlighter-rouge">x:Name</code> we normally would but instead of a method being called the command will be executed.</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;StackPanel&gt;</span> <span class="nt">&lt;TextBox</span> <span class="na">x:Name=</span><span class="s">"Token"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;Button</span> <span class="na">x:Name=</span><span class="s">"Login"</span> <span class="na">Content=</span><span class="s">"Login to System"</span> <span class="na">CommandParameter=</span><span class="s">"{Binding ElementName=Token, Path=Text}"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/StackPanel&gt;</span> </code></pre></div></div> <p>If you’re looking to combine the best of <a href="http://caliburnmicro.com/">Caliburn.Micro</a> and <a href="https://reactiveui.net/">ReactiveUI</a> then this may help.</p> Thu, 10 Aug 2017 00:00:00 +0000 http://compiledexperience.com/blog/posts/comand-conventions http://compiledexperience.com/blog/posts/comand-conventions nigel.sampson@compiledexperience.com (Nigel Sampson) Controlling the output path in Visual Studio 2017 <p>One thing you’ll notice if you’re experimenting with the new <code class="highlighter-rouge">csproj</code> project structure used in .NET Standard is the difference in <strong>Output Path</strong>. Typically the default output path for a new full .NET Framework assembly would be <code class="highlighter-rouge">bin\$(Configuration)\</code> resulting in <code class="highlighter-rouge">bin\Debug\</code> and <code class="highlighter-rouge">\bin\Release\</code>. In .NET Standard projects by default this output path isn’t defined in the <code class="highlighter-rouge">csproj</code> but defaults to much the same <strong>except</strong> that the Target Framework is also appended to the path. So for example if I created a new project targeting .NET Standard 1.4 like follows:</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Project</span> <span class="na">Sdk=</span><span class="s">"Microsoft.NET.Sdk"</span><span class="nt">&gt;</span> <span class="nt">&lt;PropertyGroup&gt;</span> <span class="nt">&lt;TargetFramework&gt;</span>netstandard1.4<span class="nt">&lt;/TargetFramework&gt;</span> <span class="nt">&lt;/PropertyGroup&gt;</span> <span class="nt">&lt;/Project&gt;</span> </code></pre></div></div> <p>then the default output path would be <code class="highlighter-rouge">bin\Debug\netstandard1.4</code>. Nothing too different but something to watch out for. This makes sense for when instead of having one Target Framework we convert the project to target multiple frameworks, by default then each framework would have it’s own output folder and we wouldn’t have any file clashes.</p> <p>What’s also very important to note is that this appending on of the Target Framework happens automatically even when the output path is defined by you. For instance the output path of the following would be <code class="highlighter-rouge">build\Debug\netstandard1.4</code>.</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Project</span> <span class="na">Sdk=</span><span class="s">"Microsoft.NET.Sdk"</span><span class="nt">&gt;</span> <span class="nt">&lt;PropertyGroup&gt;</span> <span class="nt">&lt;TargetFramework&gt;</span>netstandard1.4<span class="nt">&lt;/TargetFramework&gt;</span> <span class="nt">&lt;OutputPath&gt;</span>build\$(Configuration)<span class="nt">&lt;/OutputPath&gt;</span> <span class="nt">&lt;/PropertyGroup&gt;</span> <span class="nt">&lt;/Project&gt;</span> </code></pre></div></div> <p>If you want to disable this automatic appending, for instance you’re only going to be using one target framework or you’re defining a different output path per framework then you can use <code class="highlighter-rouge">AppendTargetFrameworkToOutputPath</code>.</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Project</span> <span class="na">Sdk=</span><span class="s">"Microsoft.NET.Sdk"</span><span class="nt">&gt;</span> <span class="nt">&lt;PropertyGroup&gt;</span> <span class="nt">&lt;TargetFramework&gt;</span>netstandard1.4<span class="nt">&lt;/TargetFramework&gt;</span> <span class="nt">&lt;AppendTargetFrameworkToOutputPath&gt;</span>false<span class="nt">&lt;/AppendTargetFrameworkToOutputPath&gt;</span> <span class="nt">&lt;/PropertyGroup&gt;</span> <span class="nt">&lt;/Project&gt;</span> </code></pre></div></div> Wed, 26 Jul 2017 00:00:00 +0000 http://compiledexperience.com/blog/posts/multi-targeting-output-path http://compiledexperience.com/blog/posts/multi-targeting-output-path nigel.sampson@compiledexperience.com (Nigel Sampson) Generation of Assembly Info in Visual Studio 2017 <p>One feature of the new <code class="highlighter-rouge">csproj</code> format (is there an official name for these?) that I wasn’t aware of is the automatic generation of the <code class="highlighter-rouge">assmebly:</code> attributes we would normally see in <code class="highlighter-rouge">AssemblyInfo.cs</code>. This can catch you by surprise with some odd errors, especially when migrating existing projects to the format.</p> <p>This feature allows you to define assembly properties such as title, description and version as project properties defined in the <code class="highlighter-rouge">.csproj</code> that looks like the following.</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;PropertyGroup&gt;</span> <span class="nt">&lt;Company&gt;</span>Compiled Expericne<span class="nt">&lt;/Company&gt;</span> <span class="nt">&lt;Authors&gt;</span>Nigel Sampson<span class="nt">&lt;/Authors&gt;</span> <span class="nt">&lt;PackageId&gt;</span>AssemblyDemo<span class="nt">&lt;/PackageId&gt;</span> <span class="nt">&lt;Version&gt;</span>1.0.0<span class="nt">&lt;/Version&gt;</span> <span class="nt">&lt;AssemblyVersion&gt;</span>1.0.1.0<span class="nt">&lt;/AssemblyVersion&gt;</span> <span class="nt">&lt;FileVersion&gt;</span>1.0.1.0<span class="nt">&lt;/FileVersion&gt;</span> <span class="nt">&lt;/PropertyGroup&gt;</span> </code></pre></div></div> <p>If you don’t want to edit the project file manually these can be set through the project properties and the Package tab. One of the reasons for this is that it combines both the assembly and package information (now the build system can create your nuget packages for you) letting you define shared properties in one place.</p> <p>At compile the following <code class="highlighter-rouge">AssemblyInfo.cs</code> is generated in the <code class="highlighter-rouge">obj</code> folder.</p> <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System</span><span class="p">;</span> <span class="k">using</span> <span class="nn">System.Reflection</span><span class="p">;</span> <span class="na">[assembly: System.Reflection.AssemblyCompanyAttribute("Compiled Expericne")]</span> <span class="na">[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]</span> <span class="na">[assembly: System.Reflection.AssemblyDescriptionAttribute("Package Description")]</span> <span class="na">[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.1.0")]</span> <span class="na">[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]</span> <span class="na">[assembly: System.Reflection.AssemblyProductAttribute("AssemblyTest")]</span> <span class="na">[assembly: System.Reflection.AssemblyTitleAttribute("AssemblyTest")]</span> <span class="na">[assembly: System.Reflection.AssemblyVersionAttribute("1.0.1.0")]</span> </code></pre></div></div> <p>If you’re migrating an exsiting project you’ll most likely have an existing <code class="highlighter-rouge">AssemblyInfo.cs</code> or using one due to properties being shared across projects with a <code class="highlighter-rouge">GlobalAssemblyInfo.cs</code> (what I was doing) meaning you’ll see erorrs such as <code class="highlighter-rouge">CS0579 Duplicate 'System.Reflection.AssemblyCompanyAttribute' attribute</code>.</p> <p>You can either shift to setting the new project properties and remove your existing file or turn off the new feature (which I chose to do) with the following project property.</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;PropertyGroup&gt;</span> <span class="nt">&lt;GenerateAssemblyInfo&gt;</span>false<span class="nt">&lt;/GenerateAssemblyInfo&gt;</span> <span class="nt">&lt;/PropertyGroup&gt;</span> </code></pre></div></div> <p>Now the <code class="highlighter-rouge">AssemblyInfo.cs</code> won’t be automatically generated and conflict with your already defined attributes.</p> Wed, 19 Jul 2017 00:00:00 +0000 http://compiledexperience.com/blog/posts/assemblyinfo-generation http://compiledexperience.com/blog/posts/assemblyinfo-generation nigel.sampson@compiledexperience.com (Nigel Sampson) Adding functionality to Caliburn.Micro conventions <p>The binding conventions in Caliburn.Micro can be extended to do almost anything you want. The method <code class="highlighter-rouge">ConventionManager.AddElementConvention&lt;T&gt;</code> returns a <code class="highlighter-rouge">ElementConvention</code> which can be further customised. In this post I’ll show what things we can do with this by modifying the <code class="highlighter-rouge">ElementConvention.ApplyBinding</code> action.</p> <p>Internally Caliburn.Micro already modifies some of the conventions with extra functionality. It’s what turns</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;TabControl</span> <span class="na">x:Name=</span><span class="s">"Items"</span> <span class="nt">/&gt;</span> </code></pre></div></div> <p>into</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;TabControl</span> <span class="na">ItemsSource=</span><span class="s">"{Binding Items}"</span> <span class="na">SelectedItem=</span><span class="s">"{Binding ActiveItem, Mode=TwoWay}"</span><span class="nt">&gt;</span> <span class="nt">&lt;TabControl.ContentTemplate&gt;</span> <span class="nt">&lt;DataTemplate&gt;</span> <span class="nt">&lt;ContentControl</span> <span class="na">cal:View.Model=</span><span class="s">"{Binding}"</span> <span class="na">VerticalContentAlignment=</span><span class="s">"Stretch"</span> <span class="na">HorizontalContentAlignment=</span><span class="s">"Stretch"</span> <span class="na">IsTabStop=</span><span class="s">"False"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/DataTemplate&gt;</span> <span class="nt">&lt;/TabControl.ContentTemplate&gt;</span> <span class="nt">&lt;TabControl.HeaderTemplate&gt;</span> <span class="nt">&lt;DataTemplate&gt;</span> <span class="nt">&lt;TextBlock</span> <span class="na">Text=</span><span class="s">"{Binding DisplayName}"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/DataTemplate&gt;</span> <span class="nt">&lt;/TabControl.HeaderTemplate&gt;</span> <span class="nt">&lt;/TabControl&gt;</span> </code></pre></div></div> <p>We can add our own extensions as well. In this example I’ll demonstrate how we can modify the convention for <code class="highlighter-rouge">TextBox</code> to take into account the <code class="highlighter-rouge">System.ComponentModel.DataAnnotations</code> attributes.</p> <p>Let’s imagine we have a view model with <code class="highlighter-rouge">FirstName</code> property that looks like the one below, it would be great if we can by convention apply these attributes to the control the property is bound to. This should let us define in one place all the metadata about this property.</p> <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">[StringLength(50)]</span> <span class="na">[Display(Name = "First Name", Prompt = "John")]</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">FirstName</span> <span class="p">{</span> <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="n">firstName</span><span class="p">;</span> <span class="p">}</span> <span class="k">set</span> <span class="p">{</span> <span class="n">firstName</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span> <span class="nf">NotifyOfPropertyChange</span><span class="p">();</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>This turns out to be really easy, we simply have something like the following in our app startup code.</p> <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ConventionManager</span><span class="p">.</span><span class="n">AddElementConvention</span><span class="p">&lt;</span><span class="n">TextBox</span><span class="p">&gt;(</span><span class="n">TextBox</span><span class="p">.</span><span class="n">TextProperty</span><span class="p">,</span> <span class="s">"Text"</span><span class="p">,</span> <span class="s">"TextChanged"</span><span class="p">)</span> <span class="p">.</span><span class="n">ApplyBinding</span> <span class="p">=</span> <span class="p">(</span><span class="n">viewModelType</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="n">property</span><span class="p">,</span> <span class="n">element</span><span class="p">,</span> <span class="n">convention</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(!</span><span class="n">ConventionManager</span><span class="p">.</span><span class="nf">SetBindingWithoutBindingOverwrite</span><span class="p">(</span><span class="n">viewModelType</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="n">property</span><span class="p">,</span> <span class="n">element</span><span class="p">,</span> <span class="n">convention</span><span class="p">,</span> <span class="n">TextBox</span><span class="p">.</span><span class="n">TextProperty</span><span class="p">))</span> <span class="p">{</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span> <span class="p">}</span> <span class="kt">var</span> <span class="n">textBox</span> <span class="p">=</span> <span class="p">(</span><span class="n">TextBox</span><span class="p">)</span> <span class="n">element</span><span class="p">;</span> <span class="kt">var</span> <span class="n">display</span> <span class="p">=</span> <span class="n">property</span><span class="p">.</span><span class="n">GetAttributes</span><span class="p">&lt;</span><span class="n">DisplayAttribute</span><span class="p">&gt;(</span><span class="k">true</span><span class="p">).</span><span class="nf">FirstOrDefault</span><span class="p">();</span> <span class="kt">var</span> <span class="n">length</span> <span class="p">=</span> <span class="n">property</span><span class="p">.</span><span class="n">GetAttributes</span><span class="p">&lt;</span><span class="n">StringLengthAttribute</span><span class="p">&gt;(</span><span class="k">true</span><span class="p">).</span><span class="nf">FirstOrDefault</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span><span class="n">display</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span> <span class="n">textBox</span><span class="p">.</span><span class="n">Header</span> <span class="p">=</span> <span class="n">display</span><span class="p">.</span><span class="nf">GetName</span><span class="p">();</span> <span class="n">textBox</span><span class="p">.</span><span class="n">PlaceholderText</span> <span class="p">=</span> <span class="n">display</span><span class="p">.</span><span class="nf">GetPrompt</span><span class="p">();</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">length</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span> <span class="n">textBox</span><span class="p">.</span><span class="n">MaxLength</span> <span class="p">=</span> <span class="n">length</span><span class="p">.</span><span class="n">MaximumLength</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span> <span class="k">true</span><span class="p">;</span> <span class="p">};</span> </code></pre></div></div> <p>IF we try to apply the binding between the view model and the <code class="highlighter-rouge">Text</code> property on the control. If this fails (there’s already a binding) then we exit out. We then check the property for the attributes in question and if they exist apply their values to the <code class="highlighter-rouge">TextBox</code>.</p> <p>We don’t need to make any modifications to the xaml so it can be very simple and still have most of the values it needs applied by the convention itself.</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;TextBox</span> <span class="na">x:Name=</span><span class="s">"FirstName"</span> <span class="nt">/&gt;</span> </code></pre></div></div> <p>Feel free to experiement in customising your conventions. I hope this gives you all some ideas on what you can do. Let me know what you find.</p> Thu, 08 Jun 2017 00:00:00 +0000 http://compiledexperience.com/blog/posts/extending-conventions http://compiledexperience.com/blog/posts/extending-conventions nigel.sampson@compiledexperience.com (Nigel Sampson) Speaking at NDC Sydney <p>Really proud to say that I’ll be speaking in August at <a href="http://ndcsydney.com/">NDC Sydney</a> on <a href="http://ndcsydney.com/speaker/nigel-sampson/">Techniques in creating great cross platform apps.</a>. This talk will be a spiritual successor to <a href="https://www.youtube.com/watch?v=x266Vy4tfT0">last years talk</a> where I’ll cover some of the newer code sharing options in Visual Studio 2017 and expanding on the ideas of the view model composition that Caliburn.Micro supports.</p> <p>There’s a fantastic <a href="http://ndcsydney.com/speakers/">speakers list</a> and some really exciting talks I’m looking forward to seeing.</p> <p>Getting this many great speakers in the same event in this part of the world is a rare feat so I’d recommend taking advantage of it and getting <a href="http://ndcsydney.com/page/tickets">your ticket</a> soon. If you do attend I’d love it you reach out and we can chat about how you build apps in a modern environment.</p> Tue, 16 May 2017 00:00:00 +0000 http://compiledexperience.com/blog/posts/ndc-2017 http://compiledexperience.com/blog/posts/ndc-2017 nigel.sampson@compiledexperience.com (Nigel Sampson) Supporting Xamarin.Forms Master Detail Page in Caliburn.Micro <p>Previously I’ve talked about <a href="/blog/posts/tabbed-page-conductor">Supporting Xamarin.Forms Tabbed Page in Caliburn.Micro</a> using conductors and Caliburns excellent view location features. I’d like to now show how we can do the same with the <code class="highlighter-rouge">MasterDetailPage</code>.</p> <p>The Master / Details pattern is very common in mobile apps providing where a master list of items is presented to the user and when one is select the view is switched to more details.</p> <p>When using <code class="highlighter-rouge">MasterDetailPage</code> with <code class="highlighter-rouge">Caliburn.Micro</code> we want to hit a number of goals.</p> <ol> <li>Our main view shouldn’t deal with displaying either the master or details and just handle creating the shell of the page.</li> <li>Our main view model should should follow a similar goal only coordinating between master and details.</li> <li>Handle creating two different views for one view model.</li> </ol> <p>In this sample our master / detail item will be characters from the upcoming TV show American Gods. Each character will have a single view model.</p> <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">CharacterViewModel</span> <span class="p">:</span> <span class="n">PropertyChangedBase</span> <span class="p">{</span> <span class="k">public</span> <span class="nf">CharacterViewModel</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="p">,</span> <span class="kt">string</span> <span class="n">tagline</span><span class="p">,</span> <span class="kt">string</span> <span class="n">image</span><span class="p">)</span> <span class="p">{</span> <span class="n">Name</span> <span class="p">=</span> <span class="n">name</span><span class="p">;</span> <span class="n">Tagline</span> <span class="p">=</span> <span class="n">tagline</span><span class="p">;</span> <span class="n">Image</span> <span class="p">=</span> <span class="n">image</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Tagline</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Image</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>We’ll then create our <code class="highlighter-rouge">ShellViewModel</code>, it will need to contain a collection of our characters as well as the currently selected character. We’ll also need a property that indicicate whether the master menu is currently available. A first draft looks something like:</p> <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">ShellViewModel</span> <span class="p">:</span> <span class="n">Screen</span> <span class="p">{</span> <span class="k">private</span> <span class="n">CharacterViewModel</span> <span class="n">selectedCharacter</span><span class="p">;</span> <span class="k">private</span> <span class="kt">bool</span> <span class="n">masterListAvailable</span><span class="p">;</span> <span class="k">public</span> <span class="nf">ShellViewModel</span><span class="p">()</span> <span class="p">{</span> <span class="n">MasterListAvailable</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span> <span class="n">Characters</span> <span class="p">=</span> <span class="k">new</span> <span class="n">BindableCollection</span><span class="p">&lt;</span><span class="n">CharacterViewModel</span><span class="p">&gt;</span> <span class="p">{</span> <span class="k">new</span> <span class="nf">CharacterViewModel</span><span class="p">(</span><span class="s">"Shadow Moon"</span><span class="p">,</span> <span class="s">"The Ex-Con"</span><span class="p">,</span> <span class="s">"character1.jpg"</span><span class="p">),</span> <span class="p">...</span> <span class="k">new</span> <span class="nf">CharacterViewModel</span><span class="p">(</span><span class="s">"Easter"</span><span class="p">,</span> <span class="s">"The Godess of Spring"</span><span class="p">,</span> <span class="s">"character10.jpg"</span><span class="p">),</span> <span class="p">};</span> <span class="p">}</span> <span class="k">public</span> <span class="n">BindableCollection</span><span class="p">&lt;</span><span class="n">CharacterViewModel</span><span class="p">&gt;</span> <span class="n">Characters</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="n">CharacterViewModel</span> <span class="n">SelectedCharacter</span> <span class="p">{</span> <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="n">selectedCharacter</span><span class="p">;</span> <span class="p">}</span> <span class="k">set</span> <span class="p">{</span> <span class="n">selectedCharacter</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span> <span class="nf">NotifyOfPropertyChange</span><span class="p">();</span> <span class="n">MasterListAvailable</span> <span class="p">=</span> <span class="n">SelectedCharacter</span> <span class="p">==</span> <span class="k">null</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">bool</span> <span class="n">MasterListAvailable</span> <span class="p">{</span> <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="n">masterListAvailable</span><span class="p">;</span> <span class="p">}</span> <span class="k">set</span> <span class="p">{</span> <span class="n">masterListAvailable</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span> <span class="nf">NotifyOfPropertyChange</span><span class="p">();</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>The most interesting things in here are populating the data in the constuctor, really this is only things specific to characters in the entire view model, but could easily be abstracted away. The other is that when the <code class="highlighter-rouge">SelectedCharacter</code> is set we update <code class="highlighter-rouge">MasterListAvailable</code> depending on the value. What this will do ultimately in the UI is that when a character is selected in the master list the UI will switch over to the details view.</p> <p>We already have a conductor in the framework that can handle this <code class="highlighter-rouge">Conductor&lt;T&gt;.Collection.OneActive</code> leaving us with.</p> <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">public</span> <span class="k">class</span> <span class="nc">ShellViewModel</span> <span class="p">:</span> <span class="n">Conductor</span><span class="p">&lt;</span><span class="n">CharacterViewModel</span><span class="p">&gt;.</span><span class="n">Collection</span><span class="p">.</span><span class="n">OneActive</span> <span class="p">{</span> <span class="k">private</span> <span class="kt">bool</span> <span class="n">masterListAvailable</span><span class="p">;</span> <span class="k">public</span> <span class="nf">ShellViewModel</span><span class="p">()</span> <span class="p">{</span> <span class="n">MasterListAvailable</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span> <span class="n">Items</span><span class="p">.</span><span class="nf">AddRange</span><span class="p">(</span><span class="k">new</span> <span class="p">[]</span> <span class="p">{</span> <span class="k">new</span> <span class="nf">CharacterViewModel</span><span class="p">(</span><span class="s">"Shadow Moon"</span><span class="p">,</span> <span class="s">"The Ex-Con"</span><span class="p">,</span> <span class="s">"character1.jpg"</span><span class="p">),</span> <span class="p">...</span> <span class="k">new</span> <span class="nf">CharacterViewModel</span><span class="p">(</span><span class="s">"Easter"</span><span class="p">,</span> <span class="s">"The Godess of Spring"</span><span class="p">,</span> <span class="s">"character10.jpg"</span><span class="p">),</span> <span class="p">});</span> <span class="p">}</span> <span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnActivationProcessed</span><span class="p">(</span><span class="n">CharacterViewModel</span> <span class="n">item</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">success</span><span class="p">)</span> <span class="p">{</span> <span class="n">MasterListAvailable</span> <span class="p">=</span> <span class="n">item</span> <span class="p">==</span> <span class="k">null</span><span class="p">;</span> <span class="p">}</span> <span class="k">public</span> <span class="kt">bool</span> <span class="n">MasterListAvailable</span> <span class="p">{</span> <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="n">masterListAvailable</span><span class="p">;</span> <span class="p">}</span> <span class="k">set</span> <span class="p">{</span> <span class="n">masterListAvailable</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span> <span class="nf">NotifyOfPropertyChange</span><span class="p">();</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Our <code class="highlighter-rouge">ShellView</code> comes next, an important point to note here is that no where in the view do we declare how a character should be display we simple style the master list and the container for the detail content. This view could be reused across any master / details view in the application.</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="utf-8" ?&gt;</span> <span class="nt">&lt;MasterDetailPage</span> <span class="na">xmlns=</span><span class="s">"http://xamarin.com/schemas/2014/forms"</span> <span class="na">xmlns:x=</span><span class="s">"http://schemas.microsoft.com/winfx/2009/xaml"</span> <span class="na">xmlns:views=</span><span class="s">"clr-namespace:MasterDetail.Views;assembly=MasterDetail"</span> <span class="na">xmlns:cm=</span><span class="s">"clr-namespace:Caliburn.Micro.Xamarin.Forms;assembly=Caliburn.Micro.Platform.Xamarin.Forms"</span> <span class="na">x:Class=</span><span class="s">"MasterDetail.Views.ShellView"</span> <span class="na">IsPresented=</span><span class="s">"{Binding MasterListAvailable}"</span><span class="nt">&gt;</span> <span class="nt">&lt;MasterDetailPage.Master&gt;</span> <span class="nt">&lt;ContentPage</span> <span class="na">Title=</span><span class="s">"Master"</span><span class="nt">&gt;</span> <span class="nt">&lt;ListView</span> <span class="na">ItemsSource=</span><span class="s">"{Binding Items}"</span> <span class="na">SelectedItem=</span><span class="s">"{Binding ActiveItem, Mode=TwoWay}"</span><span class="nt">&gt;</span> <span class="nt">&lt;ListView.ItemTemplate&gt;</span> <span class="nt">&lt;DataTemplate&gt;</span> <span class="nt">&lt;ViewCell&gt;</span> <span class="nt">&lt;ContentView</span> <span class="na">cm:View.Model=</span><span class="s">"{Binding}"</span> <span class="na">cm:View.Context=</span><span class="s">"MasterView"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/ViewCell&gt;</span> <span class="nt">&lt;/DataTemplate&gt;</span> <span class="nt">&lt;/ListView.ItemTemplate&gt;</span> <span class="nt">&lt;/ListView&gt;</span> <span class="nt">&lt;/ContentPage&gt;</span> <span class="nt">&lt;/MasterDetailPage.Master&gt;</span> <span class="nt">&lt;MasterDetailPage.Detail&gt;</span> <span class="nt">&lt;ContentPage</span> <span class="na">cm:View.Model=</span><span class="s">"{Binding ActiveItem}"</span> <span class="na">cm:View.Context=</span><span class="s">"DetailView"</span> <span class="na">Title=</span><span class="s">"Master"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/MasterDetailPage.Detail&gt;</span> <span class="nt">&lt;/MasterDetailPage&gt;</span> </code></pre></div></div> <p>The imporatant part to note here is we’re using <code class="highlighter-rouge">View.Model</code> in the same way as our <a href="/blog/posts/tabbed-page-conductor">last post about TabbedPage</a>. Normally this would be since both the master data template and details content page are both being bound to a <code class="highlighter-rouge">CharacterViewModel</code> then a <code class="highlighter-rouge">CharacterView</code> could be injected.</p> <p>This wouldn’t be desirable since we don’t want the same view for both places. Instead we use <code class="highlighter-rouge">View.Content</code> to provide some extra view location.</p> <p>For the master list item we provide a context of <code class="highlighter-rouge">MasterView</code>, this means when resolving the view for <code class="highlighter-rouge">MasterDetail.ViewModels.CharacterViewModel</code> the result will not be the typical <code class="highlighter-rouge">MasterDetail.Views.CharacterView</code> but <code class="highlighter-rouge">MasterDetail.Views.Character.MasterView</code> and <code class="highlighter-rouge">MasterDetail.Views.Character.DetaisView</code> for our details panel. We now have two views for the same view model based on their context which can significantly reduce the complexity of our view models.</p> <p>The contents of the master and details view are straight forward.</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span> <span class="nt">&lt;ContentView</span> <span class="na">xmlns=</span><span class="s">"http://xamarin.com/schemas/2014/forms"</span> <span class="na">xmlns:x=</span><span class="s">"http://schemas.microsoft.com/winfx/2009/xaml"</span> <span class="na">x:Class=</span><span class="s">"MasterDetail.Views.Character.MasterView"</span><span class="nt">&gt;</span> <span class="nt">&lt;ContentView.Content&gt;</span> <span class="nt">&lt;StackLayout</span> <span class="na">Padding=</span><span class="s">"12"</span><span class="nt">&gt;</span> <span class="nt">&lt;Label</span> <span class="na">Text=</span><span class="s">"{Binding Name}"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/StackLayout&gt;</span> <span class="nt">&lt;/ContentView.Content&gt;</span> <span class="nt">&lt;/ContentView&gt;</span> </code></pre></div></div> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span> <span class="nt">&lt;ContentView</span> <span class="na">xmlns=</span><span class="s">"http://xamarin.com/schemas/2014/forms"</span> <span class="na">xmlns:x=</span><span class="s">"http://schemas.microsoft.com/winfx/2009/xaml"</span> <span class="na">x:Class=</span><span class="s">"MasterDetail.Views.Character.DetailView"</span><span class="nt">&gt;</span> <span class="nt">&lt;ContentView.Content&gt;</span> <span class="nt">&lt;Grid&gt;</span> <span class="nt">&lt;Image</span> <span class="na">Source=</span><span class="s">"{Binding Image}"</span> <span class="na">Grid.Row=</span><span class="s">"0"</span> <span class="na">Aspect=</span><span class="s">"AspectFill"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;StackLayout</span> <span class="na">Grid.Row=</span><span class="s">"0"</span> <span class="na">Padding=</span><span class="s">"24"</span> <span class="na">BackgroundColor=</span><span class="s">"#66000000"</span><span class="nt">&gt;</span> <span class="nt">&lt;Label</span> <span class="na">Text=</span><span class="s">"{Binding Name}"</span> <span class="na">TextColor=</span><span class="s">"#FFFFFF"</span> <span class="na">FontSize=</span><span class="s">"24"</span> <span class="na">FontAttributes=</span><span class="s">"Bold"</span> <span class="na">HorizontalTextAlignment=</span><span class="s">"Center"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;Label</span> <span class="na">Text=</span><span class="s">"{Binding Tagline}"</span> <span class="na">TextColor=</span><span class="s">"#FFFFFF"</span> <span class="na">FontAttributes=</span><span class="s">"Italic"</span> <span class="na">HorizontalTextAlignment=</span><span class="s">"Center"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/StackLayout&gt;</span> <span class="nt">&lt;/Grid&gt;</span> <span class="nt">&lt;/ContentView.Content&gt;</span> <span class="nt">&lt;/ContentView&gt;</span> </code></pre></div></div> <p>So there we have it, by making use of <code class="highlighter-rouge">View.Model</code> and <code class="highlighter-rouge">View.Context</code> we can create conducting view and view models that can be responsible for only the flow between master and details and not be mixed up with the data or display of the items themselves.</p> <p>We can use contexts to have multiples views for a view model even further reducing this complexity.</p> <p>The code for all of this is up on a new <a href="https://github.com/nigel-sampson/samples/tree/master/MasterDetail">GitHub samples repository</a> where I plan to post more of the code from posts like these.</p> Wed, 29 Mar 2017 00:00:00 +0000 http://compiledexperience.com/blog/posts/master-detail-forms http://compiledexperience.com/blog/posts/master-detail-forms nigel.sampson@compiledexperience.com (Nigel Sampson) Caliburn.Micro roadmap <p>In the last couple of days I’ve put together and published a <a href="http://caliburnmicro.com/roadmap">roadmap</a> for Caliburn.Micro, there’s a couple of reasons for this. The first is help me plan what I want to do and what will be needed, the second to let people know what to expect in the future (although I deliberately didn’t attach dates to anything).</p> <p>It’s been a rough six months for me personally in terms of getting time to do open source development, but my life has settled down a lot now and I’m hoping to get back into it. This roadmap is the first step in that path.</p> <p>Part of this is also working out how I want to do development on these disparate areas and hopefully encourage other people to help out. I’m considering using GitHub projects to sketch things out in terms of adding notes and potentially pointing the way for others.</p> <p>Would love to know your thoughts on any of this and the <a href="http://caliburnmicro.com/roadmap">roadmap</a> itself.</p> Fri, 17 Feb 2017 00:00:00 +0000 http://compiledexperience.com/blog/posts/caliburn-micro-roadmap http://compiledexperience.com/blog/posts/caliburn-micro-roadmap nigel.sampson@compiledexperience.com (Nigel Sampson)