How to align controls in WPF custom user controls? - wpf

So I programmed a WPF custom user control like this:
XAML:
<UserControl x:Class="PEINC.LableWithText"
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:PEINC"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" x:Name="label" Content="{Binding Beschriftung, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<TextBox Grid.Column="1" x:Name="feld"/>
</Grid>
</UserControl>
cs:
public partial class LableWithText : UserControl
{
public string Beschriftung
{
get => (string)GetValue(BeschriftungProperty);
set => SetValue(BeschriftungProperty, value);
}
public static readonly DependencyProperty BeschriftungProperty = DependencyProperty.Register(nameof(Beschriftung), typeof(string), typeof(LableWithText), new PropertyMetadata(string.Empty));
public LableWithText()
{
InitializeComponent();
}
}
I use them in solutionDlg.xaml like this:
<control:LableWithText Grid.Row="2" Beschriftung="Bezeichnung:"/>
<control:LableWithText Grid.Row="3" Beschriftung="Projektnummer:"/>
My problem is that the TextBoxes don't align. It looks like this:
But it should look like this:
It would be cool if the user control would be able to adjust onto a grid - or some other kind of reference line - which is provided by solutionDlg.xaml.
I tried:
I could work with a fixed Width for the column the label is in. This is my fallback option, but it removes a lot of the flexibility of my custom control + I have to set it for every instance of LableWithText.
I tried to work with Grid.ColumnSpan, but this didn't work

I worked with the SharedSizeGroup-Feature:
This is the xaml I used in my UserControl:
<UserControl x:Class="RadBaseGUIElements.LabelWithText"
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:RadBaseGUIElements"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="{Binding LabelSizeGroup, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" x:Name="label" Content="{Binding Beschriftung, RelativeSource={RelativeSource AncestorType=UserControl}}" Style="{DynamicResource FeldLabel}"/>
<TextBox Grid.Column="1" x:Name="feld" Style="{DynamicResource Feld}" Text="{Binding Inhalt, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</Grid>
</UserControl>
And this is the code I use:
public partial class LabelWithText : UserControl
{
public string Beschriftung
{
get => (string)GetValue(BeschriftungProperty);
set => SetValue(BeschriftungProperty, value);
}
public static readonly DependencyProperty BeschriftungProperty = DependencyProperty.Register(nameof(Beschriftung),
typeof(string),
typeof(LabelWithText),
new PropertyMetadata("Beschriftung:"));
public string Inhalt
{
get => (string)GetValue(InhaltProperty);
set => SetValue(InhaltProperty, value);
}
public static readonly DependencyProperty InhaltProperty = DependencyProperty.Register(nameof(Inhalt),
typeof(string),
typeof(LabelWithText),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string LabelSizeGroup
{
get => (string)GetValue(LabelSizeGroupProperty);
set => SetValue(LabelSizeGroupProperty, value);
}
public static readonly DependencyProperty LabelSizeGroupProperty = DependencyProperty.Register(nameof(LabelSizeGroup),
typeof(string),
typeof(LabelWithText),
new PropertyMetadata("Labelspalte"));
public LabelWithText()
{
LabelSizeGroup = "Labelspalte";
InitializeComponent();
}
}
So I can use my User Control like this:
<Grid Grid.IsSharedSizeScope="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" SharedSizeGroup="Labelspalte"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
...
<control:LabelWithText Grid.Row="1" Beschriftung="SVN-Trunk-Pfad:" Grid.ColumnSpan="2" Inhalt="{Binding SvnTrunkFolder}"/>
</Grid>

Related

WPF EventHandler for TextBox.TextChanged in XAML or code behind?

Description
In WPF, using MvvmLight, I have a viewModel with an integer property SelectedIndex. Changing the value of this property is an expensive operation, so I only want to update the property if the operator is fairly certain that he finished typing.
I have a TextBox and a button. The operator types a number, and presses the button. This should lead to a command that updates the property.
Standard WPF MvvmLight solution for this
class MyViewModel
{
private int selectedIndex;
public MyViewModel()
{
this.CommandSelectIndex = new RelayCommand(ExecuteSelectIndex, CanSelectIndex);
}
public public RelayCommand<int> CommandSelectIndex { get; }
public int SelectedIndex
{
get => this.selectedIndex;
set => base.Set(nameof(SelectedIndex), ref this.selectedIndex, value);
}
private bool CanSelectIndex(int proposedIndex)
{
return proposedIndex > 0 && proposedIndex < MyData.Count;
}
private void ExecuteSelectIndex(int proposedIndex)
{
this.SelectedIndex = proposedIndex;
ProcessSelectedIndex(proposedIndex); // Expensive!
}
}
For those who know MvvmLight, this is fairly straightforward.
So while the operator is typing a number, I only want to update the button. I don't want to do anything with the intermediate values:
1 --> 12 --> 123 --> (typing error, backspace) --> 124 [press button]
XAML
<StackPanel Name="Test1" Orientation="Horizontal">
<TextBox Name="ProposedValue1" Text="1234" Width="300" Height="20"/>
<Button x:Name="ButtonChangeText1" Content="Change"
Height="30" Width="74" Padding="5,2"
Command="{Binding Path=CommandSelectedIndex}"
CommandParameter="{Binding ElementName=ProposedValue1, Path=Text}"/>
</StackPanel>
This works partly: at startup CanSelectIndex(1234) is called; If the button is pressed ExecuteSelectedIndex(1234) is called.
Problem
However, if the text of the TextBox changes, CanSelectIndex is not called.
The reason is because event ICommand.CanExecuteChanged is not raised when the textbox changes.
Solution:
Add an event handler:
XAML:
<TextBox Name="ProposedValue1" Text="1234" Width="300" Height="20"
TextChanged="textChangedEventHandler"/>
Code behind:
private void textChangedEventHandler(object sender, TextChangedEventArgs args)
{
((MyViewModel)this.DataContext).CommandSelectedIndex.RaiseCanExecuteChanged();
}
I always feel a bit uneasy whenever I have to write code behind. Is it standard to write eventhandlers in code behind, or is that a simplification that I only see in tutorials.
Is there a method that I can do this in XAML? Something with Binding?
TextChanged="TextChanged="{Binding Path=CommandSelectIndex ??? RaiseCanExecuteChanged() }
The RelayCommand class in MvvmLight has two implementations.
In the GalaSoft.MvvmLight.Command namespace and in the GalaSoft.MvvmLight.CommandWpf namespace.
You've probably used from namespace GalaSoft.MvvmLight.Command.
And this type doesn't actually update the state of the command.
If used from the GalaSoft.MvvmLight.CommandWpf namespace, then the state of the command is updated according to the predetermined logic.
Is there a method that I can do this in XAML? Something with Binding?
Just bind the Text property of the TextBox to a string source property of the view model and raise call the RaiseCanExecuteChanged method of the command from the setter of this one.
If you really want to handle an actual event for some reason, you should look into interaction triggers.
#Harald Coppulse, you are absolutely right!
Here is my test code for MvvmLight.
ViewModel:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
namespace InvalidateCommandMvvmLight.ViewModel
{
public class MyViewModel : ViewModelBase
{
private string _text;
private int _number;
public string Text { get => _text; private set => Set(ref _text, value); }
public int Number { get => _number; set => Set(ref _number, value); }
public RelayCommand<string> CommandTest { get; }
public RelayCommand<int> CommandNumber { get; }
public MyViewModel()
{
CommandTest = new RelayCommand<string>(Test, CanTest);
CommandNumber = new RelayCommand<int>(IntTest, CanIntTest);
}
private bool CanTest(string text)
{
// the text must have a minimum length of 4
// and be different from the current one
return text != null && text.Length >= 4 && text != Text;
}
private void Test(string text)
{
Text = text;
}
private bool CanIntTest(int num)
{
// The "num" parameter must be positive, less than 100
// and is not equal to the Number property
return num > 0 && num <100 && num != Number;
}
private void IntTest(int num)
{
Number = num;
}
}
}
XAML:
<Window x:Class="InvalidateCommandMvvmLight.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:InvalidateCommandMvvmLight"
xmlns:vm="clr-namespace:InvalidateCommandMvvmLight.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MyViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox x:Name="tbText"
Text="Alle eendjes zwemmen in het water" VerticalAlignment="Center"
/>
<Button Content="Change Text"
Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandTest}"
CommandParameter="{Binding ElementName=tbText, Path=Text}"/>
<TextBox Text="{Binding Text, Mode=OneWay}" Grid.Column="2" IsReadOnly="True" VerticalAlignment="Center"/>
<TextBox x:Name="tbNumber"
Grid.Row="1"
Text="55" VerticalAlignment="Center"/>
<Button Content="Change Number"
Grid.Row="1" Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandNumber}"
CommandParameter="{Binding ElementName=tbNumber, Path=Text}"/>
<TextBox Text="{Binding Number, Mode=OneWay}" IsReadOnly="True"
Grid.Row="1" Grid.Column="2" VerticalAlignment="Center"/>
</Grid>
</Window>
Unfortunately, the CommandsWpf.RelayCommand class in MvvmLight is implemented not correctly.
It does not take into account the peculiarities of working with values ​​of different types in WPF.
To work in a typical for WPF way, an implementation should have something like this:
using System.ComponentModel;
namespace Common
{
#region Delegates for WPF Command Methods
/// <summary>Delegate of the executive team method.</summary>
/// <param name="parameter">Command parameter.</param>
public delegate void ExecuteHandler<T>(T parameter);
/// <summary>Command сan execute method delegate.</summary>
/// <param name="parameter">Command parameter.</param>
/// <returns><see langword="true"/> if command execution is allowed.</returns>
public delegate bool CanExecuteHandler<T>(T parameter);
#endregion
/// <summary>Class for typed parameter commands.</summary>
public class RelayCommand<T> : RelayCommand
{
/// <summary>Command constructor.</summary>
/// <param name="execute">Executable command method.</param>
/// <param name="canExecute">Method allowing command execution.</param>
public RelayCommand(ExecuteHandler<T> execute, CanExecuteHandler<T> canExecute = null)
: base
(
p => execute(TypeDescriptor.GetConverter(typeof(T)).IsValid(p) ? (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(p) : default),
p => (canExecute == null) || (TypeDescriptor.GetConverter(typeof(T)).IsValid(p) && canExecute((T)TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(p)))
)
{}
}
}
Unless you have the ability to change the RelayCommand implementation, you need to somehow use Binding's ability to auto-convert values.
One variant.
Create a property of the desired type in the ViewModel and use it as a proxy for autoconversion.
But if a non-numeric value is entered, then the command will not be able to define it.
You also need to check Validation.HasError.
ViewModel:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
namespace InvalidateCommandMvvmLight.ViewModel
{
public class MyViewModel : ViewModelBase
{
private string _text;
private int _number;
private int _numberView;
public string Text { get => _text; private set => Set(ref _text, value); }
public int Number { get => _number; set => Set(ref _number, value); }
public int NumberView { get => _numberView; set => Set(ref _numberView, value); }
public RelayCommand<string> CommandTest { get; }
public RelayCommand<int> CommandNumber { get; }
public MyViewModel()
{
CommandTest = new RelayCommand<string>(Test, CanTest);
CommandNumber = new RelayCommand<int>(IntTest, CanIntTest);
}
private bool CanTest(string text)
{
// the text must have a minimum length of 4
// and be different from the current one
return text != null && text.Length >= 4 && text != Text;
}
private void Test(string text)
{
Text = text;
}
private bool CanIntTest(int num)
{
// The "num" parameter must be positive, less than 100
// and is not equal to the Number property
return num > 0 && num <100 && num != Number;
}
private void IntTest(int num)
{
Number = num;
}
}
}
XAML:
<Window x:Class="InvalidateCommandMvvmLight.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:InvalidateCommandMvvmLight"
xmlns:vm="clr-namespace:InvalidateCommandMvvmLight.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MyViewModel NumberView="55"/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox x:Name="tbText"
Text="Alle eendjes zwemmen in het water" VerticalAlignment="Center"
/>
<Button Content="Change Text"
Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandTest}"
CommandParameter="{Binding ElementName=tbText, Path=Text}"/>
<TextBox Text="{Binding Text, Mode=OneWay}" Grid.Column="2" IsReadOnly="True" VerticalAlignment="Center"/>
<TextBox x:Name="tbNumber"
Grid.Row="1"
Text="{Binding NumberView, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center"/>
<Button Content="Change Number"
Grid.Row="1" Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandNumber}"
CommandParameter="{Binding NumberView}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=tbNumber}"
Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<TextBox Text="{Binding Number, Mode=OneWay}" IsReadOnly="True"
Grid.Row="1" Grid.Column="2" VerticalAlignment="Center"/>
</Grid>
</Window>
Second variant.
Create an explicit proxy converter.
Converter:
using System;
using System.ComponentModel;
using System.Windows;
namespace InvalidateCommandMvvmLight
{
public class ProxyBinding : Freezable
{
public Type Type
{
get { return (Type)GetValue(TypeProperty); }
set { SetValue(TypeProperty, value); }
}
// Using a DependencyProperty as the backing store for Type. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TypeProperty =
DependencyProperty.Register(nameof(Type), typeof(Type), typeof(ProxyBinding), new PropertyMetadata(typeof(object), ChangedValueOrType));
private static void ChangedValueOrType(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ProxyBinding proxy = (ProxyBinding)d;
if (proxy.Type == null)
{
proxy.Value = null;
return;
}
if (proxy.Source == null)
return;
if (proxy.Type == proxy.Source.GetType())
return;
if (TypeDescriptor.GetConverter(proxy.Type).IsValid(proxy.Source))
proxy.Value = TypeDescriptor.GetConverter(proxy.Type).ConvertFrom(proxy.Source);
else
proxy.Value = null;
}
public object Source
{
get { return GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register(nameof(Source), typeof(object), typeof(ProxyBinding), new PropertyMetadata(null, ChangedValueOrType));
public object Value
{
get { return GetValue(ValueProperty); }
protected set { SetValue(ValuePropertyKey, value); }
}
// Using a DependencyProperty as the backing store for readonly Value. This enables animation, styling, binding, etc...
protected static readonly DependencyPropertyKey ValuePropertyKey =
DependencyProperty.RegisterReadOnly(nameof(Value), typeof(object), typeof(ProxyBinding), new PropertyMetadata(null));
public static readonly DependencyProperty ValueProperty = ValuePropertyKey.DependencyProperty;
protected override Freezable CreateInstanceCore()
{
return new ProxyBinding();
}
}
}
XAML:
<Window x:Class="InvalidateCommandMvvmLight.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:InvalidateCommandMvvmLight"
xmlns:vm="clr-namespace:InvalidateCommandMvvmLight.ViewModel"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MyViewModel/>
</Window.DataContext>
<Window.Resources>
<local:ProxyBinding x:Key="ProxyInt"
Type="{x:Type sys:Int32}"
Source="{Binding ElementName=tbNumber, Path=Text, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox x:Name="tbText"
Text="Alle eendjes zwemmen in het water" VerticalAlignment="Center"
/>
<Button Content="Change Text"
Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandTest}"
CommandParameter="{Binding ElementName=tbText, Path=Text}"/>
<TextBox Text="{Binding Text, Mode=OneWay}" Grid.Column="2" IsReadOnly="True" VerticalAlignment="Center"/>
<TextBox x:Name="tbNumber"
Grid.Row="1"
Text="55" VerticalAlignment="Center"/>
<Button Content="Change Number"
Grid.Row="1" Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandNumber}"
CommandParameter="{Binding Value, Source={StaticResource ProxyInt}}">
</Button>
<TextBox Text="{Binding Number, Mode=OneWay}" IsReadOnly="True"
Grid.Row="1" Grid.Column="2" VerticalAlignment="Center"/>
<TextBlock Grid.Row="2" Text="{Binding Value,Source={StaticResource proxy}}"/>
</Grid>
</Window>
Another variant.
Create converter for bindings:
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;
namespace InvalidateCommandMvvmLight
{
public class ValueTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter is Type type && TypeDescriptor.GetConverter(type).IsValid(value))
return TypeDescriptor.GetConverter(type).ConvertFrom(value);
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
XAML:
<Window x:Class="InvalidateCommandMvvmLight.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:InvalidateCommandMvvmLight"
xmlns:vm="clr-namespace:InvalidateCommandMvvmLight.ViewModel"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MyViewModel/>
</Window.DataContext>
<Window.Resources>
<local:ValueTypeConverter x:Key="ValueTypeConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox x:Name="tbText"
Text="Alle eendjes zwemmen in het water" VerticalAlignment="Center"
/>
<Button Content="Change Text"
Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandTest}"
CommandParameter="{Binding ElementName=tbText, Path=Text}"/>
<TextBox Text="{Binding Text, Mode=OneWay}" Grid.Column="2" IsReadOnly="True" VerticalAlignment="Center"/>
<TextBox x:Name="tbNumber"
Grid.Row="1"
Text="55" VerticalAlignment="Center"/>
<Button Content="Change Number"
Grid.Row="1" Grid.Column="1"
Margin="5"
Padding="5,2"
Command="{Binding Path=CommandNumber}"
CommandParameter="{Binding Text, Converter={StaticResource ValueTypeConverter}, ConverterParameter={x:Type sys:Int32}, ElementName=tbNumber}">
</Button>
<TextBox Text="{Binding Number, Mode=OneWay}" IsReadOnly="True"
Grid.Row="1" Grid.Column="2" VerticalAlignment="Center"/>
</Grid>
</Window>

Caliburn.Micro : How to bind a specific Item of Conductor.Collection.AllActive to a ContentControl

My goal is to have 4 different active ViewModels displayed in a grid on the ShellView. The issue is that I have not been able to figure out how to wire up a ContentControl to a specific Item in Items of the Conductor. How can his be done?
Here is a simplified version of what I and trying to do.
SolutionExplorer
ShellViewModel:
namespace ContentControlTest.ViewModels
{
public class ShellViewModel : Conductor<object>.Collection.AllActive
{
public ShellViewModel()
{
ActivateItem(new UC1ViewModel());
ActivateItem(new UC2ViewModel());
ActivateItem(new UC3ViewModel());
ActivateItem(new UC4ViewModel());
}
}
}
ShellView:
<Window x:Class="ContentControlTest.Views.ShellView"
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:ContentControlTest.Views"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d"
Title="ShellView" Height="450" Width="800"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Row="0" Grid.Column="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentControl cal:View.Model="{Binding UC1ViewModel}" cal:View.Context="{Binding Items[0]}"/>
</ScrollViewer>
<ScrollViewer Grid.Row="0" Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentControl cal:View.Model="{Binding UC2ViewModel}" cal:View.Context="{Binding Items[1]}"/>
</ScrollViewer>
<ScrollViewer Grid.Row="1" Grid.Column="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentControl cal:View.Model="{Binding UC3ViewModel}" cal:View.Context="{Binding Items[2]}"/>
</ScrollViewer>
<ScrollViewer Grid.Row="1" Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentControl cal:View.Model="{Binding UC4ViewModel}" cal:View.Context="{Binding Items[3]}"/>
</ScrollViewer>
</Grid>
</Window>
For simplification each UserControl ViewModel and View are Identical:
UC#ViewModel:
namespace ContentControlTest.ViewModels
{
public class UC1ViewModel : Screen
{
private string id;
public string ID
{
get { return id; }
set
{
id = value;
NotifyOfPropertyChange(() => ID);
}
}
public UC1ViewModel()
{
ID = Guid.NewGuid().ToString();
}
}
}
UC#View:
<UserControl x:Class="ContentControlTest.Views.UC1View"
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:ContentControlTest.Views"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
>
<Border BorderBrush="Black" BorderThickness="1">
<StackPanel >
<TextBlock Text="{Binding DisplayName}"/>
<TextBlock Text="{Binding ID}"/>
</StackPanel>
</Border>
</UserControl>
For testing I have tried using an ItemControl and it works but doesn't give me exactly what I want.
<ItemsControl x:Name="Items">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
You need to create properties in your ShellViewModel something like UC1, UC2, UC3 etc. You then need to change your ShellView to bind to UC1 property.
<ContentControl x:Name="UC1" />
...
Caliburn Micro should do the plumbing for you.
namespace ContentControlTest.ViewModels
{
public class ShellViewModel : Conductor<object>.Collection.AllActive
{
// Modify to implement INotifyPropertyChanged event...
public UC1ViewModel UC1 { get; set }
public ShellViewModel()
{
UC1 = new UC1ViewModel();
ActivateItem(UC1);
ActivateItem(new UC2ViewModel());
ActivateItem(new UC3ViewModel());
ActivateItem(new UC4ViewModel());
}
}
}
The Caliburn concept of Context is used to map a view model to multiple views, usually through conventions and mapping namespaces. In this case, however, each of your view models maps to exactly one view. Hence you don't need to / should not provide a context.
Second, your view model binding cannot be resolved without exposing them as public props (as #Jack suggested). Ironically, the binding you used for Context is the right one for the view model binding.
Replacing
<ContentControl cal:View.Model="{Binding UC1ViewModel}" cal:View.Context="{Binding Items[0]}"/>
With
<ContentControl cal:View.Model="{Binding Items[0]}"/>
Should do the trick.
Given the number of items is fixed it's better to follow #Jack's approach and reference the view models in a strongly typed fashion. Rather than relying on their index in the items collection. You can use either:
<ContentControl cal:View.Model="{Binding UC1ViewModel}" />
Or
<ContentControl x:Name="UC1ViewModel" />
Which are synonymous.
As you noticed the Caliburn Conductor really shines when used in combination with ItemControl. You typically don't need to have strongly typed references to the each of the Items then. That doesn't mean you can't use the conductor as you did, you still enjoy all the benefits of the managed lifecycle.
In case anyone is having an issue implementing the [perfectly fine] accepted answer, here is a more in depth answer:
Your main window that contain both (or even more than two) of your User Controls must be inherited from Caliburn.Micro.Conductor<Screen>.Collection.AllActive;
Your User Controls must be inherited from Caliburn.Micro.Screen;
You must also keep naming conventions in mind. If you use MenuUC as the name of a ContentControl in your View, also create a property named MenuUC in your ViewModel;
Initialize your UserControl as I do in Constructor;
Now you can use ActivateItem(MenuUC) and DeactivateItem(MenuUC) everywhere in your code. Caliburn.Micro automatically detects which one you want to work with.
Example XAML View code:
<Window x:Class="YourProject.Views.YourView"
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"
mc:Ignorable="d"
Title="YourViewTitle" Width="900" Height="480">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="4*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Menu Side Bar -->
<ContentControl Grid.Row="0" Grid.Column="0" x:Name="MenuUC" />
<!-- Panel -->
<Border Grid.Column="1" Grid.RowSpan="2" BorderThickness="1,0,0,0" BorderBrush="#FF707070" >
<ContentControl x:Name="PanelUC" />
</Border>
</Grid>
</Window>
Example C# ViewModel code:
class YourViewModel : Conductor<Screen>.Collection.AllActive
{
// Menu Side Bar
private MenuUCViewModel _menuUC;
public MenuUCViewModel MenuUC
{
get { return _menuUC; }
set { _menuUC = value; NotifyOfPropertyChange(() => MenuUC); }
}
// Panel
private Screen _panelUC;
public Screen PanelUC
{
get { return _panelUC; }
set { _panelUC = value; NotifyOfPropertyChange(() => PanelUC); }
}
// Constructor
public YourViewModel()
{
MenuUC = new MenuUCViewModel();
ActivateItem(MenuUC);
PanelUC = new FirstPanelUCViewModel();
ActivateItem(PanelUC);
}
// Some method that changes PanelUC (previously FirstPanelUCViewModel) to SecondPanelUCViewModel
public void ChangePanels()
{
DeactivateItem(PanelUC);
PanelUC = new SecondPanelUCViewModel();
ActivateItem(PanelUC);
}
}
In the above example, ChangePanels() acts as a method to load new User Control into your ContentControl.
Also read this question, it might be help you further.

How to bind wpf UserControl?

I am having a problem to binding viewmodel to my created usercontrol. The viewmodel doesn't reflect the value changes.
UpDown.xaml (Usercontrol xaml part)
btnUp and btnDown are used to change the text of txtNum. I don't know whether it is right?
<Grid DataContext="{Binding ElementName=btnDownRoot}">
<Grid.RowDefinitions>
<RowDefinition Height="0*"/>
<RowDefinition/>
<RowDefinition Height="0*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0*"/>
<ColumnDefinition Width="78*"/>
<ColumnDefinition Width="104*"/>
<ColumnDefinition Width="77*"/>
<ColumnDefinition Width="0*"/>
</Grid.ColumnDefinitions>
<TextBox Name="txtNum" Text="{Binding ElementName=btnDownRoot, Path=NumValue}" FontSize="20" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" BorderBrush="White" Grid.Column="2" Grid.RowSpan="2"/>
<Button Name="btnUp" Style="{StaticResource ResourceKey=btnUp}" Click="btnUp_Click" Grid.Column="3" Grid.RowSpan="2"/>
<Button Name="btnDown" Style="{StaticResource ResourceKey=btnDown}" Click="btnDown_Click" Grid.RowSpan="2" Grid.ColumnSpan="2"/>
</Grid>
UpDown.xaml.cs (Usercontrol codepart)
public string NumValue
{
get
{
return GetValue(NumValueProperty).ToString();
}
set
{
SetValue(NumValueProperty, value);
}
}
public static readonly DependencyProperty NumValueProperty =
DependencyProperty.Register("NumValue", typeof(string), typeof(UpDown),
new PropertyMetadata("1", new PropertyChangedCallback(OnNumChanged)));
private static void OnNumChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)
{
var instannce = d.ToString();
}
In the MainWindow,I am going to bind the MainViewModel to the UserControl
MainViewModel.cs
public class MainViewModel: BaseViewModel
{
private string _myValue;
public string MyValue
{
get { return _myValue; }
set
{
_myValue = value;
OnPropertyChanged("MyValue");
}
}
}
MainWindow.xaml
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<local:UpDown Grid.Row="0" NumValue="{Binding MyValue}" x:Name="udGuestNum" Width="220" Margin="0 20"/>
<Button Name="btnOK" Grid.Row="1" Content="OK" Click="btnOK_Click"/>
</Grid>
The NumValue Binding is OneWay by default. Either explicitly set it to TwoWay
NumValue="{Binding MyValue, Mode=TwoWay}"
or make TwoWay the default:
public static readonly DependencyProperty NumValueProperty =
DependencyProperty.Register(
"NumValue", typeof(string), typeof(UpDown),
new FrameworkPropertyMetadata(
"1", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnNumChanged));
numeric up/down control have already been developed...don't waste your time, you can even grab the code > https://github.com/xceedsoftware/wpftoolkit

Custom TabItem header with text and image - not sure how to proceeed

I have a TabControl in my XAML which is just an empty control as the TabItems are dynamically generated at runtime.
My problem is I want to have the tab title and an image (a "settings" image) in the Header but I'm not sure how to go about this. As I said, I'm generating the TabItems on the fly so how and where would a template to do this fit in and where would I put it etc? Would a TabItem header template apply to TabItem controls created dynamically? (I am assuming/hoping so!)
I've googled and searched around here but no-one quite seems to be doing what I'm doing... just wondering if someone could give me some guidance.
<Grid Name="MainGrid" Background="#333333" ShowGridLines="False" >
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" ToolTip="Settings">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="RoboNews" Foreground="SkyBlue" FontSize="32" Padding="5"/>
<Button Name="btnSettings" Background="Transparent" Grid.Column="1" BorderBrush="#333333" BorderThickness="0" HorizontalAlignment="Right"
Click="btnSettings_Click" ToolTip="Click for menu">
<!--<Image Source="Images/Settings48x48.png"/>-->
<Image Source="/Images/MenuOpen.png" Width="36" />
</Button>
</Grid>
<TabControl Name="tabCategories" Grid.Row="1" Background="Black" SelectionChanged="tabCategories_SelectionChanged">
</TabControl>
</Grid>
You can create a UserControl for your header
<UserControl DataContext="{Binding RelativeSource={RelativeSource Self}}" x:Class="TabControlHeader.TabItemHeader"
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" Width="Auto" Height="Auto">
<Grid >
<StackPanel>
<Image Source="{Binding ImageSource}"></Image>
<TextBlock Text="{Binding Text}"></TextBlock>
</StackPanel>
</Grid>
</UserControl>
With ideally DPs defined in its ViewModel, but for the time being, in code behind:
public string ImageSource
{
get { return (string)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for ImageSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource", typeof(string), typeof(TabItemHeader));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(TabItemHeader));
Then while creating your TabItem you need to set its HeaderTemplate:
TabItem tabItem = new TabItem();
tabItem.Height = 100;
tabItem.Width = 50;
var header = new FrameworkElementFactory(typeof(TabItemHeader));
header.SetValue(TabItemHeader.TextProperty, "This is Text");
header.SetValue(TabItemHeader.ImageSourceProperty, "This is Image uri");
header.SetValue(TabItemHeader.HeightProperty, (double)50);
header.SetValue(TabItemHeader.WidthProperty, (double)50);
tabItem.HeaderTemplate = new DataTemplate { VisualTree = header };
tabCategories.Items.Add(tabItem);

How to propagate silverlight usercontrol properties to parent?

I would like to have some of the properties in the custom user control to be available to the parent page. I created a small sample to illustrate what I am looking for.
I am trying to use MVVM pattern and all the binding mechanisms to achieve it.
USERCONTROL XAML
<UserControl x:Class="TestCustomUserControl.MyControls.UserNameControl"
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:TestCustomUserControl.ViewModels"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<local:UserNameViewModel x:Key="TheViewModel"/>
</UserControl.Resources>
<Grid x:Name="NameCtrlRoot" Background="White" DataContext="{StaticResource TheViewModel}">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="First Name:" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Last Name: "/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="txtFirstName" Text="{Binding FirstName, Mode=TwoWay}">
<i:Interaction.Behaviors>
<!-- This behavior updates the binding after a specified delay
instead of the user having to lose focus on the TextBox. -->
<local:TextChangedDelayedBindingBehavior RefreshTimeMilliseconds="750" />
</i:Interaction.Behaviors>
</TextBox>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="txtLastName" Text="{Binding LastName, Mode=TwoWay}">
<i:Interaction.Behaviors>
<!-- This behavior updates the binding after a specified delay
instead of the user having to lose focus on the TextBox. -->
<local:TextChangedDelayedBindingBehavior RefreshTimeMilliseconds="750" />
</i:Interaction.Behaviors>
</TextBox>
<TextBlock Grid.Row="3" Grid.Column="0" Text="fullname inside control:" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding FullName}" />
<Border Height="1" Background="Black" Grid.Column="2" Grid.Row="4" />
</Grid>
</StackPanel>
</Grid>
the above Usercontrol is binded to the following VIEWMODEL
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public class UserNameViewModel : BaseViewModel
{
private String _firstName;
public String FirstName
{
get { return _firstName; }
set
{
_firstName = value;
NotifyPropertyChanged("FirstName");
OnNameChange();
}
}
private String _lastName;
public String LastName
{
get { return _lastName; }
set
{
_lastName = value;
NotifyPropertyChanged("LastName");
OnNameChange();
}
}
private void OnNameChange()
{
FullName = String.Format("{0} {1}", FirstName, LastName);
}
public String _fullName;
public String FullName
{
get { return _fullName; }
set {
_fullName = value;
NotifyPropertyChanged("FullName");
}
}
}
Consumer Page that uses the above USERCONTROL
<navigation:Page xmlns:my="clr-namespace:TestCustomUserControl.MyControls" x:Class="TestCustomUserControl.Views.ConsumeName"
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"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
d:DesignWidth="640" d:DesignHeight="480"
Title="ConsumeName Page">
<Grid x:Name="LayoutRoot">
<StackPanel>
<my:UserNameControl x:Name="MyNameControl"/>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Full Name in Parent: " />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding FullName, ElementName=MyNameControl}"/>
</Grid>
</StackPanel>
</Grid>
Here is my question now, If you look at the view model associated with user control, it has a property called FullName and I would like that to be exposed via Usercontrol, so that I can access it from the consuming page. Its like consuming page want to access some of the properties of usercontrol. I am not quite sure as to how that can be acheived. I would like to stick with MVVM pattern.
you have already decalred a StaticResource, so you can use it in both views.
Do this
<UserControl.Resources>
<local:UserNameViewModel x:Key="TheViewModel"/>
</UserControl.Resources>
in the "ConsumeName Page". If yous simply add the DataContext to your Grid
<Grid x:Name="LayoutRoot" DataContext="{StaticResource TheViewModel}">
this should work. (You don't need ElementName=MyNameControl any longer).
The my:UserNameControl should inherit the DataContext. If not, you have to add it here.
<my:UserNameControl DataContext="{StaticResource TheViewModel}"/>
This should work.
Right now the local:UserNameViewModel with the key TheViewModel is only achievable where you defined it. If you define it in your app.xaml you can access it from everywhere in the app.
Hope this hels.
BR,
TJ
I am answering my own question. But before answering my question, just want to clarify somethings. I was trying to create a fully encapsulated UserControl with its own ViewModel. And where ever the usercontrol is consumed, the consumer should know nothing about the usercontrol's internal viewmodel. Only communication option I want the consumer to have is by setting some properties and using binding mechanism.
So the way I resolved my problem is, I created dependency property inside the UserControl and setting it whenever something changes in the usercontrol's viewmodel.
UserNameControl.xaml (usercontrol)
<UserControl x:Class="TestCustomUserControl.MyControls.UserNameControl"
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:TestCustomUserControl.ViewModels"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<local:UserNameViewModel x:Key="TheViewModel"/>
</UserControl.Resources>
<Grid x:Name="NameCtrlRoot" Background="White" DataContext="{StaticResource TheViewModel}">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="First Name:" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Last Name: "/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="txtFirstName" Text="{Binding FirstName, Mode=TwoWay}">
<i:Interaction.Behaviors>
<!-- This behavior updates the binding after a specified delay
instead of the user having to lose focus on the TextBox. -->
<local:TextChangedDelayedBindingBehavior RefreshTimeMilliseconds="750" />
</i:Interaction.Behaviors>
</TextBox>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="txtLastName" Text="{Binding LastName, Mode=TwoWay}">
<i:Interaction.Behaviors>
<!-- This behavior updates the binding after a specified delay
instead of the user having to lose focus on the TextBox. -->
<local:TextChangedDelayedBindingBehavior RefreshTimeMilliseconds="750" />
</i:Interaction.Behaviors>
</TextBox>
<TextBlock Grid.Row="3" Grid.Column="0" Text="fullname inside control:" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding FullName}" />
<Border Height="1" Background="Black" Grid.Column="2" Grid.Row="4" />
</Grid>
</StackPanel>
</Grid>
BaseViewModel.cs
public class BaseViewModel : INotifyPropertyChanged
{
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
UserNameViewModel.cs
public class UserNameViewModel : BaseViewModel
{
private String _firstName;
public String FirstName
{
get { return _firstName; }
set
{
_firstName = value;
NotifyPropertyChanged("FirstName");
OnNameChange();
}
}
private String _lastName;
public String LastName
{
get { return _lastName; }
set
{
_lastName = value;
NotifyPropertyChanged("LastName");
OnNameChange();
}
}
public Action NameChanged { get; set; }
private void OnNameChange()
{
FullName = String.Format("{0} {1}", FirstName, LastName);
if(NameChanged != null) NameChanged.Invoke();
}
private String _fullName;
public String FullName
{
get { return _fullName; }
set {
_fullName = value;
NotifyPropertyChanged("FullName");
}
}
}
UserNameControl.xaml.cs (Usercontrol code behind with DependencyProperty declaration)
public partial class UserNameControl : UserControl
{
private UserNameViewModel _TheViewModel;
public UserNameControl()
{
InitializeComponent();
_TheViewModel = Resources["TheViewModel"] as UserNameViewModel;
_TheViewModel.NameChanged = OnNameChanged;
}
public String SelectedFullName
{
get { return (String) GetValue(SelectedFullNameProperty); }
set { SetValue(SelectedFullNameProperty, value); }
}
public static readonly DependencyProperty SelectedFullNameProperty =
DependencyProperty.Register("SelectedFullName", typeof (String), typeof (UserNameControl), null);
private void OnNameChanged()
{
SelectedFullName = _TheViewModel.FullName;
}
}
ConsumeName.xaml (Consumer navigation Page, user above usercontrol and pulls SelectedFullName into UserFullName)
<navigation:Page xmlns:my="clr-namespace:TestCustomUserControl.MyControls" x:Class="TestCustomUserControl.Views.ConsumeName"
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"
mc:Ignorable="d"
xmlns:local="clr-namespace:TestCustomUserControl.ViewModels"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
d:DesignWidth="640" d:DesignHeight="480"
Title="ConsumeName Page">
<navigation:Page.Resources>
<local:ConsumeNameViewModel x:Key="TheConsumeNameViewModel"/>
</navigation:Page.Resources>
<Grid x:Name="LayoutRoot" DataContext="{StaticResource TheConsumeNameViewModel}">
<StackPanel>
<my:UserNameControl x:Name="MyNameControl" SelectedFullName="{Binding UserFullName, Mode=TwoWay}" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Full Name in Parent: " />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding UserFullName}"/>
</Grid>
</StackPanel>
</Grid>
ConsumeNameViewModel.cs
public class ConsumeNameViewModel : BaseViewModel
{
private string _UserFullName;
public string UserFullName
{
get { return _UserFullName; }
set
{
if (value != _UserFullName)
{
_UserFullName = value;
NotifyPropertyChanged("UserFullName");
}
}
}
}

Resources