0

I'm trying to make a re-usable WinUI dialog to display progress information, but I want the fact that I'm using a ContentDialog to be an implementation detail and not expose its API. I figured I could do this by deriving from Control and creating a ContentDialog inside of its ControlTemplate.

Something like this:

[TemplatePart(Name = PART_Dialog, Type = typeof(ContentDialog))]
public class ProgressDialog : Control
{
    private const string PART_Dialog = "PART_Dialog";

    private ContentDialog _dialog;

    public ProgressDialog()
    {
        DefaultStyleKey = typeof(ProgressDialog);
    }

    public async Task ShowAsync()
    {
        if (_dialog != null)
        {
            _ = await _dialog.ShowAsync(ContentDialogPlacement.Popup);
        }
    }

    protected override void OnApplyTemplate()
    {
        _dialog = GetTemplateChild(PART_Dialog) as ContentDialog;
        base.OnApplyTemplate();
    }
}

With a style defined like so:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MyApp.Controls">

    <Style TargetType="local:ProgressDialog" BasedOn="{StaticResource DefaultProgressDialog}" />

    <Style x:Key="DefaultProgressDialog" TargetType="local:ProgressDialog">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:ProgressDialog">
                    <ContentDialog x:Name="PART_Dialog">
                        <Grid>
                            <TextBlock Text="Hello, world!" />
                        </Grid>
                    </ContentDialog>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

And then I would show the dialog like a ContentDialog:

var dialog = new ProgressDialog();
dialog.XamlRoot = this.XamlRoot;
await dialog.ShowAsync();

I have the resource dictionary specified in Generic.xaml, but the control doesn't even attempt to load the template. My OnApplyTemplate method is never called, so _dialog doesn't get wired up. I assume this is because I'm not actually creating the control in the visual tree, but then how does ContentDialog do it?

If I call ApplyTemplate() myself in ShowAsync(), it returns false and the template still isn't loaded.

David Brown
  • 35,411
  • 11
  • 83
  • 132

1 Answers1

0

How do I wrap a ContentDialog in a custom Control?

Derive from this document

Run code that can only work once the XAML-defined visual tree from templates has been applied. For example, code that obtains references to named elements that came from a template, by calling GetTemplateChild, so that members of these parts can be referenced by other post-template runtime code.

If you just implement, but not add into visual tree. OnApplyTemplate will not be invoke, and GetTemplateChild will return null. please declare in the xaml like the following.

<Grid>
    <Button Click="Button_Click" Content="Open" />
    <local:ProgressDialog x:Name="Dialog" />
</Grid>

Or make a class that inherit ContentDialog directly, for more please refer this document.

Nico Zhu
  • 32,367
  • 2
  • 15
  • 36
  • It indeed works if I instantiate the control in XAML, but I'm not required to do that with ContentDialog. So what is it doing that allows it to load a template without being in the visual tree? Does it add itself to the tree? Is that why it requires XamlRoot to be set? – David Brown Jun 10 '21 at 02:14
  • `XamlRoot` Represents a tree of XAML content and information about the context in which it is hosted. if you have not insert to xaml, it will not work. – Nico Zhu Jun 10 '21 at 02:20
  • You may refer this case [reply](https://stackoverflow.com/a/55351260/7254781) to make the custom dialog. – Nico Zhu Jun 10 '21 at 02:25