So, I have created a UserControl which really is an "advanced button".
I have implemented Dependency Properties, one of which is ICommand, that is supposed to be bindable further when control is used in an actual Window.
However, for some reason the Command doesn't work.
When I tried an exact same approach on a regular button, everything worked fine (thus it's not the fault of my DelegateCommand implementation or my ViewModel).
I tried to followup on why the bound command doesn't fire, but I couldn't find a reliable reason for it not to.
Here is my UserControl XAML:
<Window x:Class="NoContact.MainWindow"
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:local="clr-namespace:NoContact"
mc:Ignorable="d"
xmlns:userControls="clr-namespace:NoContact.UserControls"
Title="MainWindow" Height="800" Width="960" Background="#22282a">
<Border BorderThickness="1" BorderBrush="#ffcd22" Margin="10,10,10,10">
<Grid>
<Button HorizontalContentAlignment="Stretch" Foreground="{Binding ElementName=ImageButtonUC, Path=Foreground}"
Background="{Binding ElementName=ImageButtonUC, Path=Background}">
<DockPanel Width="{Binding ElementName=ImageButtonUC, Path=ActualWidth}">
<Image Source="{Binding ElementName=ImageButtonUC, Path=Image}" DockPanel.Dock="Left"
Height="{Binding ElementName=ImageButtonUC, Path=ActualHeight, Converter={StaticResource HeightConverter}}"
Width="{Binding ElementName=ImageButtonUC, Path=ActualWidth, Converter={StaticResource HeightConverter}}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding ElementName=ImageButtonUC, Path=Text}" FontSize="17" VerticalAlignment="Center" />
</DockPanel>
</Button>
</Grid>
<UserControl.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
And here is my UserControl code-behind:
public partial class ImageButton : UserControl
{
// OTHER IRRELEVANT CLASS PARAMETERS ARE HERE
public ICommand ClickCommand
{
get { return (ICommand)GetValue(ClickCommandProperty); }
set { SetValue(ClickCommandProperty, value); }
}
// OTHER IRRELEVANT DEPENDENCY PROPERTIES ARE HERE
public static DependencyProperty ClickCommandProperty =
DependencyProperty.Register("ClickCommand", typeof(ICommand), typeof(ImageButton));
public ImageButton()
{
InitializeComponent();
}
}
Finally, this is how I use my control:
<userControls:ImageButton x:Name="phoneButton" ClickCommand="{Binding Path=MyButtonClickCommand}" Style="{StaticResource phoneImageButtonUCStyle}" Text="Telefon" Width="200" Height="100" VerticalAlignment="Top" />
The DataContext of the control is set on a stackpanel, that is wrapped around my control. I have also tried setting it directly on the control, no effect.
Again, doing the same on a regular button works just fine.
I have finally solved the problem - and it was pretty trivial.
I had ommited the most important binding - UserControl's button Command to UserControl's Dependency property.
Changing it like this made it work:
<Button HorizontalContentAlignment="Stretch" Foreground="{Binding ElementName=ImageButtonUC, Path=Foreground}"
Background="{Binding ElementName=ImageButtonUC, Path=Background}"
Command="{Binding ElementName=ImageButtonUC, Path=ClickCommand}">
Related
Edit:
How is the proper way to create a Control to avoid the following problem with the ElementName Binding:
<TextBox x:Name="MyTextBox" Text="some Text"></TextBox>
<Label>
<!--Binding works-->
<TextBlock Text="{Binding Path=Text, ElementName=MyTextBox, FallbackValue='Binding Failed'}"></TextBlock>
</Label>
<Button>
<!--Binding works-->
<TextBlock Text="{Binding Path=Text, ElementName=MyTextBox, FallbackValue='Binding Failed'}"></TextBlock>
</Button>
<local:MyUserControl>
<!-- THIS BINDING FAILS !!!-->
<TextBlock Text="{Binding Path=Text, ElementName=MyTextBox, FallbackValue='Binding Failed'}"></TextBlock>
</local:MyUserControl>
MyUserControl.xaml:
<UserControl x:Class="Problem.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" />
MyUserControl.xaml.cs
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
}
Original:
Im still new to WPF and could not find out how to do this properly.
I basically want a UserControl's child to retain the behavior of being able to bind to the root element's x:Name in a XAML.
This is an example that shows the problem caused by my UserControl Descriptor compared to the WPF Controls:
<Parent x:Name="_thisParent">
...
<Label>
<!-- Binding to _thisParent works -->
<TextBlock Text="{Binding Path=MyText, ElementName=_thisParent}" />
</Label>
<uc:Descriptor Text="description: ">
<!-- Binding to _thisParent FAILS !! -->
<TextBlock Text="{Binding Path=MyText, ElementName=_thisParent}" />
</uc:Descriptor>
<Button>
<!-- Binding to _thisParent works -->
<TextBlock Text="{Binding Path=MyText, ElementName=_thisParent}" />
</Button>
Here is the Code for my UserControl:
Descriptor.xaml
<UserControl
x:Class="EmbedContent.UserControls.Descriptor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="_thisDescriptor">
<UserControl.Template>
<ControlTemplate>
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="{Binding Path=Text, ElementName=_thisDescriptor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, FallbackValue='Binding Failed'}" />
<ContentPresenter Content="{Binding Path=Content, ElementName=_thisDescriptor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, FallbackValue='Binding Failed'}" />
</DockPanel>
</ControlTemplate>
</UserControl.Template>
Descriptor.xaml.cs
public partial class Descriptor : UserControl
{
#region Ctor
public Descriptor()
{
InitializeComponent();
}
#endregion
#region Dependency-Properties
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(Descriptor), new PropertyMetadata("Descriptor's default Text"));
#endregion
How would i need to implement a custom UserControl / ContentControl (if needed without xaml) to retain the behavior of the WPF Controls?
How is this done according to best practice anyway? I assume i only run into this problem because i'm doing something wrong.
Answer, including clarifying your decision: I finally found a solution.
Think about the problem a little deeper.
Binding a property of an element is part of the functionality of the element itself, not of its container.
Therefore, if you change the DataContext of an element, the default bindings are interpreted relative to it, not the DataContext of its container.
For this reason, you should not assign the DataContext inside a UserControl, as the default bindings behavior will change in a way that is unexpected for the user (programmer).
Now think about how ElementName bindings work.
For example, in the XAML that creates a new class from UserControl, you have defined named elements.
And then create multiple instances of UserControl.
In this case, if all elements with the same names were in the same visual tree, this would create a conflict, since it is not known which of them is being accessed.
(In this case, the "visual tree" is an oversimplification. It actually has to do with the system for registering the names of the UI elements).
To avoid such conflicts, when using XAML for ANY element (not only UserControl), its own local visual tree is created that is not associated with the main visual tree.
BUT!
I already wrote above, Bindings are part of the functionality of the element itself.
And therefore, the binding of a property of type ElementName will search for an element by name not higher in the container, but in the UserControl itself.
The same goes for Templates.
Therefore, the names of elements inside Templates are not visible outside of it.
Default elements are not implemented as UserControl, but as Custom Control.
In this case, there is a separate Sharp class with element logic.
And a separate default Template for this class.
In this implementation, since no XAML is used to create the element, the element does not have its own internal local visual tree.
And all its bindings (of the ElementName type) are interpreted in relation to the general visual tree.
To some extent, you partially implemented this in the Descriptor: ContentControl class.
Only instead of regregistering the default Template for the type, you set it up in the XAML App.
This implementation will work for an example, but in many cases it can create other problems.
Therefore, it is better to use the "standard" Custom Control for which the theme is created and the default Template is registered.
In the case of DescriptorTwo, you specify x: Class = "EmbedContent.UserControls.DescriptorTwo" and this automatically creates its own local visual tree, relative to which the ElementName bindings will work.
Slightly modified example for the solution you found.
using System.Windows;
using System.Windows.Controls;
namespace EmbedContent.CustomControls
{
public class Descriptor : ContentControl
{
#region Registering a default template
static Descriptor()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Descriptor), new FrameworkPropertyMetadata(typeof(Descriptor)));
}
#endregion
#region Ctor
public Descriptor() : base() { }
#endregion
#region Dependency-Properties
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(nameof(Text), typeof(string), typeof(Descriptor), new PropertyMetadata("Descriptor's default Text"));
#endregion
}
}
Theme with default templates - file Themes/Generic.xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:customcontrols="clr-namespace:EmbedContent.CustomControls">
<Style TargetType="{x:Type customcontrols:Descriptor}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type customcontrols:Descriptor}">
<StackPanel>
<TextBlock x:Name="tblock" Text="Example Text in UserControl"/>
<TextBlock Text="{TemplateBinding Text}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
DescriptorTwo.xml:
<UserControl x:Class="EmbedContent.UserControls.DescriptorTwo"
x:Name="PART_Main"
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:EmbedContent.UserControls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<TextBlock x:Name="tblock" Text="Example Text in UserControl"/>
<TextBlock Text="{Binding Text, ElementName=PART_Main}"/>
</StackPanel>
</UserControl>
Examle Window:
<Window x:Class="EmbedContent.ExampleWindow"
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:local="clr-namespace:EmbedContent"
xmlns:customcontrols="clr-namespace:EmbedContent.CustomControls"
xmlns:usercontrols="clr-namespace:EmbedContent.UserControls"
mc:Ignorable="d"
Title="ExampleWindow" Height="450" Width="800">
<StackPanel>
<TextBlock x:Name="tblock" Text="Example Text in Parent UIElement"/>
<Border Background="LightBlue" Margin="20" Padding="10"
BorderBrush="Blue" BorderThickness="2">
<customcontrols:Descriptor Text="{Binding Text, ElementName=tblock}"/>
</Border>
<Border Background="LightGreen" Margin="20" Padding="10"
BorderBrush="Green" BorderThickness="2">
<usercontrols:DescriptorTwo Text="{Binding Text, ElementName=tblock}"/>
</Border>
</StackPanel>
</Window>
Result:
As you can see in the XAML Designer and when launched at runtime, such bindings (in the UserControl) also work differently, which causes additional problems.
Therefore, the main purpose of the UserControl is to represent the Data coming through the DataContext.
Also consider the moment with the assignment of Content.
In UserControl in XAML, you set a value for this property.
And assigning it when used does not complement the presentation, but replace it.
In CustonControl, you can explicitly specify in the Template where to insert additional content.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:customcontrols="clr-namespace:EmbedContent.CustomControls">
<Style TargetType="{x:Type customcontrols:Descriptor}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type customcontrols:Descriptor}">
<StackPanel>
<TextBlock x:Name="tblock" Text="Example Text in UserControl"/>
<TextBlock Text="{TemplateBinding Text}"/>
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
<Window x:Class="EmbedContent.ExampleWindow"
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:local="clr-namespace:EmbedContent"
xmlns:customcontrols="clr-namespace:EmbedContent.CustomControls"
xmlns:usercontrols="clr-namespace:EmbedContent.UserControls"
mc:Ignorable="d"
Title="ExampleWindow" Height="250" Width="400">
<StackPanel>
<TextBlock x:Name="tblock" Text="Example Text in Parent UIElement"/>
<Border Background="LightBlue" Margin="20" Padding="10"
BorderBrush="Blue" BorderThickness="2">
<customcontrols:Descriptor Text="{Binding Text, ElementName=tblock}">
<TextBlock Text="{Binding Text, ElementName=tblock}"/>
</customcontrols:Descriptor>
</Border>
<Border Background="LightGreen" Margin="20" Padding="10"
BorderBrush="Green" BorderThickness="2">
<usercontrols:DescriptorTwo Text="{Binding Text, ElementName=tblock}">
<TextBlock Text="{Binding Text, ElementName=tblock}"/>
</usercontrols:DescriptorTwo>
</Border>
</StackPanel>
</Window>
Result:
I finally found a solution but it still seems somewhat ugly so i expect someone to post a better solution.
I post the code here including an example highlighting the problem again.
Solution:
split UserControl / ContentControl up into a .cs class inheriting from e.g. ContentControl and set the Template describing the extra elements in the App.xaml Resources. Basically move the .xaml part as style into Resources.
Descriptor.cs
namespace EmbedContent.UserControls
{
class Descriptor : ContentControl{
#region Ctor
public Descriptor() : base() {}
#endregion
#region Dependency-Properties
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(Descriptor), new PropertyMetadata("Descriptor's default Text"));
#endregion
}
}
App.xaml
<Application
x:Class="EmbedContent.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:EmbedContent"
xmlns:uc="clr-namespace:EmbedContent.UserControls"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Style TargetType="uc:Descriptor">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="uc:Descriptor">
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="{Binding Path=Text, RelativeSource={RelativeSource AncestorType=uc:Descriptor}}" />
<ContentPresenter />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
The above can call and consume the Descriptor as i expect it from a WPF Control.
Here the Code for the UserControl, that breaks the binding when used:
DescriptorTwo.xaml
<UserControl
x:Class="EmbedContent.UserControls.DescriptorTwo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="_thisDescriptorTwo">
<UserControl.Template>
<ControlTemplate>
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="{Binding Path=Text, ElementName=_thisDescriptorTwo}" />
<ContentPresenter Content="{Binding Path=Content, ElementName=_thisDescriptorTwo}" />
</DockPanel>
</ControlTemplate>
</UserControl.Template>
DescriptorTwo.xaml.cs
namespace EmbedContent.UserControls
{
public partial class DescriptorTwo : UserControl
{
#region Ctor
public DescriptorTwo()
{
InitializeComponent();
}
#endregion
#region Dependency-Properties
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(DescriptorTwo), new PropertyMetadata("Descriptor's default Text"));
#endregion
}
}
And here an example of how both are called from another UserControl
Example.xaml
<UserControl
x:Class="EmbedContent.UserControls.EmbedContentExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:EmbedContent.UserControls"
x:Name="parent">
<StackPanel>
<Label>
<!-- Binding to parent works -->
<TextBlock Text="{Binding Path=MyText, ElementName=parent}" />
</Label>
<uc:Descriptor Text="description: ">
<!-- Binding to parent works -->
<TextBlock Text="{Binding Path=MyText, ElementName=parent}" />
</uc:Descriptor>
<uc:DescriptorTwo Text="description: ">
<!-- Binding to parent fails -->
<TextBlock Text="{Binding Path=MyText, ElementName=parent}" />
</uc:DescriptorTwo>
</StackPanel>
and the codebehind
Example.xaml.cs
namespace EmbedContent.UserControls
{
public partial class EmbedContentExample : UserControl
{
public EmbedContentExample()
{
InitializeComponent();
}
public string MyText
{
get { return (string)GetValue(MyTextProperty); }
set { SetValue(MyTextProperty, value); }
}
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register("MyText", typeof(string), typeof(EmbedContentExample), new PropertyMetadata("EmbedContentExample's MyText"));
}
}
I'm quite new to WPF and C#. I'm trying to catch a double click event in an user control and handle it through an ICommand.
Here is how i'm trying to do that:
User Control:
<UserControl x:Class="MyNamespace.View.MyUserControl"
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:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:MyNamespace.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
DataContext="{Binding MyViewModelClass, Source={StaticResource Locator}}"
>
<UserControl.Resources>
<DataTemplate x:Key="MyTemplate">
<ContentControl>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding DataContext.HandleDoubleClickCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<WrapPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="12" >
<TextBlock Text="Data: "><Run FontWeight="Light" Text="{Binding data}"/></TextBlock>
</WrapPanel>
</ContentControl>
</DataTemplate>
</UserControl.Resources>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{DynamicResource ColorPanel2}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" BorderThickness="0 0 0 1">
<WrapPanel Margin="12" VerticalAlignment="Center">
<TextBlock FontWeight="Bold" Margin="8" VerticalAlignment="Center" FontSize="18"
Text="some text: ">
</TextBlock>
</WrapPanel>
</Border>
<ListView Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding SelectedItem.listElement, UpdateSourceTrigger=PropertyChanged}"
ItemTemplate="{StaticResource MyTemplate}"
SelectedItem="{Binding SelectedItem}"
/>
</Grid>
</UserControl>
And i placed the command to handle the event in the ViewModel class:
ICommand _HandleDoubleClickCommand;
public ICommand HandleDoubleClickCommand
{
get
{
if (_HandleDoubleClickCommand == null)
{
_HandleDoubleClickCommand = new RelayCommand<object>(ExecuteHandleDoubleClickCommand, CanExecuteHandleDoubleClickCommand);
}
return _HandleDoubleClickCommand;
}
}
private bool CanExecuteHandleDoubleClickCommand(object arg)
{
return true;
}
private void ExecuteHandleDoubleClickCommand(object obj)
{
System.Windows.MessageBox.Show("HandleDoubleClickCommand");
}
So, basically i have a list of elements and i want to handle the double click event on is.
The behavior i see is that sometimes the HandleDoubleClickCommand gets executed, sometimes it's not. I don't see any exception thrown and i checked with the debugger that the code is executed only when the message box is shown.
I also tried to use InputBindings instead of Interaction.Triggers:
<ContentControl.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick"
Command="{Binding DataContext.HandleDoubleClickCommand, RelativeSource={RelativeSource AncestorType=UserControl}, diag:PresentationTraceSources.TraceLevel=High}" />
But i see the same behavior. Enabling the TraceLevel=High didn't give me useful info..
Also, i tried to handle the right click event instead of the double click, but still, the command gets executed only sometimes.
I think i'm missing something here, could someone help me to understand what the issue is? or at least give me some advice on how i can debug the problem..
Thank you
UPDATE
I found out that the command gets always executed if i double click on the element text. The thing is that i would like it to be executed even if i double click on an empty space of the selected row of the ListView...
Move the Margin from the WrapPanel to the TextBlock and define an ItemContainerStyle that makes the contents of the ListViewItem stretch horizontally.
You should also set the Background property of the WrapPanel to capture the clicks:
<ListView Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding SelectedItem.listElement}"
SelectedItem="{Binding SelectedItem}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding DataContext.HandleDoubleClickCommand,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<WrapPanel Background="Transparent">
<TextBlock Text="Data: " Margin="12"><Run FontWeight="Light" Text="{Binding data}"/></TextBlock>
</WrapPanel>
</ContentControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I'm looking for a way to assign style with parameters(most of them just text) and assign to specified blocks
<StackPanel Orientation="Horizontal">
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="field1" Text="Field1"/>
</StackPanel>
</StackPanel>
<StackPanel>
<Button BorderThickness="0">
<Button.Content>
<Border CornerRadius="18" BorderThickness="1" BorderBrush="#FFCFCFCF" >
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="10" Text="Default" Foreground="#FFCFCFCF" Margin="0" FontWeight="Black"/>
</StackPanel>
</Border>
</Button.Content>
</Button>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock x:Name="field2" Text="Field2"/>
</StackPanel>
</StackPanel>
<StackPanel>
<Button BorderThickness="0">
<Button.Content>
<Border CornerRadius="18" BorderThickness="1" BorderBrush="#FFCFCFCF" >
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="10" Text="RCC" Foreground="#FFCFCFCF" Margin="0" FontWeight="Black"/>
</StackPanel>
</Border>
</Button.Content>
</Button>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock x:Name="field3" Text="Field3"/>
</StackPanel>
</StackPanel>
<Rectangle Width="1" Fill="Black" Height="42" VerticalAlignment="Center"/>
<StackPanel Orientation="Horizontal">
<Button BorderThickness="0">
<Button.Content>
<Border CornerRadius="18" BorderThickness="1" BorderBrush="#FFCFCFCF" >
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="10" Text="Custom" Foreground="#FFCFCFCF" FontWeight="Black"/>
</StackPanel>
</Border>
</Button.Content>
</Button>
<TextBox Width="90" Height="15"/>
<Button BorderThickness="0">
<Button.Content>
<Border CornerRadius="18" BorderThickness="1" BorderBrush="#FFCFCFCF" >
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="10" Text="Apply" Foreground="#FFCFCFCF" FontWeight="Black"/>
</StackPanel>
</Border>
</Button.Content>
</Button>
</StackPanel>
</StackPanel>
There is 3 TextBlocks(field1,field2,field3), now is there any way to pass parameters(parameters are string type), to this template, and this template is generated through loop. And how to do it? Of course I could make everything in c# but thought it would be much easier just create field(stackpanel) and assign parameters
<stackpanel Style="{StaticResource mystyle}" param1="hello" param2="this" param3="world"/>
This would be perfect if its possible to make this way. Unless there is better one. Thanks for help.
You can by declaring your own Styles and Control templates with additional use of DependencyProperties.
A DependencyProperty is basically a declaration on your own custom class of your own custom property that you want to expose available during xaml entry and can also be applied to your style templates.
Once that is done, you then define your style, plenty of resources on that. Include your dependency properties as {TemplateBinding} to your custom properties.
Then add instance of your new class to your form, and specify which style to use. I have a sample showing utilization of TWO styles under the same class. I first started with a brand new WPF application. In the MainWindow.xaml.cs, I defined my own class based on a type of UserControl (which can then hold any other control(s) such as you have nested). I added 3 Dependency Properties to reflect 3 possible text values you want to implement.
public class MyControl : UserControl
{
public static readonly DependencyProperty MyText1Property =
DependencyProperty.Register("MyText1", typeof(string),
typeof(MyControl), new UIPropertyMetadata(""));
public string MyText1
{
get { return (string)GetValue(MyText1Property); }
set { SetValue(MyText1Property, value); }
}
public static readonly DependencyProperty MyText2Property =
DependencyProperty.Register("MyText2", typeof(string),
typeof(MyControl), new UIPropertyMetadata(""));
public string MyText2
{
get { return (string)GetValue(MyText2Property); }
set { SetValue(MyText2Property, value); }
}
public static readonly DependencyProperty MyText3Property =
DependencyProperty.Register("MyText3", typeof(string),
typeof(MyControl), new UIPropertyMetadata(""));
public string MyText3
{
get { return (string)GetValue(MyText3Property); }
set { SetValue(MyText3Property, value); }
}
}
Next, my application name is StackOverflow for sample purposes, and in the following is the entire MainWindow.xaml. Clarification of components follows code.
<Window.Resources>
<Style TargetType="{x:Type myApp:MyControl}" x:Key="MyControlStyle1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type myApp:MyControl}" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{TemplateBinding MyText1}"/>
<TextBlock Text="{TemplateBinding MyText2}"/>
<TextBlock Text="{TemplateBinding MyText3}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type myApp:MyControl}" x:Key="MyControlStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type myApp:MyControl}" >
<StackPanel Orientation="Horizontal" Grid.Row="0">
<TextBlock Text="{TemplateBinding MyText1}"/>
<StackPanel>
<Button BorderThickness="0">
<Button.Content>
<Border CornerRadius="18" BorderThickness="1" BorderBrush="#FFCFCFCF" >
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="10" Text="Default" Foreground="#FFCFCFCF" Margin="0" FontWeight="Black"/>
</StackPanel>
</Border>
</Button.Content>
</Button>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="{TemplateBinding MyText2}"/>
</StackPanel>
</StackPanel>
<StackPanel>
<Button BorderThickness="0">
<Button.Content>
<Border CornerRadius="18" BorderThickness="1" BorderBrush="#FFCFCFCF" >
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="10" Text="RCC" Foreground="#FFCFCFCF" Margin="0" FontWeight="Black"/>
</StackPanel>
</Border>
</Button.Content>
</Button>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="{TemplateBinding MyText3}"/>
</StackPanel>
</StackPanel>
<Rectangle Width="1" Fill="Black" Height="42" VerticalAlignment="Center"/>
<StackPanel Orientation="Horizontal">
<Button BorderThickness="0">
<Button.Content>
<Border CornerRadius="18" BorderThickness="1" BorderBrush="#FFCFCFCF" >
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="10" Text="Custom" Foreground="#FFCFCFCF" FontWeight="Black"/>
</StackPanel>
</Border>
</Button.Content>
</Button>
<TextBox Width="90" Height="15"/>
<Button BorderThickness="0">
<Button.Content>
<Border CornerRadius="18" BorderThickness="1" BorderBrush="#FFCFCFCF" >
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="10" Text="Apply" Foreground="#FFCFCFCF" FontWeight="Black"/>
</StackPanel>
</Border>
</Button.Content>
</Button>
</StackPanel>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- NOW, we can expand the custom properties-->
<Style TargetType="{x:Type myApp:MyControl}" BasedOn="{StaticResource MyControlStyle}" />
</Window.Resources>
<Grid Height="150">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<myApp:MyControl MyText1="First String" MyText2="Second String" MyText3="Third String"
Style="{StaticResource MyControlStyle}"/>
<myApp:MyControl MyText1="Another Line" MyText2="diff string" MyText3="all done" Grid.Row="1"/>
<myApp:MyControl MyText1="Another Line" MyText2="diff string" MyText3="all done" Grid.Row="2"
Style="{StaticResource MyControlStyle1}"/>
</Grid>
At the top within the main declaration, I added
xmlns:myApp="clr-namespace:StackOverflow"
this basically states that when within this xaml file, I see a prefix of "myApp", it is similar to a "using StackOverflow;" command as if in code. So now I have access to the custom class(es) or other things within that namespace to the xaml.
Next I start to declare my own "style" for the custom MyControl class via
<Window.Resources>
<Style TargetType="{x:Type myApp:MyControl}" x:Key="MyControlStyle1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type myApp:MyControl}" >
...
You might want to create a separate ResourceDictionary if you deal with many styles / templates used throughout your app. Notice the "Syle" and custom "ControlTemplate" are based on the "myApp:MyControl" class structure. Now, I can make use of my "MyText1", "MyText2", "MyText3" elements within the control template.
The x:Key="MyControlStyle1" is like creating a variable by given name so it can be used if you need to explicitly say which style to use. This first style is just to show the point that the 3 "MyText" properties are available and the Text is getting its value from the
Text="{TemplateBinding MyText1}"
Class that the control template is bound to (hence TemplateBinding).
Once you get the basics working, then you can glorify your template as you have with your nested stack panels which is the lower
<Style TargetType="{x:Type myApp:MyControl}" x:Key="MyControlStyle">
declaration by a different x:Key name.
Now, so you don't have to explicitly keep adding xaml for your control and say... by the way, use this explicit style of MyControlStyle, I have the following
<Style TargetType="{x:Type myApp:MyControl}" BasedOn="{StaticResource MyControlStyle}" />
indicating whenever you see a target type of "MyControl", default the style to the "MyControlStyle" so I don't have to keep remembering to do it.
Finally implementing its use. The end of the code has a simple Grid control with 3 rows.
<myApp:MyControl MyText1="First String" MyText2="Second String" MyText3="Third String"
Style="{StaticResource MyControlStyle}"/>
<myApp:MyControl MyText1="Another Line" MyText2="diff string" MyText3="all done" Grid.Row="1"/>
<myApp:MyControl MyText1="Another Line" MyText2="diff string" MyText3="all done" Grid.Row="2"
Style="{StaticResource MyControlStyle1}"/>
Notice the first instance I CAN explicitly declare the style to be used. The second has no explicit style as per the default, but the third instance explicitly states to use the simplified "MyControlStyle1" which was just the 3 textblocks side-by-side showing that you can have one class and make it look differently as needed.
Revision per questions/comments.
If you are building these controls based on a loop and dynamically adding them, you would just set the properties respectively in the code. Performance should not be significant because the CLASS is already declared, you are just adding one more into your list.
foreach( var oneThing in YourListOfToBeAddedItems )
{
var mc = new MyControl();
mc.MyText1 = oneThing.TextFieldUsedForField1;
mc.MyText2 = oneThing.FieldForSecondText;
mc.MyText3 = oneThing.ThirdTextBasisForDisplay;
// Now, add the "mc" to whatever your control is
// can't confirm this line below as I dont know context
// of your form and dynamic adding.
YourWindowGridOrOtherControl.Controls.Add( mc );
}
Also, since the default style was defined, I would not need to explicitly declare the "Style" for the control.
I have 3 screens
Mainwindow.xaml
<Window x:Class="PatientAdminTool.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Window.DataContext>
<vm:MainViewModel></vm:MainViewModel>
</Window.DataContext>
<WindowChrome.WindowChrome>
<WindowChrome
CaptionHeight="0"/>
</WindowChrome.WindowChrome>
<ContentControl >
<v:PatientWindow DataContext="{Binding PatientVM}"/>
</ContentControl>
</Window>
PatientWindow.xaml
<UserControl x:Class="PatientAdminTool.View.PatientWindow"
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:vm="clr-namespace:PatientAdminTool.ViewModel"
xmlns:v="clr-namespace:PatientAdminTool.View"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" >
<UserControl.DataContext>
<vm:PatientSelectorVM/>
</UserControl.DataContext>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" LastChildFill="True" Height="40" Background="#646161" >
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal">
<TextBlock Margin=" 10,5,0,0" HorizontalAlignment="Center" Text="Patients" FontSize="16" TextAlignment="Center" VerticalAlignment="Center" Foreground="#FFFFFF"/>
</StackPanel>
<StackPanel Margin="10,10,0,0" DockPanel.Dock="Right" Background="Transparent" Orientation="Vertical" HorizontalAlignment="Right" VerticalAlignment="Top" Width="50" >
<StackPanel Background="Transparent" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Focusable="False" ToolTip="Close" VerticalAlignment="Center" Background="#646161" BorderThickness="0" BorderBrush="Transparent" Padding="-4" Command="{Binding CloseCommand,Mode=TwoWay}">
<Button.Content>
<Grid Width="45" Height="23">
<TextBlock Foreground="White" Text="r" FontFamily="Marlett" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</Button.Content>
<Button.Template>
<ControlTemplate TargetType="Button">
<ContentPresenter Content="{TemplateBinding Content}"/>
</ControlTemplate>
</Button.Template>
</Button>
</StackPanel>
</StackPanel>
</DockPanel>
<ContentControl Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<v:PatientSelector DataContext="{Binding PatientSelectorVM}" />
</ContentControl>
</Grid>
</UserControl>
Result should be as per the below image
I need to show the child window by using corresponding view model.
I am not getting how to do this?
You can't access UI elements (including windows) in ViewModels directly.
Just make abstraction of the functionality "Open Window". The easiest way is to use interface:
interface IDialogService
{
public void OpenWindow(BaseViewModel windowViewModel);
}
//in viewmodel:
void OpenPatientWindow_CommandExecuted()
{
var patientWindowVM = new PatientWindowViewModel())
patientWindowVM.Parameter = "This way you can pass parameters";
_dialogService.OpenWindow(patientWindowVM);
}
go here for more inspiration: Handling Dialogs in WPF with MVVM
Bind the Content property of ContentPresenter to the PatientVM property of the MainViewModel in MainWindow.xaml and use define a DataTemplate per child view/view model type:
<ContentControl Content="{Binding PatientVM}">
<ContentControl.Resources>
<DataTemplate DataType="local:PatientSelectorVM">
<v:PatientWindow />
</DataTemplate>
<DataTemplate DataType="local:ThirdScreenType">
<v:ThirdScreenView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
You then set the PatientVM property of the MainViewModel to a PatientSelectorVM object to display the PatientWindow view and to a ThirdScreenType object (or whatever your class is actually called) to display the other view in the ContentControl.
Make sure that the MainViewModel implements the INotifyPropertyChanged interface and raise the PropertyChanged event in the setter of the PatientVM property:
public BaseViewModel PatientVM
{
get { return _patientVM; }
set
{
patientVM = value;
OnPropertyChanged();
}
}
For you to be able to set the property to both a PatientSelectorVM object and the other type of objects, these classes should derive from the same base class or implement the same interface.
I am trying to figure out how I can bind the ContextMenu of the Button that is being added in the ItemsControl I have. Basically, I'm wanting to be able to right click on a button and remove it from the observable collection that sits on my viewmodel. I understand that ContextMenu's are not part of the VisualTree, so using RelativeSource to walk up the tree to find my DataContext hasn't been useful to me.
The end goal of what I want to do is Bind the Command on the MenuItem to the RemoveCommand on my ViewModel and then pass in the Content property of the Button that you right click on so that I can remove it from the observable collection.
Any help on this would be greatly appreciated.
Model:
public class Preset
{
public string Name { get; set; }
}
ViewModel:
public class SettingsWindowViewModel
{
public ObservableCollection<Preset> MyPresets { get; } = new ObservableCollection<Preset>();
private ICommand _plusCommand;
public ICommand PlusCommand => _plusCommand ?? (_plusCommand = new DelegateCommand(AddPreset));
private ICommand _removeCommand;
public ICommand RemoveCommand => _removeCommand ?? (_removeCommand = new DelegateCommand<string>(RemovePreset));
private void AddPreset()
{
var count = MyPresets.Count;
MyPresets.Add(new Preset {Name = $"Preset{count+1}"});
}
private void RemovePreset(string name)
{
var preset = MyPresets.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.CurrentCultureIgnoreCase));
if (preset!= null)
{
MyPresets.Remove(preset);
}
}
}
XAML:
<Window x:Class="WpfTesting.Esper.Views.SettingsWindow"
x:Name="MainSettingsWindow"
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:viewModels="clr-namespace:WpfTesting.Esper.ViewModels"
mc:Ignorable="d"
Title="SettingsWindow" Height="470" Width="612">
<Window.DataContext>
<viewModels:SettingsWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<Style BasedOn="{StaticResource {x:Type MenuItem}}" TargetType="{x:Type MenuItem}" x:Key="PopupMenuItem">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MenuItem}">
<Border>
<ContentPresenter ContentSource="Header"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="35"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="2" Orientation="Horizontal">
<Button Width="70" Content="Load"/>
<Button Width="70" Content="Save As"/>
<ItemsControl ItemsSource="{Binding MyPresets}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="70" Content="{Binding Name}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Style="{StaticResource PopupMenuItem}" Header="Remove">
<!--
I need to set up binding a Command to a method on the DataContext of the Window, and I need to pass in the Content of the Button that is the parent of the ContextMenu
-->
</MenuItem>
</ContextMenu>
</Button.ContextMenu>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Width="20" Background="Transparent" BorderBrush="Transparent" Content="+" FontSize="21.333" HorizontalAlignment="Center" VerticalAlignment="Center" Command="{Binding PlusCommand}"/>
</StackPanel>
</Grid>
</Window>
Using WPF: Binding a ContextMenu to an MVVM Command as an introduction to what Tags can do, I figured out how to do what I was looking for by using multiple Tags to save the Context of what I was looking for.
I first made sure to give my window a x:Name
<Window x:Name="MainSettingsWindow"
Next, on the Button inside my DataTemplate of my ItemsControl, I set a Tag and set it to my Window
<ItemsControl ItemsSource="{Binding MyPresets}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="70" Content="{Binding Name}" Tag="{Binding ElementName=MainSettingsWindow}">
Next, in the ContextMenu, I seth the DataContext of the ContextMenu to the Tag I set on the Button, I also needed to create a Tag on the ContextMenu and point it back to the Content Property of the Button so that I can pass that into the CommandParameter
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=Self}}" Tag="{Binding PlacementTarget.Content, RelativeSource={RelativeSource Mode=Self}}">
At this point, I can now bind my MenuItem correctly using the Command from my ViewModel and the Content Property from the Button
This is the final XAML for my ItemsControl:
<ItemsControl ItemsSource="{Binding MyPresets}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="70" Content="{Binding Name}" Tag="{Binding ElementName=MainSettingsWindow}">
<Button.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=Self}}" Tag="{Binding PlacementTarget.Content, RelativeSource={RelativeSource Mode=Self}}">
<MenuItem Header="Remove"
Style="{StaticResource PopupMenuItem}"
Command="{Binding Path=DataContext.RemoveCommand}"
CommandParameter="{Binding Path=Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"/>
</ContextMenu>
</Button.ContextMenu>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
One thing to note is that I had to change the CommandParameter on my ViewModel to take an Object instead of a String. The reason I did this was because I was getting an exception on the CanExecute method in my DelegateCommand
This is the exception I was getting:
Unable to cast object of type 'MS.Internal.NamedObject' to type 'System.String'.
I'm not sure exactly what's causing that exception to throw, but changing it to Object works ok for me.
I had basically a similar problem, and the solution I found was to use the Messenger class some MVVM frameworks like Devexpress or Mvvm Light have.
Basically you can register in a viewModel to listen for incoming messages. The class itself, at least in the Devexpress implementation works with weak references, so you may not even unregister message handlers and it will not cause memory leaks.
I had used this method for removing on right click tabs from a ObservableCollection, so it was similar to your scenario.
You can have a look here :
https://community.devexpress.com/blogs/wpf/archive/2013/12/13/devexpress-mvvm-framework-interaction-of-viewmodels-messenger.aspx
and here :
https://msdn.microsoft.com/en-us/magazine/jj694937.aspx