WPF binding to property of non-simple property of other class - c#

A little bit can't figure out how to use WPF binding in this case:
Assume, we have an object Car with non-simple property of type CarInfo:
public class CarInfo : DependencyObject
{
public static readonly DependencyProperty MaxSpeedProperty =
DependencyProperty.Register("MaxSpeed", typeof (double), typeof (CarInfo), new PropertyMetadata(0.0));
public double MaxSpeed
{
get { return (double) GetValue(MaxSpeedProperty); }
set { SetValue(MaxSpeedProperty, value); }
}
}
public class Car : DependencyObject
{
public static readonly DependencyProperty InfoProperty =
DependencyProperty.Register("Info", typeof (CarInfo), typeof (Car), new PropertyMetadata(null));
public CarInfo Info
{
get { return (CarInfo) GetValue(InfoProperty); }
set { SetValue(InfoProperty, value); }
}
}
Also assume, Car is an ui element and it has the Car.xaml, something simple:
<Style TargetType="assembly:Car">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="assembly:Car">
<Grid >
!--> <TextBlock Text="{Binding Path=MaxSpeed}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
So, I wanted this TextBlock, in my Car.xaml, to represent the property "MaxSpeed" of my CarInfo class, which is actually a property of my Car class. How can I do this?
Thank you in advance, appreciate any help! :)

It depends upon what is assigned to the DataCOntext of the UI element representing the Car - you need to specify a binding path relative to that. In this case I would suggest you start with this:
<TextBlock Text="{Binding Path=Info.MaxSpeed}" />
this is assuming that a Car object has been assigned to the DataContext of the Car UI element.
Note that your properties don't have to be dependency properties - you can also bind to normal properties (depending on what you are doing).
Edit
It seems you are looking to use element binding, so you should be able to achieve what you want by using either the TemplatedParent or an ancestor as your relative source. See this previous SO answer for an example. Your binding should look something like this:
<TextBlock Text="{Binding Path=Info.MaxSpeed, RelativeSource={RelativeSource TemplatedParent}}" />
This will take you back to your templated parent control (the Car), then travel down the Info property of the UI element to the MaxSpeed property on its contents.
As I said in my comment, you are making this very messy by having your UI element so closely match your data element and then assigning your data object to a relatively non standard property on the UI element. You might have your reasons, but XAML and WPF don't need to be that complicated.

<TextBlock Text="{Binding Path=Info.MaxSpeed}" />

That code works well for me:
<TextBlock Text="{Binding Path=Info.MaxSpeed, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
and usage:
Car.Info = new CarInfo { MaxSpeed = 100.0 };

Related

Derive from IconElement or IconSourceElement in UWP

I'm trying to create the same custom symbol icon control as MS SymbolIcon, which will get the enum symbol values as input, and the equivalent path data value will be retrieved from the dictionary<Symbol, string> collection. But the symbol icon class inherited from IconElement and the same below issue faced in my application.
'IconElement does not take a constructor that takes 0 arguments'
Derive from IconElement in UWP
but I have marked my constructor as extern and enclosed with semicolon to resolve the constructor issue.
public class CustomSymbolIcon : IconElement
{
public extern CustomSymbolIcon();
}
But my question is, I can get input from the end user as Symbol Enum and retrieved equivalent path geometry based on input from the stored dictionary. But I couldn't bind the path geometry to the path element(Targeted Custom icon class)and I can't write the template style for this class. Because IconElement was derived from the framework element.
I can achieve these all with control class , but I can't use this inside the <NavigationView.Icon> (its a IconElement base) tag due to base class.
public class SymbolToIconConversion : Control //IconElement instead of control
{
internal static Dictionary<Symbol, string> enumValuesCollection = new Dictionary<Symbol, string>();
public SymbolToIconConversion()
{
this.DefaultStyleKey = typeof(SymbolToIconConversion);
PopulateEnumCollection();
}
public static Dictionary<Symbol, string> EnumValuesCollection
{
get { return enumValuesCollection; }
set { enumValuesCollection = value; }
}
internal void PopulateEnumCollection()
{
enumValuesCollection.Add(Symbol.Accept, "M0,4 5,9 9,0 4,5");
enumValuesCollection.Add(Symbol.Close, "F1 M 22,12L 26,12L 26,22L 36,22L 36,26L 26,26L 26,36L 22,36L 22,26L 12,26L 12,22L 22,22L 22,12 Z");
enumValuesCollection.Add(Symbol.Save, "M0,4 5,9 9,0 4,5");
enumValuesCollection.Add(Symbol.Add, "M0,5 H10 M5,5 V10Z");
}
public Symbol Symbol
{
get { return (Symbol)GetValue(SymbolProperty); }
set { SetValue(SymbolProperty, value); }
}
// Using a DependencyProperty as the backing store for Symbol. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SymbolProperty =
DependencyProperty.Register("Symbol", typeof(Symbol), typeof(SfSymbolIcon), new PropertyMetadata(typeof(Symbol), new PropertyChangedCallback(OnSymbolChanged)));
internal Geometry Geometry
{
get { return (Geometry)GetValue(GeometryProperty); }
set { SetValue(GeometryProperty, value); }
}
// Using a DependencyProperty as the backing store for Geometry. This enables animation, styling, binding, etc...
internal static readonly DependencyProperty GeometryProperty =
DependencyProperty.Register("Geometry", typeof(Geometry), typeof(SymbolToIconConversion), new PropertyMetadata(null));
private static void OnSymbolChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SymbolToIconConversion symbolIcon = d as SymbolToIconConversion;
if (symbolIcon != null)
{
foreach (var value in EnumValuesCollection)
{
if (symbolIcon.Symbol == value.Key)
{
symbolIcon.Geometry = (Geometry)XamlBindingHelper.ConvertValue(typeof(Geometry), value.Value);
return;
}
}
}
}
<Style TargetType="core:SymbolToIconConversion">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="core:SymbolToIconConversion">
<Viewbox x:Name="ContentViewbox" AutomationProperties.AccessibilityView="Raw" HorizontalAlignment="Stretch" Height="{ThemeResource AppBarButtonContentHeight}" Margin="{ThemeResource AppBarButtonContentViewboxCollapsedMargin}">
<Path x:Name="Content"
Width="{Binding Width, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Height="{Binding Height, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Fill="{Binding Foreground, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Data="{Binding Geometry, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
</Viewbox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
How to initialize dictionary in constructor of custom class? - Need to populate the dictionary when control loaded. I can't call this method in extern constructor.
If possible, path geometry retrieval using symbol achieved by Dictionary<Symbol, String> collection populated. Is this efficient way?,Bcz it leads to key already added in collection issue when initialize the control at second time. Please suggest alternate ways to achieve this.
How can write style for framework element? I need to bind the Path data in control style. But it doesn't have template property.
Can anyone suggest how to achieve this?
Derive from IconElement or IconSourceElement in UWP
I'm afraid you can't make CustomSymbolIcon that inherit IconElement, and IconElement does not provide method to set ControlTemplate, for your scenario, we suggest you use custom Datatemplate to replace NavigationViewItem like the following
<NavigationView.MenuItemTemplate>
<DataTemplate>
<Grid Width="{Binding ElementName=nvSample, Path=OpenPaneLength}" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<local:SymbolToIconConversion Symbol="Accept" VerticalAlignment="Center"/>
<StackPanel
Grid.Column="1"
Margin="45,0,10,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<TextBlock x:Name="Header" Text="Header" />
<TextBlock x:Name="Line1" Text="TheFirstLine" />
</StackPanel>
</Grid>
</DataTemplate>
</NavigationView.MenuItemTemplate>

Binding to Nested Element in Attached Property in WPF

I am trying to bind an element nested inside of an attached property to my DataContext, but the problem is that the attached property is not part of the logical tree and therefore does not properly set or bind to the data context of the parent object. The dependency property, in this case Value, is always null.
Here is some example XAML
<StackPanel>
<!-- attached property of static class DataManager -->
<local:DataManager.Identifiers>
<local:TextIdentifier Value="{Binding Path=MyViewModelString}" />
<local:NumericIdentifier Value="{Binding Path=MyViewModelInt}" />
<local:NumericIdentifier Value="{Binding Path=SomeOtherInt}" />
</local:DataIdentifiers>
<!-- normal StackPanel items -->
<Button />
<Button />
</StackPanel>
Due to the implementation, this cannot be a single attached property - it needs to be a collection that allows for n entities. Another acceptable solution would be to put the identifiers directly in the node, but I don't think this syntax is possible without including these element explicitly in the logical tree. i.e...
<Button>
<local:NumericIdentifier Value="{Binding}" />
<local:TextIdentifier Value="{Binding}" />
<TextBlock>Actual button content</TextBlock>
</Button>
Here is the start of the implementation of DataManager.
[ContentProperty("IdentifiersProperty")]
public static class DataManager
{
public static Collection<Identifier> GetIdentifiers(DependencyObject obj)
{
return (Collection<Identifier>)obj.GetValue(IdentifiersProperty);
}
public static void SetIdentifiers(DependencyObject obj, Collection<Identifier> value)
{
obj.SetValue(IdentifiersProperty, value);
}
public static readonly DependencyProperty IdentifiersProperty =
DependencyProperty.RegisterAttached("Identifiers", typeof(Collection<Identifier>), typeof(DataManager), new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIdentifiersChanged)));
}
I've tried making the base class Identifiers implement Freezable in the hopes that it would for the inheritance of the data and binding context, but that did not have any effect (likely because it is nested inside another layer - the attached property).
A couple more key points:
I would like this to work on any UIElement, not just StackPanels
The Identifiers are not part of the visual tree. They do not and should not have visual elements
as this is an internal library, I would prefer to avoid requiring a Source or RelativeSource to the binding as it is not intuitive that this needs to be done
Is it possible to bind to the inherited DataContext in this layer of the markup? Do I need to manually add these to the logical tree? If so, how?
Thanks!
In addition to having Identifier inherit from Freezable, you will need to also use FreezableCollection instead of Collection<Identifier> as attached property type. This will ensure that inheritance chain is not broken.
public class Identifier : Freezable
{
... // dependency properties
protected override Freezable CreateInstanceCore()
{
return new Identifier();
}
}
Create a custom collection:
public class IdentifierCollection : FreezableCollection<Identifier> { }
And, modify attached property to use this collection:
[ContentProperty("IdentifiersProperty")]
public static class DataManager
{
public static readonly DependencyProperty IdentifiersProperty =
DependencyProperty.RegisterAttached(
"Identifiers",
typeof(IdentifierCollection),
typeof(DataManager),
new FrameworkPropertyMetadata(OnIdentifiersChanged));
...
public static void SetIdentifiers(UIElement element, IdentifierCollection value)
{
element.SetValue(IdentifiersProperty, value);
}
public static IdentifierCollection GetIdentifiers(UIElement element)
{
return element.GetValue(IdentifiersProperty) as IdentifierCollection;
}
}
Sample usage:
<Window.DataContext>
<local:TestViewModel
MyViewModelInt="123"
MyViewModelString="Test string"
SomeOtherInt="345" />
</Window.DataContext>
<StackPanel x:Name="ParentPanel" ... >
<!-- attached property of static class DataManager -->
<local:DataManager.Identifiers>
<local:IdentifierCollection>
<local:TextIdentifier Value="{Binding Path=MyViewModelString}" />
<local:NumericIdentifier Value="{Binding Path=MyViewModelInt}" />
<local:NumericIdentifier Value="{Binding Path=SomeOtherInt}" />
</local:IdentifierCollection>
</local:DataManager.Identifiers>
<!-- normal StackPanel items -->
<TextBlock Text="{Binding Path=(local:DataManager.Identifiers)[0].Value,
ElementName=ParentPanel, StringFormat=Identifer [0]: {0}}" />
<TextBlock Text="{Binding Path=(local:DataManager.Identifiers)[1].Value,
ElementName=ParentPanel, StringFormat=Identifer [1]: {0}}" />
<TextBlock Text="{Binding Path=(local:DataManager.Identifiers)[2].Value,
ElementName=ParentPanel, StringFormat=Identifer [2]: {0}}" />
</StackPanel>

Binding a property defined in code-behind, and another one defined in a class in the same template

I would like to binding a property defined in code-behind, and another one defined in a class in the same template with datatype.
Here's an example:
My class:
public class MyClass
{
public string name { get; set; }
public MyClass(string name)
{
this.name=name;
}
}
Code behind:
public string name2;
public MyView()
{
this.InitializeComponent();
name2 = "Tim";
}
<DataTemplate x:Key="MasterListViewItemTemplate" x:DataType="model:MyClass">
<StackPanel>
<TextBlock Text="{x:Bind name}"/>
<TextBlock Text="{x:Bind name2}"/>
</StackPanel>
</DataTemplate>
In this case obviously the first TextBlock has no problems.
I wish the second TextBlock refers to the code-behind and not in MyClass.
How can I do ?
You should set the second TextBlock's datacontext to be the current window.
I think this can be achieved by using binding expression like this
<TextBlock DataContext="{Binding ElementName=MyView, Path=.}" Text="{x:Bind name2}" />
where MyView in the binding expression is the x:Name property of the MyView window.
EDIT(WPF): This binding should work even for ResourceDictionary entries
<TextBlock DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=.}" Text="{Binding name2}" />
And something important, i see that your example, name2 is just defined in the constructor of the window. The right way to do it should look like this.
public string name2 { get; set; }
public MyView()
{
this.InitializeComponent();
this.name2 = "Tim";
}
I hope this helps.
Try the following:
In constructor of your CodeBehind:
public MyView()
{
this.InitializeComponent();
this.DataContext = this; //set the datacontext here
name2 = "Tim";
}
In your XAML:
<TextBlock Text="{Binding DataContext.name2, ElementName=MyView}"/>
First your binding always looks at the DataContext of itself first and if none is specified is traverses up the tree to owner by owner until a DataContext is assigned. It then looks there for the property to bind to. Since you have put the same property name in both textblocks and haven't specified any other means of binding they are both looking for the property in the same DataContext. In other words they are both looking at your MyClass object.
To make this work you will need to tell the Binding where to look for the property by specifying the binding more discretely.
<TextBlock Text="{Binding Name2, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
This is assuming your DataTemplate is used on an object that is in a MainWindow type. Play with it to get yours.
Also, you will need to change the property in the code behind to be a DependencyProperty (since it's a UIElement this is easiest.)
public string Name2
{
get { return (string)GetValue(Name2Property); }
set { SetValue(Name2Property, value); }
}
public static readonly DependencyProperty Name2Property =
DependencyProperty.Register(nameof(Name2), typeof(string), typeof(MainWindow));
If you do this your DataTemplate will bind to that value.
This is just to answer the question and help you understand it some but not how I would personally design a DataTemplate.

Custom Control DataContext Doesn't Work

I have a Custom Controller that works fine like this:
<Controller:DLBox ID="{Binding SHVM.Selected.ID}" />
I mean binding ID to a property in ViewModel. But when I want to bind it like this:
<ScrollViewer DataContext="{Binding SHVM.Selected}">
<Controller:DLBox ID="{Binding ID}" />
</ScrollViewer>
Binding to DataContext of parent, It doesn't work at all. I have some other Custom Controllers and they do fine, But I don't know what the hell is this one's problem!
This is the controller:
public partial class DLBox : UserControl
{
public static DependencyProperty IDProperty =
DependencyProperty.Register("ID", typeof(int), typeof(DLBox),
new FrameworkPropertyMetadata(0,FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
(o, e) => (o as DLBox).IDPropertyChanged((int)e.NewValue)));
public int ID { get; set; }
private void IDPropertyChanged(int e)
{
ID = e;
}
}
Could someone please tell my what's wrong? Because I'm debugging for 6 hours straight and didn't find anything! Thanks a lot.
‌
‌
UPDATE:
That worked with just adding:
<... DataContext={Binding} .../>
And I don't know why!
Now the real problem is that I want to use this inside 2 ItemsControls and even with DataContext Still doesn't work.
(Just for clarification, I have 2 Lists inside each other. Think about the first one like 10 schools First List, and inside each school there is some studens Second List)
<ItemsControl ItemsSource="{Binding Source={StaticResource Locator}, Path=SHVM.Extra.Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl DataContext="{Binding}" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Controller:DLBox DataContext="{Binding}" ID="{Binding ID}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
<ItemsControl.ItemTemplate>
</ItemsControl>
UPDATE 2:
ID is just a TextBlock In a UserControl. There is nothing that I can show here!
All I did, Just set the Text of TextBlock inside the PropertyCallBack (Didn't use MVVM inside my controllers):
<TextBlock x:Name="txtIDValue"/>
And inside CodeBehind:
private void IDPropertyChanged(string e)
{
ID= e;
txtIDValue.Text = e;
}
There is nothing relevant to this problem, And that's why I couldn't figure it out!
Appreciate any help.
ANSWER:
After 12 hours working on it, I find out that It was an idiotic mistake! I don't know why and when I set DataContext in my Controller's XAML!
Anyway, Thanks.
Your dependency property declaration is wrong, because the getter and setter of the CLR wrapper must call the GetValue and SetValue methods respectively. Besides that, your PropertyChangedCallback is redundant. There is no need to set the property again in a callback that is called when the property value has just been set.
The declaration should look like this:
public static readonly DependencyProperty IDProperty = DependencyProperty.Register(
"ID", typeof(int), typeof(DLBox),
new FrameworkPropertyMetadata(
0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public int ID
{
get { return (int)GetValue(IDProperty); }
set { SetValue(IDProperty, value); }
}

wpf target property dependency

I'm trying to create a simple Bindable property called MyBoolValue in my UserControl class
First, here the xaml
<UserControl x:Class="TMDE.Controls.SimNaoRadioPicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="16"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0" Content="Teste" IsChecked="{Binding Path=MyBoolValue}" x:Name="chk" />
</Grid>
</UserControl>
And here the code-behind:
public partial class SimNaoRadioPicker : UserControl
{
public SimNaoRadioPicker()
{
InitializeComponent();
}
public bool? MyBoolValue
{
get
{
return (bool?)GetValue(MyCustomPropertyProperty);
}
set
{
SetValue(MyCustomPropertyProperty, value);
}
}
// Using a DependencyProperty as the backing store for MyCustomProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyCustomPropertyProperty =
DependencyProperty.Register("MyBoolValue",
typeof(bool?), typeof(SimNaoRadioPicker),
new UIPropertyMetadata(MyPropertyChangedHandler));
public static void MyPropertyChangedHandler(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// Get instance of current control from sender
// and property value from e.NewValue
// Set public property on TaregtCatalogControl, e.g.
((SimNaoRadioPicker)sender).chk.IsChecked = (bool?)e.NewValue;
}
}
Now, when a try to use this control in another Window, like this:
<my:SimNaoRadioPicker x:Name="test" MyBoolValue="{Binding QCV_Localizacao_Reutilizacao}" Height="16" HorizontalAlignment="Left" Margin="287,456,0,0" VerticalAlignment="Top" Width="167" />
the Binding doesnt working, the property QCV_Localizacao_Reutilizacao doesnt get update and vice-versa.
The DataContext of the Window its a class that implements INotifyPropertyChanged, so the
property "QCV_Localizacao_Reutilizacao" should work ok.
Also if I use a regular CheckBox instead of my UserControl, its works okay
What I'm doing wrong?
I would remove the nullable part of the boolean and just make it a boolean, then set binding modes to two way.
There are two major issues -
First, your binding mode needs to be TwoWay which you can achieve in two ways -
Either specifed it to be TwoWay in xaml like this -
<my:SimNaoRadioPicker MyBoolValue="{Binding QCV_Localizacao_Reutilizacao,
Mode=TwoWay}"/>
The drawback with above apporach is that you have to explicitly set the mode whenever you are using the UserControl's instance.
Another approach would be to modify your DP itself to say that it always be bind by default in a TwoWay mode like this using FrameworkPropertyMetadata -
public static readonly DependencyProperty MyCustomPropertyProperty =
DependencyProperty.Register("MyBoolValue",
typeof(bool?), typeof(SimNaoRadioPicker),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
MyPropertyChangedHandler));
Secondly, QCV_Localizacao_Reutilizacao property lies in your Window's DataContext. But, by default any control will look for binding in its own dataContext so you explicilty need to tell it to look into Window's DataContext using RelativeSource like this -
<my:SimNaoRadioPicker MyBoolValue="{Binding QCV_Localizacao_Reutilizacao,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Window}/>

Categories