Ali Asgari
Ali Asgari

Reputation: 850

Parameter is null only when using ContentControl

I'm developing UI for an application using WPF. I'm using command binding. Command works correctly when the button is put inside the Window. But when I put the button inside my UserControl the command parameter is null.

ViewModel code:

        public RelayCommand<Window> MainCommand { get; private set; }
        private void MainAction(Window window)
        {
            // here the parameter is null
            if (window == null) return;
            MainPage main = new MainPage();
            main.Show();
            window.Close();
        }

The user control:

    <UserControl x:Class="Kitchen.UI.View.HeaderFooter"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:Kitchen.UI.View"
                 mc:Ignorable="d" 
                 d:DesignHeight="300" d:DesignWidth="300">
        <UserControl.Resources>
            <ResourceDictionary>
                <ResourceDictionary.MergedDictionaries>
                    <ResourceDictionary Source="../Skins/MainSkin.xaml"/>
                </ResourceDictionary.MergedDictionaries>
            </ResourceDictionary>
        </UserControl.Resources>

        <UserControl.Template>
            <ControlTemplate TargetType="UserControl">
                <Grid DataContext="{Binding DataContext, RelativeSource={RelativeSource TemplatedParent}}">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"></RowDefinition>
                        <RowDefinition Height="*"></RowDefinition>
                        <RowDefinition Height="Auto"></RowDefinition>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                        <ColumnDefinition Width="Auto"></ColumnDefinition>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                    </Grid.ColumnDefinitions>

                    <ScrollViewer Grid.Column="1" Grid.Row="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
                        <ContentControl Content="{TemplateBinding Content}"/>
                    </ScrollViewer>
                    <Image Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="3" Source="{StaticResource HeaderImage}"
                   Margin="0 10 0 0" HorizontalAlignment="Stretch" SizeChanged="Image_SizeChanged" IsHitTestVisible="False"></Image>
                    <Image Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="3" Source="{StaticResource FooterImage}"
                   Margin="0 -1 0 10" HorizontalAlignment="Stretch"  Height="Auto"></Image>


                </Grid>
            </ControlTemplate>
        </UserControl.Template>

    </UserControl>

The way I put the button inside the user control:

<Window xmlns:View="clr-namespace:Kitchen.UI.View"  x:Class="Kitchen.UI.View.Order" Name="OWindow"  ResizeMode="NoResize" WindowState="Maximized"
        WindowStartupLocation="CenterOwner" WindowStyle="None "
        Width="1600"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:ignore="http://www.galasoft.ch/ignore"
        mc:Ignorable="d ignore" 
        DataContext="{Binding Order, Source={StaticResource Locator}}">



    <View:HeaderFooter x:Name="HeaderFooter">
        <Button Grid.Row="1" Grid.Column="0" 
                Command="{Binding ShowExitCommand}"
                        CommandParameter="{Binding ElementName=OWindow}"
                Style="{StaticResource ImageButtonStyle}">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition ></RowDefinition>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <Image Grid.Column="0" Grid.Row="0" Source="{StaticResource ButtonBack}" Width="180" Grid.ColumnSpan="2" />
                    <TextBlock Grid.Column="1" Grid.Row="0" Text="مشاهده سفارشات خارج شده از صف" Style="{StaticResource ImageButtonTextBlockStyle}" FontSize="15"/>
                </Grid>
        </Button>
    </View:HeaderFooter>
</Window>

Upvotes: 0

Views: 85

Answers (2)

Ali Asgari
Ali Asgari

Reputation: 850

Finally I changed my way through the problem. I needed a template to be applied to several windows, so I used a UserControl and it was successful as a template unless I wanted to pass the reference of the containing window of the UserControl as a parameter to a command from within the content of the UserControl.

Now I'm applying a custom ControlTemplate to the Window and it's working now.

But I think this is a bug or at least an architectural weakness for WPF. When you use {Binding ElementName=OWindow} from within the Window itself or other Controls like Grid the reference is resolved and handled but if you use it inside the user defined user control it causes the null reference.

I think this is because they are capturing the parameter at the Window construction.

Upvotes: 0

user6996876
user6996876

Reputation:

Try with x:Reference instead of ElementName, this should bring language-level support for element reference resolution, which ElementName (that operates at the framework level) may not be able to resolve.

CommandParameter="{Binding Source={x:Reference OWindow}}"

To avoid a cyclic dependency, you have to reference something that is not the container of the user control itself, for example

<Grid >
    <DataGrid  Name="OWindow" Width="10" Height="10"/>

    <local:HeaderFooter x:Name="HeaderFooter">
    <Button Grid.Row="1" Grid.Column="0" 
            Command="{Binding ShowExitCommand}"
                    CommandParameter="{Binding  Source={x:Reference OWindow} }" 
            > <!-- instead of ElementName=OWindow   -->

Please note, the above example makes clear that my answer correctly resolves the issue in a consistent context, while the circular reference is just another issue of the original code to be fixed.

Upvotes: 1

Related Questions