Microsoft-ui-xaml: Two questions regarding porting WPF code to WinUI: Window Datacontext and Application.Current.Dispatcher

Created on 16 Jun 2020  路  13Comments  路  Source: microsoft/microsoft-ui-xaml

Using .Net 5 preview 4 and WinUI 3.0 preview, I'm experimenting porting WPF code and have encountered two questions:

  1. What has happened to the DataContext property for the MainWindow xaml - are viewmodels binded differently?
  2. What is the equivalent of initiating tasks on the Application.Current.Dispatcher GUI thread?
question winui3preview

Most helpful comment

OK thanks for pointers, will look at converting to x:Bind as that seems to be the preferred option.
However, my existing code uses a lot of User Controls which simply content bind to ViewModels:
<ContentControl Content="{Binding ControlViewModel}" />

which in turn refers to my MainWindow resources to load the related view:
<DataTemplate DataType="{x:Type vms:ControlViewModel}"> <local:ControlView /> </DataTemplate>

Is this still possible or is there also a different/better approach?
Was hoping for minimal changes ;)

All 13 comments

@MikeHillberg @ryalanms or @oscarcabrero could one of you weigh in on question 1?

The answer to 2 is to use DispatcherQueue.GetForCurrentThread().TryEnqueue() https://docs.microsoft.com/en-us/uwp/api/windows.system.dispatcherqueue.tryenqueue?view=winrt-19041#Windows_System_DispatcherQueue_TryEnqueue_Windows_System_DispatcherQueueHandler_

Window in WinUI isn't a FrameworkElement like it is in WPF, and so doesn't inherit the DataContext property. So you need to set the DataContext on the root element.

It could potentially be added. But DataContext isn't used in WinUI as often as it is in WPF, because WinUI has x:Bind, which doesn't need it. (WinUI does still have Binding though.)

@Wigmund
1) I was also wondering what happened with the DataContext in the MainWindow
2) please visit also this question Dispatchers in WinUI3. There are multiple options how to use the Dispatcher in WINUI 3 Preview 1

OK thanks for pointers, will look at converting to x:Bind as that seems to be the preferred option.
However, my existing code uses a lot of User Controls which simply content bind to ViewModels:
<ContentControl Content="{Binding ControlViewModel}" />

which in turn refers to my MainWindow resources to load the related view:
<DataTemplate DataType="{x:Type vms:ControlViewModel}"> <local:ControlView /> </DataTemplate>

Is this still possible or is there also a different/better approach?
Was hoping for minimal changes ;)

@Wigmund I did the exact same think. I converted a WPF app to WinUI Desktop app Preview.
Here are my findings hopefully they will spare you a lot of time and you will remain happy :)

1) ObservableCollections does not raise notifications to the UI, or at least the UI is not refreshed.
2) {Binding}s are in my opinion not working correctly
3) be aware that the default mode for x:Bind is OneTime!

In case you would like to switch to x:Bind you can start with a dependency property for your view model:

{x:Bind ViewModel.Property}

```C#
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(nameof(ViewModel),
typeof(MainViewModel),
typeof(MainPage),
new PropertyMetadata(null));

    public MainViewModel ViewModel
    {
      get => GetValue(ViewModelProperty) as MainViewModel;
      set => SetValue(ViewModelProperty, value);
    }

This still works:
```Xaml
<DataTemplate x:DataType="viewModels:ProductViewModel">

with x:Binds inside the template the bindings are actually compile/design time safe. This was not present in WPF

Thanks for the heads up.
It appears that isn't a thing either :/ I guess because its not a proper FrameworkElement

Compilation fails with a "property not found" when using

<DataTemplate x:Name="vStartPageView" x:DataType="vms:StartPageViewModel">

in the Application resource dictionary - is this expected?
Compiles fine without the DataType property...

@Wigmund I did the exact same think. I converted a WPF app to WinUI Desktop app Preview.
Here are my findings hopefully they will spare you a lot of time and you will remain happy :)

  1. ObservableCollections does not raise notifications to the UI, or at least the UI is not refreshed.
  2. {Binding}s are in my opinion not working correctly
  3. be aware that the default mode for x:Bind is OneTime!

In case you would like to switch to x:Bind you can start with a dependency property for your view model:

{x:Bind ViewModel.Property}
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(nameof(ViewModel),
          typeof(MainViewModel),
          typeof(MainPage),
          new PropertyMetadata(null));

        public MainViewModel ViewModel
        {
          get => GetValue(ViewModelProperty) as MainViewModel;
          set => SetValue(ViewModelProperty, value);
        }

This still works:

<DataTemplate x:DataType="viewModels:ProductViewModel">

with x:Binds inside the template the bindings are actually compile/design time safe. This was not present in WPF

I see in your sample you are using a UserControl to bind the ViewModel to. I guess this is because Window is not a DependencyObject (FrameworkElement) so you can't create a dependency property on it.

I was trying to create a MainViewModel for the MainWindow itself. I guess that isn't possible.

Steve

I see in your sample you are using a UserControl to bind the ViewModel to. I guess this is because Window is not a DependencyObject (FrameworkElement) so you can't create a dependency property on it.

@abritabroad yes that's why I ended with a UserControl. I originally wanted to use PRISM, but it's not available for WinUI3. Currently it's only for WinUI 2. So I also wanted to start with (Shell(MainWindow).xaml/ShellViewModel(MainViewModel)) pair.

It seems that I will have to use conditional compilation in my shared project in some cases, since there are at least three different namespaces (WPF, WinUI2/UWP, WinUI3) :(
```C#
using System.Windows.Controls;
using Windows.UI.Xaml;
using Microsoft.UI.Xaml;

```C#
    protected override void InitializeShell(UIElement /*Window*/ shell)
    {
      shell.DataContext = shellViewModel;

      base.InitializeShell(shell);
    }

OK thanks for pointers, will look at converting to x:Bind as that seems to be the preferred option.
However, my existing code uses a lot of User Controls which simply content bind to ViewModels:

which in turn refers to my MainWindow resources to load the related view:

Is this still possible or is there also a different/better approach?
Was hoping for minimal changes ;)

@Wigmund I tried it in a UWP app and it didn't work there neither, so this is probably expected. I moved the datatemplate inside a separate resource dictionary and it worked.

I was also hoping for minimal changes and even sharing of XAML, but different namespace declarations and so on won't enable this (WPF vs WinUI3):

xmlns:views="clr-namespace:Joker.Sample.Views"

xmlns:views="using:Joker.Sample.Views"

AFAIK it's not possible to use conditional compilation in a XAML file.

Note that with x:Bind the source property doesn't need to be a DependencyProperty, it can be any C# property.

To keep the markup/code the same, can you set the DataContext on the Window's root panel? That prevents the Window itself from binding to the data context, but works the same for anything below the panel.

Note that with x:Bind the source property doesn't need to be a DependencyProperty, it can be any C# property.

oh, that's good to know! My brain still thinks in WPF. Been on the web side for several years now.

When trying to figure out why I was getting the following compilation error, Google brought me here.

The XAML Binary Format (XBF) generator reported syntax error '0x09C4' : Property Not Found

As a WPF developer, the above error just consumed a couple hours of my day, in part because I allowed myself to become frustrated. I thought I would share the solution so that other developers who are led here by Google can find the answer.

The problem occurs as @Wigmund stated, when you are using a DataType on a DataTemplate.

<DataTemplate x:Key="TitleBarVmDataTemplate" x:DataType="vm:TitleBarVm">
    <customControls:TitleBar Background="{StaticResource TitleBarColor}" />
</DataTemplate>

Although I tried to move the template all over the app, the final attempt was to keep it in a resource dictionary named DataTemplates.xaml. To stop the compilation error, I needed to add a class definition to the DataTemplates.xaml file and then create a partial class DataTemplates.xaml.cs to match.

Even though I am still getting use to the UWP way of XAML vs WPF, I was aware that sometimes you had to have a backing '.cs' file for the XAML. However, I thought that you only needed that when using the 'x:Bind' markup extension. Apparently I was wrong because the above syntax won't even work in a UWP application.

Update - using x:Bind in a Resource Dictionary

I further learned that when you include a dictionary that has been defined as described above it is Important that you include it like the following. If you don't, InitializeComponent will not be called and x:Bind will not work! This is an easy mistake to make.

<Application
    x:Class="WinUI.DemoApp.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:templates="using:WinUI.CustomControls.Templates">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />

                <!-- 
                Please note that when including a dictionary that has a code behind file you MUST
                include the dictionary as if you were defining an instance of it directly.  This
                can take a couple days off your life if you make this mistake.  
                -->

                <!-- NOTE THAT THE FOLLOWING LINE IS CORRECT -->
                <templates:DataTemplates />

                <!-- NOTE THAT THE FOLLOWING LINE IS WRONG -->
                <!-- <ResourceDictionary Source="ms-appx:///WinUI.CustomControls/Templates/DataTemplates.xaml" /> -->

            </ResourceDictionary.MergedDictionaries>

        </ResourceDictionary>
    </Application.Resources>
</Application>

I will never make this mistake again! :) Hopefully this helps somebody.

Was this page helpful?
0 / 5 - 0 ratings