Locating View/ViewModel for POCO

Topics: Conventions
Oct 11, 2011 at 8:54 AM

So i was setting up a view today which contains a lost bound to a POCO and i wasn't expecting the view to display data because i hadn't set up the view i want to use as the item template for the POCO. When i started it i get the message "Cannot find view for ...." in the list box. Obviously Caliburn is smart enough to try to find a view for it but i can't figure out what the convention is that it uses. What do i need to do to my ViewModel to say this ViewModel belongs to this type of object?

Oct 11, 2011 at 10:46 AM

You have to set up a new naming convention adding a new rule to ViewLocator.NameTransformer.
This way you can easily pair each POCO type with a corresponding view.

Another option you have is to set up a convention for ViewLocator tweaking one of its "overload", usually ViewLocator.LocateForModelType.
Replacing the default delegate you can inspect the type of the model being represented in the UI and choose a view accordingly.
This way you can return an appropriate view for your POCO or fall back to the default implementation for the general case.

Oct 12, 2011 at 7:44 AM

Ok so my regex is pretty bad. If i had a model called Product and my ProductManagerViewModel looks like

 

 

    public class ProductsManagerViewModel
    {
        public ProductsManagerViewModel()
        {
            Products = new ObservableCollection<Product>();
        }

        public ObservableCollection<Product> Products { get; set; }
    }

 

The ListBox binds to the Products property and says that it can't find the view for Product.  Based on what you said i tried this

            ViewModelLocator.NameTransformer.AddRule("Product$", "ProductViewModel");
            ViewLocator.NameTransformer.AddRule("Product$", "ProductView");
But i think i did something wrong because this didn't help.

Oct 12, 2011 at 4:44 PM

doesn't necessarily mean it was your viewmodel that was at fault though, how did you setup the view for the listbox in xaml?

Oct 13, 2011 at 12:02 AM

Nothing special, just what is shown below

<Window x:Class="Products.ProductsManagerView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ProductsView" Height="300" Width="300">
    <Grid>
        <StackPanel>
            <ListBox x:Name="Products"/>
        </StackPanel>
    </Grid>
</Window>
Oct 13, 2011 at 1:33 AM
nitro52 wrote:

Ok so my regex is pretty bad. If i had a model called Product and my ProductManagerViewModel looks like

 

 

    public class ProductsManagerViewModel
    {
        public ProductsManagerViewModel()
        {
            Products = new ObservableCollection<Product>();
        }

        public ObservableCollection<Product> Products { get; set; }
    }

 

The ListBox binds to the Products property and says that it can't find the view for Product.  Based on what you said i tried this

 

            ViewModelLocator.NameTransformer.AddRule("Product$", "ProductViewModel");
            ViewLocator.NameTransformer.AddRule("Product$", "ProductView");
But i think i did something wrong because this didn't help.

 


Do Product and ProductView/ProductViewModel share the same namespace? If not, then these custom rules won't work.

Oct 13, 2011 at 2:11 PM

ahh, they are in a xxxx.Models / xxxx.Views / xxxx.ViewModels namespace

How would you get that working?

Oct 13, 2011 at 7:38 PM
Edited Oct 13, 2011 at 7:43 PM

Take a look at the source files ViewLocator.cs and ViewModelLocator.cs. The static ctors contain rules that are added by default. You can take one of the rules that take into account namespace conventions, and customize it to your needs like so:

ViewLocator.NameTransformer.AddRule
(
    @"(?<nsbefore>([A-Za-z_]\w*\.)*)?(?<nsvm>Models\.)(?<nsafter>[A-Za-z_]\w*\.)*(?<basename>[A-Za-z_]\w*$)",
    @"${nsbefore}Views.${nsafter}${basename}View",
    @"(([A-Za-z_]\w*\.)*)?Models\.([A-Za-z_]\w*\.)*[A-Za-z_]\w*$"
);

ViewModelLocator.NameTransformer.AddRule
(
    @"(?<nsbefore>([A-Za-z_]\w*\.)*)?(?<nsview>Models\.)(?<nsafter>[A-Za-z_]\w*\.)*(?<basename>[A-Za-z_]\w*$)",
    new[] {
                    @"${nsbefore}ViewModels.${nsafter}${basename}ViewModel",
                    @"${nsbefore}ViewModels.${nsafter}${basename}",
                    @"${nsbefore}ViewModels.${nsafter}I${basename}ViewModel",
                    @"${nsbefore}ViewModels.${nsafter}I${basename}"
                },
    @"(([A-Za-z_]\w*\.)*)?Models\.([A-Za-z_]\w*\.)*[A-Za-z_]\w*$"
);

This will yield the following transformations:

View Name Resolution:

  • SomeNameSpace.Models.SomeType => SomeNameSpace.Views.SomeTypeView
  • SomeNameSpace.Models.SomeOtherNameSpace.SomeType => SomeNameSpace.Views.SomeOtherNameSpace.SomeTypeView

    ViewModel Name Resolution:

  • SomeNameSpace.Models.SomeType => SomeNameSpace.ViewModels.SomeTypeViewModel
  • SomeNameSpace.Models.SomeOtherNameSpace.SomeType => SomeNameSpace.ViewModels.SomeOtherNameSpace.SomeTypeViewModel