Reputation: 850
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
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
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