Pushing ActualWidth/ActualHeight from a custom control to TemplateBinding - c#

I have set up a simple example to try to achieve a simple thing: exposing a dependency property in a custom control that exposes a ActualWidth/ActualHeight of a control within this custom control.
In order to attempt to achieve that I have:
customcontrol.cs
public class CustomControl : ContentControl
{
static CustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl)));
}
public static readonly DependencyProperty RenderedWidthProperty = DependencyProperty.Register(
"RenderedWidth", typeof (double), typeof (CustomControl), new PropertyMetadata(default(double)));
public double RenderedWidth
{
get { return (double) GetValue(RenderedWidthProperty); }
set { SetValue(RenderedWidthProperty, value); }
}
public static readonly DependencyProperty RenderedHeightProperty = DependencyProperty.Register(
"RenderedHeight", typeof (double), typeof (CustomControl), new PropertyMetadata(default(double)));
public double RenderedHeight
{
get { return (double) GetValue(RenderedHeightProperty); }
set { SetValue(RenderedHeightProperty, value); }
}
}
generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Custom_Control_Pushing_ActualWidth">
<Style TargetType="{x:Type local:CustomControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl}">
<Grid local:SizeObserver.Observe="True"
local:SizeObserver.ObservedWidth="{Binding RenderedWidth, RelativeSource={RelativeSource TemplatedParent}}"
local:SizeObserver.ObservedHeight="{Binding RenderedHeight, RelativeSource={RelativeSource TemplatedParent}}">
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
viewmodel.cs
class ViewModel
{
private double width;
private double height;
public double Width
{
get { return width; }
set
{
width = value;
Console.WriteLine("Width: {0}", value);
}
}
public double Height
{
get { return height; }
set
{
height = value;
Console.WriteLine("Height: {0}", value);
}
}
}
mainwindow.xaml
<Window x:Class="Custom_Control_Pushing_ActualWidth.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Custom_Control_Pushing_ActualWidth"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<local:CustomControl RenderedWidth="{Binding Width, Mode=OneWayToSource}" RenderedHeight="{Binding Height, Mode=OneWayToSource}" />
</Grid>
</Window>
And I use SizeObserver from this SO answer.
However although I see the code in the dependency property being updated in the size observer, the bound viewmodel property's setter doesn't get set with the values. Something's wrong with my binding and I don't know what it is.
How can I correctly bind the DependencyProperty with the ViewModel's property?

Change this:
<Grid local:SizeObserver.Observe="True"
local:SizeObserver.ObservedWidth="{Binding RenderedWidth, RelativeSource={RelativeSource TemplatedParent}}"
local:SizeObserver.ObservedHeight="{Binding RenderedHeight, RelativeSource={RelativeSource TemplatedParent}}">
</Grid>
To this:
<Grid local:SizeObserver.Observe="True"
local:SizeObserver.ObservedWidth="{Binding RenderedWidth, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWayToSource}"
local:SizeObserver.ObservedHeight="{Binding RenderedHeight, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWayToSource}">
</Grid>
So add Mode=OneWayToSource to the end of the binding.
This way the Width property in the ViewModel is properly updating for me.
The reason behind this is not entirely clear for me, but I think that the default binding mode of the ObservedWidth and ObservedHeight attached properties are OneWay. So they only update the target properties (ObservedWidth, ObservedHeight) when the source properties (RenderedWidth, RenderedHeight) change.
You want the exact opposite.
With the OneWayToSource modification the changes in the ActualWidth and ActualHeight properties will nicely propagate up to your ViewModel.

Related

XAML x:Class Generic

I have this class
public class FeatureTabBase<T> : UserControl, IFeatureTab<T>
where T : BaseModel
{
public string TabGuid { get; set; }
public T FeaturedElement
{
get { return (T)GetValue(FeaturedElementProperty); }
set { SetValue(FeaturedElementProperty, value); }
}
// Using a DependencyProperty as the backing store for FeaturedElement. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FeaturedElementProperty =
DependencyProperty.Register("FeaturedElement", typeof(T), typeof(FeatureTabBase<T>), new PropertyMetadata(null));
}
That implements this interface
public interface IFeatureTab<T>
where T : class
{
T FeaturedElement { get; set; }
string TabGuid { get; set; }
}
And this instance from it
public partial class MyClass : FeatureTabBase<MyType>
{
public MyClass()
{
InitializeComponent();
}
}
But don't know how instantiate it on XAML
All I'm trying to do is a generic console that can show some pages for my different kind of items.
I was reading about x:TypeArguments at
https://learn.microsoft.com/en-us/dotnet/desktop-wpf/xaml-services/xtypearguments-directive
But nothing works.
Any Ideas?
Add x:TypeArguments to the UserControl declaration in XAML:
<ctr:FeatureTabBase
x:Class="YourNamespace.MyClass"
x:TypeArguments="local:MyType"
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:ctr="clr-namespace:YourNamespace"
xmlns:local="clr-namespace:YourNamespace"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
...
</ctr:FeatureTabBase>
Here's a control I use which will at least illustrate the concept.
Obviously, this is not going to be cut and paste for whatever it is you have in mind.
An editrow allows me to easily line up a series of labelled controls inside a stackpanel, and add various standardised functionality to the controls I make content.
public class EditRow : ContentControl
{
public string LabelFor
{
get { return (string)GetValue(LabelForProperty); }
set { SetValue(LabelForProperty, value); }
}
public static readonly DependencyProperty LabelForProperty = DependencyProperty.RegisterAttached(
"LabelFor",
typeof(string),
typeof(EditRow));
public string LabelWidth
{
get { return (string)GetValue(LabelWidthProperty); }
set { SetValue(LabelWidthProperty, value); }
}
public static readonly DependencyProperty LabelWidthProperty = DependencyProperty.RegisterAttached(
"LabelWidth",
typeof(string),
typeof(EditRow)
);
public string PropertyWidth
{
get { return (string)GetValue(PropertyWidthProperty); }
set { SetValue(PropertyWidthProperty, value); }
}
public static readonly DependencyProperty PropertyWidthProperty = DependencyProperty.RegisterAttached(
"PropertyWidth",
typeof(string),
typeof(EditRow)
);
public EditRow()
{
this.IsTabStop = false;
}
}
I template this in a resource dictionary. ( There are other options including custom control generic xaml)
<Style TargetType="{x:Type spt:EditRow}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type spt:EditRow}">
<Grid Height="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding RelativeSource={
RelativeSource FindAncestor,
AncestorType=spt:EditRow},
Path=LabelWidth, TargetNullValue=2*}"/>
<ColumnDefinition Width="{Binding RelativeSource={
RelativeSource FindAncestor,
AncestorType=spt:EditRow},
Path=PropertyWidth, TargetNullValue=3*}"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding RelativeSource={
RelativeSource FindAncestor,
AncestorType=spt:EditRow},
Path=LabelFor}"
HorizontalAlignment="Right"
TextAlignment="Right"
Margin="2,4,0,2"/>
<Border Padding="8,2,8,2" Grid.Column="1" BorderThickness="0">
<ContentPresenter>
<ContentPresenter.Resources>
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource ErrorToolTip}"/>
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource ErrorToolTip}"/>
<Style TargetType="{x:Type DatePicker}" BasedOn="{StaticResource ErrorToolTip}"/>
</ContentPresenter.Resources>
</ContentPresenter>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Usage:
<ItemsControl>
<spt:EditRow LabelFor="Name:" >
<TextBox Text="{Binding EditVM.TheEntity.CustomerName,
UpdateSourceTrigger=PropertyChanged,
NotifyOnSourceUpdated=True,
NotifyOnValidationError=True,
Mode=TwoWay}" />
</spt:EditRow>
<spt:EditRow LabelFor="Address:" >
<TextBox Text="{Binding EditVM.TheEntity.Address1,
UpdateSourceTrigger=PropertyChanged,
NotifyOnSourceUpdated=True,
NotifyOnValidationError=True,
Mode=TwoWay}" />
</spt:EditRow>
Notice that I have a TextBox as content of each of those editrows, but it could be a datepicker or whatever.
You could bind that content. Then use datatype on viewmodel type for variable datatemplates.
XAML doesnt support generics, so the ctrl:FeatureTabBase will never work. Also, you cannot inherit the XAML part of a UserControl if you derive a new class from an existing UserControl.
You can't use strongly-typed DataTemplates as they only hook up to the concrete class specified in the type. You need to take a different approach. Maybe simplify ?
public class FeatureTab : UserControl
{
public static readonly DependencyProperty FeaturedElementProperty =
DependencyProperty.Register(
"FeaturedElement",
typeof(ModelBase),
typeof(FeatureTab)
, new PropertyMetadata(null));
public string TabGuid { get; set; }
public ModelBase FeaturedElement
{
get => (ModelBase) GetValue(FeaturedElementProperty);
set => SetValue(FeaturedElementProperty, value);
}
}
NOTE This answer is valid for .Net frameworks prior to 4.7, if you point your project to .Net framework 4.7.2 Generics must work on xaml.

binding to nested object from the parent user control wpf

I am writing a new user control. It needs to be able to display an ObservableCollection of items. Those items will have a property that is also an observable collection, so it is similar to a 2-d jagged array. The control is similar to a text editor so the outer collection would be the lines, the inner collection would be the words. I want the consumer of the control to be able to specify not only the binding for the lines, but also the binding for the words. The approach I have so far is as follows:
The user control inherits from ItemsControl. Inside this control it has a nested ItemsControl. I would like to be able to specify the binding path of this nested ItemsControl from the parent user control. The XAML for the UserControl is
<ItemsControl x:Class="IntelliDoc.Client.Controls.TextDocumentEditor"
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:IntelliDoc.Client"
xmlns:con="clr-namespace:IntelliDoc.Client.Controls"
xmlns:data="clr-namespace:IntelliDoc.Data;assembly=IntelliDoc.Data"
xmlns:util="clr-namespace:IntelliDoc.Client.Utility"
xmlns:vm="clr-namespace:IntelliDoc.Client.ViewModel"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
x:Name="root"
d:DesignHeight="300" d:DesignWidth="300"
>
<ItemsControl.Template>
<ControlTemplate>
<StackPanel Orientation="Vertical">
<ItemsPresenter Name="PART_Presenter" />
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate >
<StackPanel Orientation="Horizontal">
<ItemsControl Name="PART_InnerItemsControl" ItemsSource="{Binding NestedBinding, ElementName=root}" >
<ItemsControl.Template>
<ControlTemplate>
<StackPanel Name="InnerStackPanel" Orientation="Horizontal" >
<TextBox Text="" BorderThickness="0" TextChanged="TextBox_TextChanged" />
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<ContentControl Content="{Binding Path=Data, Mode=TwoWay}" />
<TextBox BorderThickness="0" TextChanged="TextBox_TextChanged" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
The code behind has this property declared
public partial class TextDocumentEditor : ItemsControl
{
public static readonly DependencyProperty NestedItemsProperty = DependencyProperty.Register("NestedItems", typeof(BindingBase), typeof(TextDocumentEditor),
new PropertyMetadata((BindingBase)null));
public BindingBase NestedItems
{
get { return (BindingBase)GetValue(NestedItemsProperty); }
set
{
SetValue(NestedItemsProperty, value);
}
}
...
}
The expected bound object will be as follows:
public class ExampleClass
{
ObservableCollection<InnerClass> InnerItems {get; private set;}
}
public class InnerClass : BaseModel //declares OnPropertyChanged
{
private string _name;
public string Name //this is provided as an example property and is not required
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
....
}
public class ViewModel
{
public ObservableCollection<ExampleClass> Items {get; private set;}
}
The XAML declaration would be as follows:
<Window x:Class="IntelliDoc.Client.TestWindow"
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:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="TestWindow" Height="300" Width="300">
<DockPanel>
<TextDocumentEditor ItemsSource="{Binding Path=Items}" NestedItems={Binding Path=InnerItems} >
<DataTemplate>
<!-- I would like this to be the user defined datatemplate for the nested items. Currently I am just declaring the templates in the resources of the user control by DataType which also works -->
</DataTemplate>
</TextDocumentEditor>
</DockPanel>
In the end, I want the user control I created to provide the ItemsControl template at the outer items level, but I want the user to be able to provide the datatemplate at the inner items control level. I want the consumer of the control to be able to provide the bindings for both the Outer items and the nested items.
I was able to come up with a solution that works for me. There may be a better approach, but here is what I did.
First, on the outer ItemsControl, I subscribed to the StatusChanged of the ItemContainerGenerator. Inside that function, I apply the template of the ContentPresenter and then search for the Inner ItemsControl. Once found, I use the property NestedItems to bind to the ItemsSource property. One of the problems I was having originally was I was binding incorrectly. I fixed that and I changed the NestedItems to be a string. Also, I added a new property called NestedDataTemplate that is of type DataTemplate so that a user can specify the DataTemplate of the inner items control. It was suggested that I not use a UserControl since I don't inherit from a UserControl, so I will change it to a CustomControl. The code changes are below
public static readonly DependencyProperty NestedItemsProperty = DependencyProperty.Register("NestedItems", typeof(string), typeof(TextDocumentEditor),
new PropertyMetadata((string)null));
public static readonly DependencyProperty NestedDataTemplateProperty = DependencyProperty.Register("NestedDataTemplate", typeof(DataTemplate), typeof(TextDocumentEditor),
new PropertyMetadata((DataTemplate)null));
public DataTemplate NestedDataTemplate
{
get { return (DataTemplate)GetValue(NestedDataTemplateProperty); }
set
{
SetValue(NestedDataTemplateProperty, value);
}
}
public string NestedItems
{
get { return (string)GetValue(NestedItemsProperty); }
set
{
SetValue(NestedItemsProperty, value);
}
}
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if (((ItemContainerGenerator)sender).Status != GeneratorStatus.ContainersGenerated)
return;
ContentPresenter value;
ItemsControl itemsControl;
for (int x=0;x<ItemContainerGenerator.Items.Count; x++)
{
value = ItemContainerGenerator.ContainerFromIndex(x) as ContentPresenter;
if (value == null)
continue;
value.ApplyTemplate();
itemsControl = value.GetChildren<ItemsControl>().FirstOrDefault();
if (itemsControl != null)
{
if (NestedDataTemplate != null)
itemsControl.ItemTemplate = NestedDataTemplate;
Binding binding = new Binding(NestedItems);
BindingOperations.SetBinding(itemsControl, ItemsSourceProperty, binding);
}
}
}

Update Normal Property in Dependency property/AttachedProperty,

I am trying to bind a normal property of AvalonDock,
xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock"
<xcad:LayoutAnchorable Title="Folder" CanHide="{Binding IsHideExplorerView}">
<Views:ExplorerView DataContext="{Binding ExplorerViewModel}"/>
</xcad:LayoutAnchorable>
Here CanHide is a Normal property, if trying to bind will throw the exception like
A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
My question is, Is it possible any way to make a normal property to override DependencyProperty to make it Bindable.
Edit
Added a class which inherit LayoutAnchorable but PropertyChangedCallback of DependencyProperty Never calls.
public class ExtendedAnchorableItem : LayoutAnchorable
{
public static readonly DependencyProperty IsCanHideProperty =
DependencyProperty.RegisterAttached("IsCanHide", typeof(bool), typeof(ExtendedAnchorableItem),
new FrameworkPropertyMetadata((bool)false,
new PropertyChangedCallback(OnCanHideChanged)));
public bool IsCanHide
{
get { return (bool)GetValue(IsCanHideProperty); }
set { SetValue(IsCanHideProperty, value);
this.IsVisible = value; // No effect.
}
}
private static void OnCanHideChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ExtendedAnchorableItem)d).Hide();
}
}
XAML
<xcad:LayoutAnchorablePane>
<Utility:ExtendedAnchorableItem IsCanHide="{Binding IsHideExplorer}">
<Views:ExplorerView DataContext="{Binding ExplorerViewModel}"/>
</Utility:ExtendedAnchorableItem>
</xcad:LayoutAnchorablePane>
Similarly i have tried creating an AttachedProperty which can hook it to LayoutAnchorable but PropertyChangedCallback Never get called click here for a new question i have posted.
Any Help guys ?
I did and example previously in my case i need to create new button with 2 images one when the button is available and the other one when it's disabled, to do that first i created new user control named "MyButton" my xaml was like this
<Button ToolTip="{Binding ButtonLabel,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"
Command="{Binding ButtonCommand,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"
Cursor="Hand" VerticalAlignment="Center" >
<Button.Template>
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="45"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Name="ButtonImage" IsEnabled="{Binding Path=IsEnabled,RelativeSource={RelativeSource AncestorType=Button,Mode=FindAncestor}}" >
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Source" Value="{Binding ActiveImage,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Source" Value="{Binding DeactiveImage,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"/>
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Label Name="LabelContent" Content="{Binding ButtonLabel,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor},UpdateSourceTrigger=PropertyChanged}"
Grid.Column="1" IsEnabled="{Binding Path=IsEnabled,RelativeSource={RelativeSource AncestorType=Button,Mode=FindAncestor}}" VerticalContentAlignment="Center" />
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
then i added dependency Properties for ActiveImage and DeactiveImage using this code
public static DependencyProperty activeImage =
DependencyProperty.Register("ActiveImage", typeof(type of this property like "string"), typeof(type of the custom control that you need like "MyButton"), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string ActiveImage
{
get { return (string)GetValue(activeImage); }
set { SetValue(activeImage, value); }
}
then i used this new control in my project
<custom:MyButton ButtonCommand="{Binding DecreaseImagesCount}" ButtonLabel="ZoomIn" ActiveImage="/Images/ActiveImages/ZoomIn.png" DeactiveImage="/Images/GrayImages/ZoomIn.png"
Grid.Column="2" Margin="3,4" />
notice that i can do binding the path for Button Image now
If it is enough for you to just set that property from your view model then you could use an attached behavior.
Just create a new class and add an attached property like this (I did not really test this, since I actually do not have AvalonDock at hand, but you should get the idea):
public class YourBehavior
{
public static readonly DependencyProperty YourCanHideProperty = DependencyProperty.RegisterAttached(
"YourCanHide",
typeof(bool),
typeof(LayoutAnchorable),
new PropertyMetadata(YourCanHidePropertyChanged));
private static void YourCanHidePropertyChanged(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
LayoutAnchorable control = dependencyObject as LayoutAnchorable;
if (control != null)
{
control.CanHide = e.NewValue as bool;
}
}
public static bool GetYourCanHideProperty(LayoutAnchorablewindow)
{
return window.GetValue(YourProperty) as bool?;
}
public static void SetYourCanHideProperty(LayoutAnchorable control, bool value)
{
window.SetValue(YourProperty, value);
}
}
Now you should be able to use that behavior like this:
<xcad:LayoutAnchorable Title="Folder" namespacealias:YourBehavior.YourCanHideProperty="{Binding IsHideExplorerView}"/>
If you want to have it working in both directions just check out the attached Blend behaviors.
Yes, you can do it.. you need to implement INotifypropertyChanged interface and raise a ProprtyChanged Event inside the property setter. After changing the property to a DependencyProperty, you will get the notification mechanism, so the property change is propagated to the target (in this case xcad) .
you can find lot of examples implementing the INotifyPropertyChanged..

WPF binding UserControl to MainWindow as parent is failed

I am newbie in XAML Databinding and I am stuck in this situation. I am using Mahapps MetroWindow.
Suppose that I have a UserControl named usrctrl_Camera_Control. I have a simple button there. The C# code is given below.
namespace TA141501005
{
public partial class usrctrl_Camera_Control : UserControl
{
public usrctrl_Camera_Control()
{
this.DataContext = this;
InitializeComponent();
}
}
The XAML is given below
<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:Custom="http://metro.mahapps.com/winfx/xaml/controls" xmlns:local="clr-namespace:TA141501005" x:Class="TA141501005.usrctrl_Camera_Control"
mc:Ignorable="d"
d:DesignHeight="768" d:DesignWidth="1366" Background="#FF2B2B2B" Height="738" Width="1336">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Resources/Icons.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Button x:Name="btn_Test" Content="Button" Grid.Column="1" HorizontalAlignment="Left" Margin="472,59,0,0" VerticalAlignment="Top" Width="75" Grid.RowSpan="2" IsEnabled="{Binding Path=IsNyetEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
</Grid>
The C# code for MainWindow is given below.
namespace TA141501005
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : MetroWindow
{
usrctrl_Camera_Control usrctrl_camera_control;
public bool IsNyetEnabled { get; set; }
public MainWindow()
{
IsNyetEnabled = false;
this.DataContext = this;
InitializeComponent();
usrctrl_camera_control = new usrctrl_Camera_Control();
}
}
}
and the XAML code for MainWindow is given below.
<Controls:MetroWindow x:Class="TA141501005.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
Title="Macromium System Control V1.0ES - TA141501005 [Engineering Sample]" Height="768" Width="1366" Background="#FF2B2B2B" ScrollViewer.VerticalScrollBarVisibility="Disabled" ResizeMode="NoResize" WindowStyle="ThreeDBorderWindow" WindowStartupLocation="CenterScreen" IsMinButtonEnabled="False" IsWindowDraggable="False" ShowMaxRestoreButton="False" ShowMinButton="False" ShowSystemMenuOnRightClick="False" IconOverlayBehavior="Flyouts">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Resources/Icons.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Button x:Name="btn_test" Content="Button" HorizontalAlignment="Left" Margin="589,590,0,0" VerticalAlignment="Top" Width="75" IsEnabled="{Binding IsNyetEnabled}"/>
</Grid>
I want to binding the property IsEnabled in btn_test in usrctrl_Camera_Control and btn_test in MainWindow into IsNyetEnabled in MainWindow. I set the IsNyetEnabled into false before I do InitializeComponent() in MainWindow.
The binding between btn_test.IsEnabled in MainWindow into IsNyetEnabled in MainWindow is done flawlessly. The btn_test in MainWindow is no longer enabled. (I know, I need to implement INotifyPropertyChanged to notify the subsriber if there is any change but for now, just leave it as is for simplicity).
But, the binding between btn_test.IsEnabled in usrctrl_Camera_Control into IsNyetEnabled in MainWindow is failed. I have used Visual Studio "Create Data Binding" wizard but it always return error when compiling.
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='TA141501005.MainWindow', AncestorLevel='1''. BindingExpression:Path=IsNyetEnabled; DataItem=null; target element is 'Button' (Name='btn_Test'); target property is 'IsEnabled' (type 'Boolean')
Do you have any suggestion? I have tried for whole day without a luck.
Is there any way to access parent datacontext without removing this.Datacontext = this?
Looking forward for your suggestion and explanation.
Thank you very much.
Edit.
I display my UserControl via Flyout.
Window parentWindow = Window.GetWindow(this);
object obj = parentWindow.FindName("mainFlyout");
Flyout flyout = (Flyout) obj;
flyout.Content = new SomeFlyOutUserControl();
flyout.IsOpen = !flyout.IsOpen;
Create the IsNyetEnabled property as a dependency property on the MainForm. This is the mechanism that will propagate changes in WPF
Example:
public static readonly DependencyProperty IsNyetEnabledProperty =
DependencyProperty.Register("IsNyetEnabled", typeof(bool),
typeof(MainWindow),new FrameworkPropertyMetadata(false));
public bool IsNyetEnabled
{
get { return (bool)GetValue(MainWindow.IsNyetEnabled); }
set { SetValue(MainWindow.IsNyetEnabled, value); }
}
To that you can bind your Button in the MainWindow directly.
For the UserControl though we have to add a shim, because the UserControl won't be able to find the ancestor when you create it.
Create on your UserControl like Moez posted another DependencyProperty:
public static readonly DependencyProperty IsButtonEnabledProperty =
DependencyProperty.Register("IsButtonEnabled", typeof(bool),
typeof(usrctrl_Camera_Control),new FrameworkPropertyMetadata(false));
public bool IsButtonEnabled
{
get { return (bool)GetValue(usrctrl_Camera_Control.IsButtonEnabledProperty); }
set { SetValue(usrctrl_Camera_Control.IsButtonEnabledProperty, value); }
}
Bind your UserControl button IsEnabled property to that.
Now as a last step we will have to connect the two Dependency Properties when you create your UserControl.
<local:usrctrl_Camera_Control x:Name="yourControl" ...Whatever else... IsButtonEnabled="{Binding Path=IsNyetEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}} />
This should work now.
I suggest you use DependencyProperty :
public static readonly DependencyProperty IsButtonEnabledProperty = DependencyProperty.Register(
"IsButtonEnabled", typeof(bool), typeof(UserControl1), new PropertyMetadata(default(bool)));
public bool IsButtonEnabled
{
get { return (bool)GetValue(IsButtonEnabledProperty); }
set { SetValue(IsButtonEnabledProperty, value); }
}
// Here Rename your UserControl To Test
<Grid>
<Button x:Name="btn_Test" Content="Button" Grid.Column="1" HorizontalAlignment="Left" Margin="472,59,0,0" VerticalAlignment="Top" Width="75" Grid.RowSpan="2" IsEnabled="{Binding Path=IsButtonEnabled, ElementName=Test}"}" />
</Grid>
As #Frank J suggested, I modify his last step.
I create the usercontrol during runtime, not from the XAML. Therefore it would be look like this for the last step.
usrctrl_camera_control = new usrctrl_Camera_Control();
Binding b = new Binding("IsNyetEnabled");
b.Source = this;
b.Mode = BindingMode.OneWay;
usrctrl_camera_control.SetBinding(usrctrl_Camera_Control.IsButtonEnabledProperty, b);
Thanks all.. I hope it would be useful for those who have the same problem with me.

Data Binding in WPF User Controls

I am creating a UserControl for a series of controls shared by several windows. One of the controls is a Label which shows the flow of some other process in terms of "protocol numbers".
I am trying to offer DataBinding with this Label so the Window automatically reflects the state of the process as the protocol number variable changes.
This is the User control XAML:
<UserControl Name="MainOptionsPanel"
x:Class="ExperienceMainControls.MainControls"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
>
<Label Height="Auto" Name="numberLabel">Protocol:</Label>
<Label Content="{Binding Path=ProtocolNumber}" Name="protocolNumberLabel"/>
(...)
</UserControl>
And this is the Code-Behind:
public partial class MainControls
{
public MainControls()
{
InitializeComponent();
}
public int ProtocolNumber
{
get { return (int)GetValue(ProtocolNumberProperty); }
set { SetValue(ProtocolNumberProperty, value); }
}
public static DependencyProperty ProtocolNumberProperty =
DependencyProperty.Register("ProtocolNumber", typeof(int), typeof(MainControls));
}
This seems to be working because if on the constructor I set ProtocolNumber to an arbitrary value, it is reflected in the user control.
However, when using this usercontrol on the final window, the data binding breaks.
XAML:
<Window x:Class="UserControlTesting.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:expControl="clr-namespace:ExperienceMainControls;assembly=ExperienceMainControls"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
>
<StackPanel>
<expControl:MainControls ProtocolNumber="{Binding Path=Number, Mode=TwoWay}" />
</StackPanel>
</Window>
Code-Behind for window:
public partial class Window1 : Window
{
public Window1()
{
Number= 15;
InitializeComponent();
}
public int Number { get; set; }
}
This sets the Protocol Number to zero, ignoring the value set to Number.
I've read example
if you look at your output window you should see the binding exception.
The problem you have is the following: within your usercontrol you will bind the label to the DP ProtocolNumber of your usercontrol and not the DataContext, so you have to add for example the element name to the binding.
<UserControl Name="MainOptionsPanel"
x:Class="ExperienceMainControls.MainControls"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="uc"
>
<Label Height="Auto" Name="numberLabel">Protocol:</Label>
<Label Content="{Binding Path=ProtocolNumber, ElementName=uc}" Name="protocolNumberLabel"/>
(...)
</UserControl>
EDIT: to clear some things up, your usercontrol also works if you change the binding in your MainWindow. but you have to bind to the DataContext of the MainWindow with RelativeSource.
<expControl:MainControls ProtocolNumber="{Binding Path=Number, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
What you have in effect:
<expControl:MainControls DataContext="{Binding RelativeSource={RelativeSource Self}}"
ProtocolNumber="{Binding Path=Number, Mode=TwoWay}"/>
=> Do not set the DataContext in UserControl declarations, use RelativeSource or ElementName bindings instead.
If you're not specifying the RelativeSource of the binding, try set the DataContext in the constructor:
public Window1()
{
Number= 15;
DataContext = this;
InitializeComponent();
}

Categories