Set User Control Dependency Property value to a lable control - wpf

I am creating a user control, inside the control I have a Label Control. (for example: an email control, contains a label and a text box).
In the email address control, I defined a Title property, which when the user changes the property value, I want to set the string value to the Label.
bellow is the XAML and Code:
XAML:
<UserControl x:Class="SIMind.ClinicManagement.GUI.Controls.CommonControl.EmailAddressCtrl"
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" Height="32" MinHeight="32" MaxHeight="32" Width="386" MinWidth="386">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel HorizontalAlignment="Stretch" Margin="0" Name="stackPanel1" VerticalAlignment="Stretch" Orientation="Horizontal">
<Label Content="Email :" Height="24" HorizontalContentAlignment="Right" Name="lblEmailCaption" Tag="PhoneType" Width="140" />
<TextBox MaxLength="100" Name="txtEmail" Text="email#server.com" Width="240" Margin="1" Height="23" />
</StackPanel>
</Grid>
</UserControl>
Code:
namespace SIMind.ClinicManagement.GUI.Controls.CommonControl
{
/// <summary>
/// Interaction logic for EmailAddressCtrl.xaml
/// </summary>
public partial class EmailAddressCtrl : UserControl
{
public EmailAddressCtrl()
{
InitializeComponent();
}
#region Dependency Property
#region Title Property
/// <summary>
/// Title property
/// </summary>
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(String),
typeof(EmailAddressCtrl), new PropertyMetadata("Phone Number :"));
/// <summary>
/// Gets and Sets the main label of the control.
/// </summary>
public string Title
{
get { return (string)GetValue(TitleProperty); }
set {
SetValue(TitleProperty, value);
lblEmailCaption.Content = value;
}
}
#endregion
#endregion
}
}
But it seems not working the way I wanted it to be: The dependency property is set, but the label is not refreshed to be the property set.
Any one got a good answer? :-)

Hope this will work for you
<Label Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type EmailAddressCtrl}},Path=Title}" Height="24" HorizontalContentAlignment="Right" Name="lblEmailCaption" Tag="PhoneType" Width="140" />

Related

WPF custom control: Proper way to react to property changes [duplicate]

This question already has answers here:
DependencyProperty not triggered
(2 answers)
Callback when dependency property recieves xaml change
(2 answers)
Closed 1 year ago.
I have created a custom control in WPF: ChannelsDisplay, a kind of very simple charts control. For testing the control I use a MVVM test application:
View
<UserControl x:Class="UI.CustomControls.TestApp.Views.ChannelsDisplayView"
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:UI.CustomControls.TestApp.Views"
xmlns:ucc="clr-namespace:UI.CustomControls;assembly=UI.CustomControls"
xmlns:viewmodels="clr-namespace:UI.CustomControls.TestApp.ViewModels"
d:DataContext="{d:DesignInstance Type=viewmodels:ChannelsDisplayViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ucc:ChannelsDisplay Lines="{Binding Lines}"
ChannelsYSpacing="{Binding YSpacing, Mode=TwoWay}"/>
<StackPanel Grid.Row="1" Orientation="Vertical">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Content="ChannelsYSpacing" />
<Slider Grid.Column="1"
Value="{Binding YSpacing, Mode=TwoWay}"
Minimum="0" Maximum="99" IsSnapToTickEnabled="True"/>
<TextBox Grid.Column="2" Text="{Binding YSpacing}"/>
</Grid>
</StackPanel>
</Grid>
</UserControl>
ViewModel
public class ChannelsDisplayViewModel : ObservableObject, IPageViewModel
{
public string Name => "ChannelsDisplay";
public ObservableCollection<List<int>> Lines { get; set; }
public int YSpacing
{
get { return ySpacing; }
set
{
if (value != ySpacing)
{
loginViewModel = value;
OnPropertyChanged(nameof(YSpacing));
}
}
}
// ...
};
In the view there is a Slider, a TextBox and the ChannelsDisplay, all of them bind to the view model property YSpacing. The aim is that the Slider can change the YSpacing of my ChannelsDisplay.
So the ChannelsDisplay has among other things a property ChannelsYSpacing, which is implemented as a full DependencyProperty:
/// <summary>
/// Y-Distance between each band of data
/// </summary>
[Category("Common")]
public int ChannelsYSpacing
{
get => (int)GetValue(ChannelsYSpacingProperty);
set
{
SetValue(ChannelsYSpacingProperty, value);
createScaledLines();
createVisibleLines();
updateCanvas();
}
}
/// <summary>
/// Identifies the <see cref="Lines"/> dependency property.
/// </summary>
public static readonly DependencyProperty ChannelsYSpacingProperty =
DependencyProperty.Register("ChannelsYSpacing",
typeof(int),
typeof(ChannelsDisplay),
new FrameworkPropertyMetadata(20, new PropertyChangedCallback(OnSpacingChanged)));
The problem is, if the bound value of ChannelsYSpacing changes (E.g. the slider was moved by the user) the ChannelsDisplay custom control must be updated and redrawn. And the only way to do it that I could find out to write some code into the PropertyChangedCallback:
private static void OnSpacingChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
ChannelsDisplay channelsDisplay = (ChannelsDisplay)d;
if(e.Property.Name == nameof(ChannelsYSpacing))
{
channelsDisplay.ChannelsYSpacing = (int)e.NewValue;
}
}
So the custom controls property ChannelsYSpacing will be set to e.NewValue and the property setter then calls createScaledLines(), createVisibleLines() and updateCanvas().
That works, the control is updated when the slider is moved. But going through the callback handler and writing things like if (e.Property.Name == nameof(ChannelsYSpacing)) somehow feels not right. It feelslike overhead. Ist this really necessary or is there a better way to do it?

How to bind a UserControl's property to a property?

I'd like to set a property of a re-defined UserControl (for example its background color) to a property of the class. For example.
If I define the background of a Button to a property (<Button x:Name="myButton" Background="{Binding ColorName}"/>), it works fine. However, if I do the same for a re-defined UserControl (<local:MyUserControl Background="{Binding Path=ColorName}"/>), it does not.
What's funny though, is that, if I do <local:MyUserControl Background="{Binding Background, ElementName=myButton}"/>, it works perfectly fine.
Could I have some help on that? I must be missing something.
Thanks!
EDIT
Here is all the code. The setting of the background color worked fine. What solved this was to set properly the MainWindow.DataContext and to remove the DataContext = this in MyUserControl.xaml.cs. Setting Color as a DependencyProperty is also useful to be able to change the Color setting in a later execution of the code.
Nonetheless, while removing DataContext=this in MyUserControl.xaml.cs,
the {Binding TextContent} does not work and needs to be replaced by {Binding TextContent, RelativeSource={RelativeSource AncestorType=c:MyUserControl}}.
MainWindow.xaml
<Window x:Class="BindingBug.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:BindingBug"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Button Background="{Binding Path=Color}"
Width="250"
Height="30"
Content="I am bound to be RED!"
Grid.Row="0"
x:Name="myButton"/>
<c:MyUserControl Background="{Binding Background, ElementName=myButton}"
Width="250"
Height="30"
Content="I am bound to be RED!"
Grid.Row="1"/>
<c:MyUserControl Background="{Binding Path=Color}"
Width="250"
Height="30"
Content="I am bound to be RED!"
Grid.Row="2"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Media;
namespace BindingBug
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Color = Brushes.Red;
}
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(Brush), typeof(MainWindow));
public Brush Color
{
get
{
return (Brush)GetValue(ColorProperty);
}
set
{
SetValue(ColorProperty, value);
}
}
}
}
MyUserControl.xaml
<UserControl x:Class="BindingBug.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:c="clr-namespace:BindingBug"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
FontSize="13"
Text="{Binding TextContent, RelativeSource={RelativeSource AncestorType=c:MyUserControl}}"
VerticalAlignment="Center"/>
</Grid>
</UserControl>
MyUserControl.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace BindingBug
{
/// <summary>
/// Interaction logic for NumberDataHolder.xaml
/// </summary>
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public static readonly DependencyProperty TextContentProperty = DependencyProperty.Register("TextContent", typeof(string), typeof(MyUserControl));
public string TextContent
{
get
{
return (string)GetValue(TextContentProperty);
}
set
{
SetValue(TextContentProperty, value);
}
}
}
}
EDIT 2
I tried to acheive the same results without having to declare the whole Text="{Binding TextContent, RelativeSource={RelativeSource AncestorType=c:MyUserControl}}" inside TextBlock. So, following #KeithStein advice, I placed DataContext="{Binding RelativeSource={RelativeSource Self}}" inside MyUserControl and only kept Text="{Binding TextContent}"inside TextBlock. That, however cancels the effect of setting Background="{Binding Path=Color}" in MainWindow.xaml. Any idea why? Is there another possibility to set Background="{Binding Path=Color}" in MainWindow.xaml and to only keepText="{Binding TextContent}"inside TextBlock?
MainWindow.xaml
<Window x:Class="BindingBug.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:BindingBug"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Button Background="{Binding Path=Color}"
Width="250"
Height="30"
Content="I am bound to be RED!"
Grid.Row="0"
x:Name="myButton"/>
<c:MyUserControl Background="{Binding Background, ElementName=myButton}"
Width="250"
Height="30"
Content="I am bound to be RED!"
Grid.Row="1"/>
<c:MyUserControl Background="{Binding Path=Color}"
Width="250"
Height="30"
Content="I am bound to be RED!"
Grid.Row="2"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Media;
namespace BindingBug
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Color = Brushes.Red;
}
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(Brush), typeof(MainWindow));
public Brush Color
{
get
{
return (Brush)GetValue(ColorProperty);
}
set
{
SetValue(ColorProperty, value);
}
}
}
}
MyUserControl.xaml
<UserControl x:Class="BindingBug.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:c="clr-namespace:BindingBug"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
FontSize="13"
Text="{Binding TextContent}"
VerticalAlignment="Center"/>
</Grid>
</UserControl>
MyUserControl.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace BindingBug
{
/// <summary>
/// Interaction logic for NumberDataHolder.xaml
/// </summary>
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public static readonly DependencyProperty TextContentProperty = DependencyProperty.Register("TextContent", typeof(string), typeof(MyUserControl));
public string TextContent
{
get
{
return (string)GetValue(TextContentProperty);
}
set
{
SetValue(TextContentProperty, value);
}
}
}
}
This answer developed gradually through back and forth comments with OP. To summarize:
Use a Brush-type dependency property for your color. Brush because that is the type of the Background property that you want to bind to, and a dependency property so that updates of the property trigger any bindings to refresh.
When binding inside a Window or UserControl, you need to set DataContext, which is essentially the default sourced used by bindings.
For a Window, add DataContext="{Binding RelativeSource={RelativeSource Self}}" to the opening tag. This sets the default source for all controls contained within to the Window itself.
For a UserControl, add the following to the outer-most panel of said control: DataContext={Binding RelativeSource={RelativeSource AncestorType=UserControl}} (UserControl can be replaced with the name of your particular control, i.e. c:MyUserControl). This tells everything inside that root panel to use the UserControl as the default source. You can't use RelativeSource Self in this case, because then instances of the MyUserControl will bind to themselves when placed inside Windows, instead of inheriting the Window's DataContext.

Update popup position based on parent position

I want to update my Popup's position when the size of its parent is changing.
The following code works but there's a problem.
As you can see, inside the popup there's a big button (width 300), before the textbox reach this size, it don't update the popup position (try it yourself - write a super big sentence and you will see)
<TabControl>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid Height="26"
Background="{TemplateBinding Background}"
x:Name="TabGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter x:Name="tabTitle" Margin="5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
ContentSource="Header"/>
<StackPanel Grid.Column="1" Height="26" Margin="0,0,1,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<ToggleButton x:Name="Edit" Width="16" Content="e"
ToolTip="Edit" />
<Popup AllowsTransparency="True"
IsOpen="{Binding IsChecked, ElementName=Edit}"
Placement="Right"
PlacementTarget="{Binding ElementName=TabGrid}"
StaysOpen="False"
VerticalOffset="30"
HorizontalOffset="-20">
<Grid x:Name="PopupGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Width="16" Height="3" Margin="0,0,20,0"
HorizontalAlignment="Right"
Panel.ZIndex="1" Background="White" />
<Border Grid.Row="1" Margin="0,-2,0,0"
Background="White"
BorderBrush="{Binding TabColor}"
BorderThickness="2">
<StackPanel>
<TextBox Name="Text"
Text="{Binding Content, ElementName=tabTitle, UpdateSourceTrigger=PropertyChanged}"
Margin="10"/>
<Button Width="300"/>
</StackPanel>
</Border>
</Grid>
</Popup>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
<TabItem Header="TabItem">
<Grid Background="#FFE5E5E5"/>
</TabItem>
<TabItem Header="TabItem">
<Grid Background="#FFE5E5E5"/>
</TabItem>
</TabControl>
Issue is PopUp needs some notification to update itself. So, as a workaround you can follow these steps to get it updated.
Hook TextChanged event for TextBox where you can raise some notification to popUp to update itself.
Next challenge would be how to get to popUp instance from TextBox. For that we can store the popUp in Tag of TextBox which we can access from code.
One of the property which results in re-calculation of popUp placement is VerticalOffset which we can set manually to force popUp to recalculate position.
That being said here is the code (updated XAML code):
<Popup x:Name="popUp" AllowsTransparency="True"
IsOpen="{Binding IsChecked, ElementName=Edit}" Placement="Right"
PlacementTarget="{Binding ElementName=TabGrid}" StaysOpen="False"
VerticalOffset="30" HorizontalOffset="-20">
<Grid x:Name="PopupGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Width="16" Height="3" Margin="0,0,20,0" HorizontalAlignment="Right"
Panel.ZIndex="1" Background="White" />
<Border Grid.Row="1" Margin="0,-2,0,0" Background="White" BorderBrush="Black"
BorderThickness="2">
<StackPanel>
<TextBox Name="Text" TextChanged="Text_TextChanged"
Tag="{Binding ElementName=popUp}"
Text="{Binding Content, ElementName=tabTitle,
UpdateSourceTrigger=PropertyChanged}" Margin="10"/>
<Button Width="300"/>
</StackPanel>
</Border>
</Grid>
</Popup>
Code behind:
private void Text_TextChanged(object sender, TextChangedEventArgs e)
{
Popup popup = ((TextBox)sender).Tag as Popup;
if (popup != null)
{
popup.VerticalOffset += 1;
popup.VerticalOffset -= 1;
}
}
I created a behavior for your Popup, using the reflection solution from this question. I'm not sure whether that is a right solution to you... I tested the implementation on .NET3.5 without any issues.
Add the PopupBehavior class to your project:
/// <summary>
/// Attaches alignment behavior to a Popup element.
/// </summary>
public class PopupBehavior : Behavior<Popup>
{
#region Public fields
public static readonly DependencyProperty HeaderWidthProperty = DependencyProperty.Register("HeaderWidth", typeof(double), typeof(PopupBehavior), new FrameworkPropertyMetadata(0.0, HeaderWidthChanged));
public static readonly DependencyProperty PopupConnectionOffsetProperty = DependencyProperty.Register("PopupConnectionOffset", typeof(double), typeof(PopupBehavior), new FrameworkPropertyMetadata(0.0));
#endregion Public fields
#region Private fields
private MethodInfo updateMethod;
#endregion Private fields
#region Public properties
/// <summary>
/// Gets or sets the Width of the control to subscribe for changes.
/// </summary>
public double HeaderWidth
{
get { return (double)GetValue(HeaderWidthProperty); }
set { SetValue(HeaderWidthProperty, value); }
}
/// <summary>
/// Gets or sets the offset of the connection visual of the popup.
/// </summary>
public double PopupConnectionOffset
{
get { return (double)GetValue(PopupConnectionOffsetProperty); }
set { SetValue(PopupConnectionOffsetProperty, value); }
}
#endregion Public properties
#region Public constructors
/// <summary>
/// Creates an instance of the <see cref="PopupBehavior" /> class.
/// </summary>
public PopupBehavior()
{
updateMethod = typeof(Popup).GetMethod("Reposition", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
}
#endregion Public constructors
#region Protected methods
/// <summary>
/// Called after the behavior is attached to an AssociatedObject.
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
var pd = DependencyPropertyDescriptor.FromProperty(Popup.IsOpenProperty, typeof(Popup));
pd.AddValueChanged(this.AssociatedObject, IsOpenChanged);
}
#endregion Protected methods
#region Private methods
/// <summary>
/// The HeaderWidth property has changed.
/// </summary>
private static void HeaderWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var b = d as PopupBehavior;
if (b != null)
b.UpdateHorizontalOffset();
}
/// <summary>
/// Gets the width of the associated popup.
/// </summary>
/// <returns>A double value; width of the popup.</returns>
/// <remarks>
/// This method gets the width of the popup's child, since the popup itself has a width of 0
/// when collapsed.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Occurs when the child of the popup is not derived from FrameworkElement.
/// </exception>
private double GetPopupWidth()
{
var child = this.AssociatedObject.Child as FrameworkElement;
if (child != null)
return child.ActualWidth;
else
throw new InvalidOperationException("Child of Popup is not derived from FrameworkElement");
}
/// <summary>
/// The IsOpen property of the popup has changed.
/// </summary>
private void IsOpenChanged(object sender, EventArgs e)
{
if (this.AssociatedObject.IsOpen)
UpdateHorizontalOffset();
}
/// <summary>
/// Updates the HorizontalOffset of the popup.
/// </summary>
private void UpdateHorizontalOffset()
{
if (this.AssociatedObject.IsOpen)
{
var offset = (GetPopupWidth() - PopupConnectionOffset) * -1;
if (this.AssociatedObject.HorizontalOffset == offset)
updateMethod.Invoke(this.AssociatedObject, null);
else
this.AssociatedObject.HorizontalOffset = offset;
}
}
#endregion Private methods
}
Make some changes in your XAML:
Change the Placement property to "Bottom".
Bind the PlacementTarget to your button (Edit) .
Remove the HorizontalOffset and VerticalOffset properties from your Popup.
Assign the Behavior.
Your code should look something like this:
...
xmlns:local="clr-namespace:MyProject"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
...
<Popup
AllowsTransparency="True"
IsOpen="{Binding IsChecked, ElementName=Edit}"
Placement="Bottom"
PlacementTarget="{Binding ElementName=Edit}"
StaysOpen="False">
<i:Interaction.Behaviors>
<local:PopupBehavior
HeaderWidth="{Binding ActualWidth, ElementName=tabTitle}"
PopupConnectionOffset="36" />
</i:Interaction.Behaviors>
...
</Popup>
Change the "local" clr-namespace to that of your project. If not already using, you might need to add a reference to the System.Windows.Interactivity assembly, I installed it using NuGet (Blend.Interactivity.Wpf).
The HeaderWidth purely subscribes to the changes of the header width of the TabItem and PopupConnectionOffset defines the offset from the right side of the Popup to the left side of the 'white stripe Border'.
Note that FrameworkElement should be assignable from the Popup's child value. Also, since the Popup is now targetted to the Edit button instead of the TabGrid, it aligns correctly when the TabItems exceed the parent container, preventing situations as these:

Binding to a property of a borders child

I have implemented my own simple version of a navigation window, mainly because navigation windows journal does not give me control over how many children can exist. So I am using a border inside a window and changig its child everytime. As children I am using a UserControl. I want to bind the title of my Window to the Title property of my current child. Somehow I cannot figure out a way to do it.
MainWindow XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="525"
Height="350"
Background="AliceBlue"
Title="{Binding Path=Child.Title,
ElementName=borderContent}">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Button Content="<-" x:Name="btnBack" />
<Button Content="->" x:Name="btnForward" />
</StackPanel>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
<Button Content="1" Click="Button_Click_1" />
<Button Content="2" Click="Button_Click_2" />
</StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Border x:Name="borderContent" />
</ScrollViewer>
</DockPanel>
MainWindow Code behind:
using System;
using System.Windows;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
this.borderContent.Child = new ContentPage("Title 1");
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
this.borderContent.Child = new ContentPage("TITLE 2");
}
}
}
UserControl XAML:
<UserControl x:Class="WpfApplication1.ContentPage"
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:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="{Binding Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=Title}" />
</Grid>
User Control Code behind:
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Content.xaml
/// </summary>
public partial class ContentPage : UserControl
{
public string Title
{
get { return (string)this.GetValue(ContentPage.TitleProperty); }
set { this.SetValue(ContentPage.TitleProperty, value); }
}
// Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(ContentPage), new UIPropertyMetadata(string.Empty));
public ContentPage(string Title)
{
this.Title = Title;
InitializeComponent();
}
}
}
Somehow the binding inside the UserControl is also not working. What am I doing wrong?
The problem is that the Child property of a Borderisn't a DependencyProperty so there is no change notification. You'll have to update the Binding manually everytime you change the Child
private void Button_Click_1(object sender, RoutedEventArgs e)
{
this.borderContent.Child = new ContentPage("Title 1");
UpdateTitleBindingExpression();
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
this.borderContent.Child = new ContentPage("TITLE 2");
UpdateTitleBindingExpression();
}
private void UpdateTitleBindingExpression()
{
BindingExpressionBase beb = BindingOperations.GetBindingExpressionBase(this, Window.TitleProperty);
if (beb != null)
{
beb.UpdateTarget();
}
}
I'm not sure why you are doing what you are doing, but regarding your question:
change "Source" to "RelativeSource"
<Grid>
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=Title}" />
</Grid>
That should fix the binding issue
Edit:
When you really want to do it that way, you could make the borderContent element an ContentControl and use the Content property instead. Since this is an DependencyProperty you're binding will work:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="525"
Height="350"
Background="AliceBlue"
Title="{Binding Content.Title, ElementName=borderContent}">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Button Content="<-" x:Name="btnBack" />
<Button Content="->" x:Name="btnForward" />
</StackPanel>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
<Button Content="1" Click="Button_Click_1" />
<Button Content="2" Click="Button_Click_2" />
</StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ContentControl x:Name="borderContent" />
</ScrollViewer>
</DockPanel>
</Window>

Interesting Issue with Silverlight Datagrid

Folks,
I'm having an interesting issue with Silverlight DataGrid data binding. It may be b/c I'm not binding the data source properly. Here's the object & the observable collection
/// <summary>
/// Interface for all model elements
/// </summary>
public interface IBaseModel
{
}
/// <summary>
/// Employee model
/// </summary>
public class EmployeeModel : IBaseModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString()
{
return FirstName + LastName;
}
}
// The observable collection is loaded and bound in the user control
public partial class EmployeeMasterDetailsWindow : UserControl
{
public EmployeeMasterDetailsWindow()
{
try
{
InitializeComponent();
ObservableCollection<IBaseModel> k = new ObservableCollection<IBaseModel>()
{new EmployeeModel(){FirstName="Frodo",
LastName=" Baggins"},
new EmployeeModel(){FirstName="Pippin",
LastName="Thomas"},
new EmployeeModel(){FirstName="John",
LastName="Doe"},
new EmployeeModel(){FirstName="Tim",
LastName="Kiriev"}};
dataGrid1.DataContext = k;
CustomersListBox.DataContext = k;
}
catch (Exception ex)
{
}
}
}
//here's the XAML
<UserControl x:Class="AdventureWorksManagement.UI.EmployeeMasterDetailsWindow"
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"
d:DesignHeight="379" d:DesignWidth="516"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">
<UserControl.Resources>
<DataTemplate x:Key="CustomerTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" Height="371" Width="595">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="312*" />
<ColumnDefinition Width="283*" />
</Grid.ColumnDefinitions>
<sdk:DataGrid Height="325" HorizontalAlignment="Left"
Margin="12,12,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="271" ItemsSource="{Binding}"
RowDetailsTemplate="{StaticResource CustomerTemplate}">
</sdk:DataGrid>
<ListBox x:Name="CustomersListBox"
Margin="10,10,10,11"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource CustomerTemplate}" />
</Grid>
The Listbox shows all the of the employees, but the DataGrid doesn't. I don't even see the DataGrid. I see this error message in the output window:
'System.Collections.ObjectModel.ObservableCollection1[AdventureWorksManagement.Model.IBaseModel]'
'System.Collections.ObjectModel.ObservableCollection1[AdventureWorksManagement.Model.IBaseModel]'
(HashCode=54025633).
BindingExpression: Path='FirstName'
DataItem='System.Collections.ObjectModel.ObservableCollection`1[AdventureWorksManagement.Model.IBaseModel]'
(HashCode=54025633); target element is
'System.Windows.Controls.TextBlock'
(Name=''); target property is 'Text'
(type 'System.String')..
What could I be doing wrong?
By making it an ObservableCollection<IBaseModel> you are effectively casting all the child objects to IBaseModel, which has no members.
In this instance make it an ObservableCollection<EmployeeModel>.

Resources