wpf xaml MVVM inheritance with multiple ContentPresenter - c#

I am rewriting import masks that have a lot in common, so I want (and must) use inheritance.
I have a basic UserControl with all common controls: (I have left out the grid definitions)
BaseClass.xaml
<UserControl x:Class="BaseImport.BaseClass"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Grid>
<Border Grid.Row="0" Grid.Column="0">
<StackPanel>
<Label Content="Text1:"/>
<ComboBox Name="cbText1" MinWidth="80"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="1">
<StackPanel>
<Label Content="Text2:"/>
<ComboBox Name="cbText2" MinWidth="80"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="2">
<StackPanel>
<ContentPresenter ContentSource="Content"/> <!-- ContentSource="Content" is the default-->
</StackPanel>
</Border>
<!-- next Row -->
<Border Grid.Row="1" Grid.Column="0">
<StackPanel>
<Label Content="Text3:"/>
<TextBox Name="tbText3" TextWrapping="Wrap" Text="" MinWidth="80" VerticalAlignment="Center"/>
</StackPanel>
</Border>
<Border Grid.Row="1" Grid.Column="1">
<StackPanel>
<ContentPresenter/>
</StackPanel>
</Border>
</Grid>
</ControlTemplate>
</UserControl.Template>
</UserControl>
This is a kind of Template that gets "used" like this:
MainWindow.xaml (just for demonstration a mainwindow)
<Window x:Class="zzz.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:my="clr-namespace:BaseImport;assembly=BaseImport"
mc:Ignorable="d"
Title="MainWindow" Height="280" Width="600">
<my:BaseClass>
<StackPanel>
<Label Content="Test:"/>
<ComboBox ItemsSource="{Binding TestTyps}" MinWidth="80"/>
</StackPanel>
</my:BaseClass>
</Window>
MainWindow.xaml.cs
using WpfApp1.ViewModel;
namespace zzz
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
}
and to wrap it up MainViewModel.cs:
namespace WpfApp1.ViewModel
{
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public string[] TestTyps { get { return new string[] { "abc", "123", "xyz" }; } }
}
}
If I have one ContentPresenter everything works fine. But in the BaseClass I have two, potentially more.
Like this only the "last" Presenter gets populated. And in MainWindow.xaml can only be one declared.
How can I put more Content in MainWindow.xaml?
How can I select the right one?
Thanks
The red rectangle is were the second presenter is located (row 1, column 1) but I want it to be were the arrow points (row 0, column 2).
I want another control in place of the red rectangle also declared in MainWindow.xaml.

The stuff you put directly in the <my:BaseClass> tags is the contentProperty which can be only one.
Why only the last ContentPresenter shows it? Because each VisualElement can only have one parent, so the last one claiming it wins.
However you can create as many properties as you want.
<UserControl x:Class="BaseImport.BaseClass"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Grid>
<Border Grid.Row="0" Grid.Column="0">
<StackPanel>
<Label Content="Text1:"/>
<ComboBox Name="cbText1" MinWidth="80"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="1">
<StackPanel>
<Label Content="Text2:"/>
<ComboBox Name="cbText2" MinWidth="80"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="2">
<StackPanel>
<ContentPresenter ContentSource="FirstContent"/>
</StackPanel>
</Border>
<!-- next Row -->
<Border Grid.Row="1" Grid.Column="0">
<StackPanel>
<Label Content="Text3:"/>
<TextBox Name="tbText3" TextWrapping="Wrap" Text="" MinWidth="80" VerticalAlignment="Center"/>
</StackPanel>
</Border>
<Border Grid.Row="1" Grid.Column="1">
<StackPanel>
<ContentPresenter ContentSource="SecondContent"/>
</StackPanel>
</Border>
</Grid>
</ControlTemplate>
</UserControl.Template>
</UserControl>
<my:BaseClass>
<my:BaseClass.FirstContent>
<StackPanel>
<Label Content="Test:"/>
<ComboBox ItemsSource="{Binding TestTyps}" MinWidth="80"/>
</StackPanel>
<my:BaseClass.FirstContent>
<my:BaseClass.SecondContent>
<StackPanel>
<Label Content="Test10:"/>
<TextBox Text="{Binding Whatever}" MinWidth="80"/>
</StackPanel>
<my:BaseClass.SecondContent>
</my:BaseClass>

I gave the point to Firo, because he gave me the right answer. But I want to give some final thoughts, so everyone can benefit from it.
For the xaml "base-class":
<UserControl
x:Class="BaseImport.BaseClass"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Grid>
<Border Grid.Row="0" Grid.Column="0">
<StackPanel>
<Label Content="Text1:"/>
<ComboBox Name="cbText1" MinWidth="80"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="1">
<StackPanel>
<Label Content="DB:"/>
<ComboBox
Name="cbxDB"
ItemsSource="{Binding DBs}"
SelectedItem="{Binding DB}"
MinWidth="80"
SelectionChanged="selectionChanged"
Loaded="DB_Loaded">
<!--
here the DataContext can be set differently
<ComboBox.DataContext>
<Views:DBViewModel/>
</ComboBox.DataContext>
-->
</ComboBox>
</StackPanel>
</Border>
<!-- the content placeholder -->
<Border Grid.Row="0" Grid.Column="2">
<StackPanel MinWidth="80">
<ContentControl
Content="{Binding ContentOne}"
ContentTemplate="{Binding ContentOneTemplate}"
ContentTemplateSelector="{Binding ContentOneTemplateSelector}"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="3">
<StackPanel MinWidth="80">
<ContentControl
Content="{Binding ContentTwo}"
ContentTemplate="{Binding ContentTwoTemplate}"
ContentTemplateSelector="{Binding ContentTwoTemplateSelector}"/>
</StackPanel>
</Border>
</Grid>
</ControlTemplate>
</UserControl.Template>
</UserControl>
For the "base-class":
public partial class BaseClass : UserControl
{
public BaseClass(BaseViewModel _bvm)
{
InitializeComponent();
// set DataContext for all other controls
DataContext = _bvm;
}
#region DependencyProperty
public static readonly DependencyProperty ContentOneProperty = DependencyProperty.Register("ContentOne", typeof(object), typeof(BaseImportClass));
public static readonly DependencyProperty ContentOneTemplateProperty = DependencyProperty.Register("ContentOneTemplate", typeof(DataTemplate), typeof(BaseImportClass));
public static readonly DependencyProperty ContentOneTemplateSelectorProperty = DependencyProperty.Register("ContentOneTemplateSelector", typeof(DataTemplateSelector), typeof(BaseImportClass));
public static readonly DependencyProperty ContentTwoProperty = DependencyProperty.Register("ContentTwo", typeof(object), typeof(BaseImportClass));
public static readonly DependencyProperty ContentTwoTemplateProperty = DependencyProperty.Register("ContentTwoTemplate", typeof(DataTemplate), typeof(BaseImportClass));
public static readonly DependencyProperty ContentTwoTemplateSelectorProperty = DependencyProperty.Register("ContentTwoTemplateSelector", typeof(DataTemplateSelector), typeof(BaseImportClass));
public object ContentOne
{
get { return GetValue(ContentOneProperty); }
set { SetValue(ContentOneProperty, value); }
}
public DataTemplate ContentOneTemplate
{
get { return (DataTemplate)GetValue(ContentOneTemplateProperty); }
set { SetValue(ContentOneTemplateProperty, value); }
}
public DataTemplateSelector ContentOneTemplateSelector
{
get { return (DataTemplateSelector)GetValue(ContentOneTemplateSelectorProperty); }
set { SetValue(ContentOneTemplateSelectorProperty, value); }
}
public object ContentTwo
{
get { return GetValue(ContentTwoProperty); }
set { SetValue(ContentTwoProperty, value); }
}
public DataTemplate ContentTwoTemplate
{
get { return (DataTemplate)GetValue(ContentTwoTemplateProperty); }
set { SetValue(ContentTwoTemplateProperty, value); }
}
public DataTemplateSelector ContentTwoTemplateSelector
{
get { return (DataTemplateSelector)GetValue(ContentTwoTemplateSelectorProperty); }
set { SetValue(ContentTwoTemplateSelectorProperty, value); }
}
#endregion
}
For the "derived" class xaml:
<UserControl x:Class="WpfApp1.DerivedClass"
xmlns:base="clr-namespace:BaseImport.Views;assembly=BaseImport"
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">
<StackPanel>
<base:BaseClass Name="bc">
<base:BaseClass.ContentOneTemplate>
<DataTemplate>
<StackPanel>
<Label Content="Test:"/>
<ComboBox
ItemsSource="{Binding TestTyps}"
SelectedItem="{Binding TestTyp}"
SelectionChanged="TestTypChanged"
Loaded="TestTypLoaded">
<!--
here the DataContext can be set differently
<ComboBox.DataContext>
<Views:ViewModelXYZ/>
</ComboBox.DataContext>
-->
</ComboBox>
</StackPanel>
</DataTemplate>
</base:BaseClass.ContentOneTemplate>
</base:BaseClass>
</StackPanel>
</UserControl>
For the "derived" class:
public partial class DerivedClass : UserControl
{
ViewModelABC vmabc = null;
public DerivedClass(ViewModel _vm) : this()
{
vmabc = _vm;
this.DataContext = vmabc;
bc.DataContext = vmabc; // or another viewmodel that holds TestTyps and TestTyp or leave it for ViewModelXYZ
}
private void TestTypChanged(object sender, SelectionChangedEventArgs e)
{
PropertyChanged();
}
private void TestTypLoaded(object sender, RoutedEventArgs e)
{
(sender as ComboBox).SelectedValue = (this.DataContext as ViewModel(XYZ/ABC).TestTyp;
}
}
That is what I came up.
I hope that helps others.

Related

How can I use DelegateCommand correctly?

In the debug mode, goes from constructor to the set method, but in the get method does not enter at any point, which I think is the problem, but I don't know how to solved.
In the .xaml file I defined the button.
<dx:ThemedWindow
x:Class="ProductsModule.View.ProductView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
Title="MenuView" Height="800" Width="1000">
<Grid Margin="20" VerticalAlignment="Top" Background="White" Height="420" DataContext="{Binding Detail}">
<Grid.Effect>
<DropShadowEffect BlurRadius="20" ShadowDepth="1"/>
</Grid.Effect>
<StackPanel Margin="750 70 70 70" HorizontalAlignment="Left">
<TextBlock Text="{Binding Name}" FontSize="18" Margin="0 5" Foreground="#FF6A6A6A"/>
<Button Command="{Binding ShowCommand }">Add data</Button>
</StackPanel>
</Grid>
</dx:ThemedWindow>
In .xaml.cs file I defined datacontext.
public SecondView()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
VIEW MODEL
public ViewModel()
{
ShowCommand = new DelegateCommand(ShowMethod);
}
public ICommand ShowCommand
{
get;
set;
}
private void ShowMethod()
{
OrderView orderView = new OrderView();
orderView.Show();
}
When I press the button, ShowMethod() is not called and does not do anything.

Nested UserControl event doesn't work with EventTrigger/InvokeCommandAction in MVVM/WPF scenario

I'm working with WPF with Prism (MVVM), and trying to build an Inspector for a few classes. One of those classes is Vector3:
<Grid x:Name="Vector3Root" Background="White">
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal">
<xctk:DoubleUpDown Tag="X" Style="{StaticResource DoubleUpDownStyle}" Value="{Binding X}" ValueChanged="Vector3ValueChanged"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<xctk:DoubleUpDown Tag="Y" Style="{StaticResource DoubleUpDownStyle}" Value="{Binding Y}" ValueChanged="Vector3ValueChanged"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<xctk:DoubleUpDown Tag="Z" Style="{StaticResource DoubleUpDownStyle}" Value="{Binding Z}" ValueChanged="Vector3ValueChanged"/>
</StackPanel>
</StackPanel>
</Grid>
And its code-behind
namespace SimROV.WPF.Views{
public partial class Vector3View : UserControl
{
public Vector3View()
{
InitializeComponent();
}
public static readonly RoutedEvent SettingConfirmedEvent =
EventManager.RegisterRoutedEvent("SettingConfirmed", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(Vector3View));
public event RoutedEventHandler SettingConfirmed
{
add { AddHandler(SettingConfirmedEvent, value); }
remove { RemoveHandler(SettingConfirmedEvent, value); }
}
public void Vector3ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
RaiseEvent(new RoutedEventArgs(SettingConfirmedEvent));
}
}}
The problem that I'm struggling with is that I can't catch neither of the fired events (ValueChanged or SettingConfirmed) on another UserControl's ViewModel that is using Vector3View:
<UserControl
x:Class="SimROV.WPF.Views.TransformView"
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:views="clr-namespace:SimROV.WPF.Views"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:prism="http://prismlibrary.com/"
mc:Ignorable="d" >
<Grid x:Name="TransformRoot" Background="White">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Position" Margin="5"/>
<!--<ContentPresenter ContentTemplate="{StaticResource Vector3Template}"/>-->
<views:Vector3View x:Name="PositionVector3">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SettingConfirmed">
<prism:InvokeCommandAction Command="{Binding PositionValueChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</views:Vector3View>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Rotation" Margin="5"/>
<!--<ContentPresenter ContentTemplate="{StaticResource Vector3Template}"/>-->
<views:Vector3View x:Name="RotationVector3" SettingConfirmed="RotationValueChangedEvent"/>
</StackPanel>
</StackPanel>
</Grid>
At this point I CAN catch SettingConfirmed with RotationValueChangedEvent on code-behind, but since I'm following MVVM pattern, that doesn't work for me, which is why I'm using EventTrigger and InvokeCommandAction to catch those events on TransformViewModel, but those never get fired.
Here it's the TransformViewModel:
namespace SimROV.WPF.ViewModels{
public class TransformViewModel : BindableBase
{
private ICommand _positionCommand;
public ICommand PositionValueChangedCommand => this._positionCommand ?? (this._positionCommand = new DelegateCommand(PositionChanged));
private void PositionChanged()
{
}
public TransformViewModel()
{
}
}}
PositionChanged just never gets fired and I can't understand why at all.
I don't know if this is relevant, but Transform is an element of an ObservableCollection<IComponent> at another ViewModel, which is being presented by a ListView with a ItemContainerStyle, that has a ContentPresenter with a ContentTemplateSelector inside.
Can someone point me out on why this is happening and how to fix it?
Thank you.
Your EventTrigger and InvokeCommandAction should work just fine provided that the DataContext of the Vector3View actually is a TransformViewModel so the binding to the PositionValueChangedCommand property succeeds.

Fill Stackpanel inside Usercontrol

I'm searching for hours now but could not find the correct way how to do that.
I build a UserControl "MyToolbarGroup" having a GroupText and an empty Stackpanel inside.
Now I want to use the control MyToolbarGroup on my other UserControl "MyUserControl" and create some Buttons inside the Stackpanel of the MyToolbarGroup control.
MyToolbarGroup-XAML
<UserControl x:Class="TestUi.MyToolbarGroup"
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"
mc:Ignorable="d" d:DesignWidth="153" d:DesignHeight="103">
<Grid>
<Border BorderBrush="Black" BorderThickness="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Center" Margin="5,5,5,5" />
<Label Grid.Row="1" HorizontalContentAlignment="Center" Content="{Binding Path=GroupText}" Background="LightBlue" />
</Grid>
</Border>
</Grid>
</UserControl>
MyToolbarGroup-Code
public partial class MyToolbarGroup : UserControl
{
public static readonly DependencyProperty GroupTextProperty = DependencyProperty.Register("GroupText", typeof(string), typeof(MyToolbarGroup));
public String GroupText
{
get { return (String)GetValue(GroupTextProperty); }
set { SetValue(GroupTextProperty, value); }
}
public MyToolbarGroup()
{
InitializeComponent();
DataContext = this;
}
}
MyUserControl-XAML
<UserControl
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:TestUi" x:Class="TestUi.MyUserControl"
mc:Ignorable="d"
d:DesignHeight="224" d:DesignWidth="343">
<Grid>
<local:MyToolbarGroup HorizontalAlignment="Left" Margin="40,30,0,0" VerticalAlignment="Top" Height="148" Width="238" GroupText="Test-Group">
<!-- Something like that
<Stackpanel-Inside-MyToolbarGroup>
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Height="65" Width="65" Content="Button 1"/>
</Stackpanel-Inside-MyToolbarGroup>
-->
</local:MyToolbarGroup>
</Grid>
</UserControl>
Any ideas how to set some buttons inside the stackpanel?
Thanx for any help!
include a ContentPresenter in MyToolbarGroup template to accept user Content
<UserControl x:Class="TestUi.MyToolbarGroup"
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:testUi="clr-namespace:TestUi"
mc:Ignorable="d" d:DesignWidth="153" d:DesignHeight="103">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Grid>
<Border BorderBrush="Black" BorderThickness="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<ContentPresenter Content="{TemplateBinding Content}"/>
<Label Grid.Row="1" HorizontalContentAlignment="Center"
Content="{Binding GroupText, RelativeSource={RelativeSource AncestorType=testUi:MyToolbarGroup}}"
Background="LightBlue" />
</Grid>
</Border>
</Grid>
</ControlTemplate>
</UserControl.Template>
</UserControl>
and then use it like this:
<testUi:MyToolbarGroup Grid.Row="2"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Height="148" Width="238" GroupText="Test-Group">
<!--any content, e.g. -->
<StackPanel Orientation="Horizontal">
<TextBlock Text="A" Margin="5"/>
<TextBlock Text="B" Margin="5"/>
</StackPanel>
</testUi:MyToolbarGroup>
result:
A stack panel doesn't have the functionality you want (if I understand you correctly). What you want is a dynamic collection of controls based on some data right?
What you need is an ItemsControl:
<ItemsControl ItemsSource="{Binding MyButtonDataCollection}" >
<ItemsControl.ItemTemplate >
<DataTemplate >
<Button Content="{Binding ButtonName}"
Command="{Binding ButtonClick}"
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
bound to a collection of data objects that have the properties and commands you want to bind to:
class MyButtonData : INotifyPropertyChanged
{
String ButtonName { get; set; } //notify property changed and all that
public ICommand ButtonClick
{
get;
internal set;
}
private bool CanExecuteButtonClick()
{
//can this execute?
return true;
}
private void CreateButtonClick()
{
ButtonClick = new RelayCommand(SaveExecute, CanExecuteSaveCommand);
}
public void ButtonClickExecute()
{
//do my logic for click
}
}
If you want layout inside MyToolbarGroup to be predefined, you need to replace StackPanel with ItemsControl having StackPanel as ItemsPanel.
Here's MyToolbarGroup XAML:
<UserControl x:Class="WpfApplication2.MyToolbarGroup"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2">
<Border BorderBrush="Black" BorderThickness="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<ItemsControl Grid.Row="0" HorizontalAlignment="Center" Margin="5,5,5,5"
ItemsSource="{Binding GroupItems, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<Label Grid.Row="1" HorizontalContentAlignment="Center"
Content="{Binding GroupText, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
Background="LightBlue" />
</Grid>
</Border>
</UserControl>
and code-behind:
public partial class MyToolbarGroup : UserControl
{
public MyToolbarGroup()
{
InitializeComponent();
}
public string GroupText
{
get { return (string)GetValue(GroupTextProperty); }
set { SetValue(GroupTextProperty, value); }
}
public static readonly DependencyProperty GroupTextProperty =
DependencyProperty.Register("GroupText", typeof(string), typeof(MyToolbarGroup), new PropertyMetadata(null));
public IEnumerable GroupItems
{
get { return (IEnumerable)GetValue(GroupItemsProperty); }
set { SetValue(GroupItemsProperty, value); }
}
public static readonly DependencyProperty GroupItemsProperty =
DependencyProperty.Register("GroupItems", typeof(IEnumerable), typeof(MyToolbarGroup), new PropertyMetadata(null));
}
Now, you can use it like this:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow">
<Grid>
<local:MyToolbarGroup HorizontalAlignment="Left" Margin="40,30,0,0" VerticalAlignment="Top" Height="148" Width="238" GroupText="Test-Group">
<local:MyToolbarGroup.GroupItems>
<x:Array Type="{x:Type sys:Object}">
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Height="65" Width="65" Content="Button 1"/>
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Height="65" Width="65" Content="Button 2"/>
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Height="65" Width="65" Content="Button 3"/>
</x:Array>
</local:MyToolbarGroup.GroupItems>
</local:MyToolbarGroup>
</Grid>
</Window>
Screenshot of result:

DataTemplate and ContentControl when user class as DataContext

What I have:
User class
public class MyButton
{
public String ButtonProperty { get; set; }
public String LabelProperty { get; set; }
public MyButton()
{
ButtonProperty = "MyButtonText!";
LabelProperty = "LabelText!";
}
}
DataTemplate defined in window resources
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyButton}">
<Border Width="100" Height="100" BorderThickness="2" BorderBrush="Aquamarine">
<StackPanel >
<Button>
<TextBlock Text="{Binding ButtonProperty}"></TextBlock>
</Button>
<Label Content="{Binding LabelProperty}"></Label>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
I want to DataTemplate will draw instead of instance of MyButton class
<Window x:Class="WpfApplication7.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication7"
Title="MainWindow" Height="500" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyButton}">
<Border Width="100" Height="100" BorderThickness="2" BorderBrush="Aquamarine">
<StackPanel >
<Button>
<TextBlock Text="{Binding ButtonProperty}">
</TextBlock>
</Button>
<Label Content="{Binding LabelProperty}">
</Label>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<!-- Create instance of MyButton in XAML-->
<local:MyButton></local:MyButton>
</Window>
It works fine, but it is not what I want at the end. What if instance of MyButton will DataContext for Window?
public MainWindow()
{
//Set instance of MyButton as DataContext
DataContext = new MyButton();
InitializeComponent();
}
I thought I must write that in XAML-side
<ContentControl DataContext="{Binding}">
<!--MyButton XAML code from DataTemplate here -->
</ContentControl>
instead of
<local:MyButton></local:MyButton>
but it doesn't work at all. what I am doing wrong?
You should try to bind to the Content property of your ContentControl instead of the DataContext property :
<ContentControl Content={Binding } />
Besides, the DataContext of the ContentControl is already the MyButton.
I'm not really sure what are you trying to achieve there. IF you simply want to extend the functionality of the default Button you could define attached properties
Why would you want the DataContext of your Window to be the Button? Maybe the other way around? Not sure I understood that part correctly.

User control with custom properties

I'm trying to create a UserControl which is a legend of a graph item,
I've defined it so it has a label with the graph name, a check box which
define whether we show it or not and a rectangle with the graph color.
The xaml is defined like this:
<UserControl x:Class="Resources.LegendItem"
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"
mc:Ignorable="d">
<UserControl.Template>
<ControlTemplate>
<StackPanel Margin="1,0,0,0" Orientation="Horizontal">
<CheckBox Name="cb" IsChecked="{TemplateBinding IsGraphVisible}" >
<StackPanel Margin="1,0,0,0" Orientation="Horizontal">
<Rectangle Name="rec" RadiusX="2" RadiusY="2" Height="10" Width="10" />
<Label Name="lab" Content="{TemplateBinding GraphName}" />
</StackPanel>
</CheckBox>
</StackPanel>
</ControlTemplate>
</UserControl.Template>
</UserControl>
and the cs file is:
namespace Resources
{
public partial class LegendItem : UserControl
{
public static readonly DependencyProperty IsGraphVisibleProperty = DependencyProperty.Register("IsGraphVisible", typeof(Boolean), typeof(LegendItem));
public static readonly DependencyProperty GraphNameProperty = DependencyProperty.Register("GraphName", typeof(String), typeof(LegendItem));
public bool IsGraphVisible
{
get { return (bool)GetValue(IsGraphVisibleProperty); }
set { SetValue(IsGraphVisibleProperty, value); }
}
public string GraphName
{
get { return (string)GetValue(GraphNameProperty); }
set { SetValue(GraphNameProperty, value); }
}
public LegendItem()
{
InitializeComponent();
}
}
}
But when I compile it, I get an error "Cannot find the static member 'IsGraphVisibleProperty' on the type 'Control'."
Any help would be appreciated.
You do not need a template at all. UserControl allows the XAML to be declared directly. You can't set the template in a UserControl:
<UserControl x:Class="Resources.LegendItem" x:Name="MyControl"
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"
mc:Ignorable="d">
<StackPanel Margin="1,0,0,0" Orientation="Horizontal">
<CheckBox Name="cb" IsChecked="{Binding ElementName=MyControl, Path=IsGraphVisible}" >
<StackPanel Margin="1,0,0,0" Orientation="Horizontal">
<Rectangle Name="rec" RadiusX="2" RadiusY="2" Height="10" Width="10" />
<Label Name="lab" Content="{Binding ElementName=MyControl, Path=GraphName}" />
</StackPanel>
</CheckBox>
</StackPanel>
</UserControl>
You need to specify TargetType="{x:Type Resources.LegendItem}" on your ControlTemplate, otherwise it defaults to being a template for Control and you get that error.

Categories