Bind to parent object in xaml - wpf

I have a hierarcial class like this
Part
SubPart
SubSubPart1
SubSubPart2
I have a control that is populated by SubSubPart and in that control i want to show information about the parent SubPart and Part. I want to use normal binding in xaml to display information about parent part.
Each part has a unique ObjectId as a property, each part has multiple properties that i want to display.
The control only knows about one subsubpart.
I realize that i can write a converter
public object Convert(object value, System.Type targetType, object parameter, CultureInfo culture)
{
if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{ return "Design Part"; }
else
{
IDataService applicationService = ServiceLocator.Current.GetInstance<IDataService>();
IPartItem partItem = applicationService.GetEquipmentFromComponent(value.ToString());
return partItem.PartData.Name;
}
}
and apply it like this
<TextBlock Grid.Row="0" Grid.Column="1"
Text="{Binding Path=ObjectId,Converter={StaticResource partConverter}}" Margin="0,0,10,0">
</TextBlock>
But then i need to write a converter for every property of the parent parts. Any solutions out there.

You could do what you're looking for by using the FindAncestor mode of a RelativeSource binding.
For example the text property of a TextBlock would be the following:
Text="{Binding Path=ObjectId, RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:SubPart}, AncestorLevel=1}
where local would be declared to be the namespace where the class SubPart is declared.
You can follow the same pattern for the Part class, changing the AncestorType and AncestorLevel attributes as needed.

Bind the DataContext of your control using the converter and update your converter to just return the parent part
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding ObjectId}" DataContext="{Binding Converter={StaticResource partConverter}}" Margin="0,0,10,0" />

Related

How to display a different WPF DataTemplate for multiple object types

The following blog post demonstrates how to dynamically display a different DataTemplate depending on which object is displayed in a ListBox:
http://www.thejoyofcode.com/different_datatemplates_for_different_types.aspx
Although this is useful, my particular situation is a bit more challenging.
I have a collection of objects in my main viewmodel:
public IEnumerableCollection<IGenericObject> CurrentObjects
I currently display them in a ComboBox using XAML as follows:
<ComboBox ItemsSource="{Binding CurrentObjects}"
SelectedItem="{Binding SelectedObject,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged }"
DisplayMemberPath="Name"
SelectedIndex="0"/>
I would now like to have a separate panel below the ComboBox which displays properties for each object. However, each object has different properties depending on its concrete type.
For example, a AObject would not only support IGenericObject but IAObject as well, so I want to always display properties that are common to all objects and then dynamically display those that are specific whatever object is currently selected.
I don't know how to set up the bindings and obtain the property values for each specific object type given the collection I have makes use of the common IGenericObject interface.
How can I achieve this?
which displays properties for each object.
Keep in mind that binding at the end of the process is just reflection. One can place property names and if the binding fails, nothing is shown.
However, each object has different properties depending on its concrete type.
Ultimately we are using a converter to hide labels and textblocks depending on the target type contained as the selected item in the combobox.
Example
This example is a person and an employee which share the same attributes such as First, Last and Phone, but the employee has an EmployeeId unlike the person. We want to show the first and last name for both, but if its an employee also show its Id. Also swap out the headers stating if it is an employee or a person.
Last names in combo, two objects, Smith is a person and Man is an employee. So when we have a person the screen shows this:
and when its an employee show this with its ID for the last info:
Xaml
<Page x:Class="WPFStack.Views.BindingCoverterPage"
...
xmlns:local="clr-namespace:WPFStack.Views"
xmlns:model="clr-namespace:WPFStack.Model"
xmlns:converter="clr-namespace:WPFStack.Converters"
.>
<Page.Resources>
<model:People x:Key="people">
<model:Person First="Joe"
Last="Smith"
Phone="303-555-5555" />
</model:People>
<model:Employees x:Key="employeePeople">
<model:Employee First="Omega"
Last="Man"
Phone="303-867-5309"
EmployeeId="90125" />
</model:Employees>
<converter:EmployeeVisiblity x:Key="isEmployeeVisibility"/>
<Style x:Key="LabelStyle" TargetType="Label" >
<Setter Property="Margin" Value="-20,0,0,0"/>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</Page.Resources>
<StackPanel Margin="10"
HorizontalAlignment="Left">
<ComboBox Name="mySelectionCombo"
SelectedItem="{Binding SelectedObject,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged }"
DisplayMemberPath="Last"
SelectedIndex="0"
Width="200">
<ComboBox.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource people}}" />
<CollectionContainer Collection="{Binding Source={StaticResource employeePeople}}" />
</CompositeCollection>
</ComboBox.ItemsSource>
</ComboBox>
<StackPanel Margin="10">
<Label Content="Employee Stats:"
Style="{StaticResource LabelStyle}"
Visibility="{Binding SelectedItem,
ElementName=mySelectionCombo,
Converter={StaticResource isEmployeeVisibility}}" />
<Label Content="Person Stats:"
Style="{StaticResource LabelStyle}"
Visibility="{Binding SelectedItem,
ElementName=mySelectionCombo,
ConverterParameter=Reverse,
Converter={StaticResource isEmployeeVisibility}}" />
<TextBlock Text="{Binding SelectedItem.First, ElementName=mySelectionCombo}" />
<TextBlock Text="{Binding SelectedItem.Last, ElementName=mySelectionCombo}" />
<TextBlock Text="{Binding SelectedItem.EmployeeId, ElementName=mySelectionCombo}"
Visibility="{Binding SelectedItem,
ElementName=mySelectionCombo,
Converter={StaticResource isEmployeeVisibility}}" />
</StackPanel>
</StackPanel>
</Page>
Converter
namespace WPFStack.Converters
{
public class EmployeeVisiblity : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var isVisible = Visibility.Collapsed;
if (value != null)
if (value is Employee)
{
if (parameter == null) // Does not say string "Reverse"
isVisible = Visibility.Visible;
}
else // Value is a person
{
if (parameter != null) // Does say string "Reverse"
isVisible = Visibility.Visible;
}
return isVisible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
}
Usage
Note that what the example is primarily doing is simply binding to a similar (but not the same type of things as your data) for it is process and not the data one needs to focus on for the answer to the question.
Even though I am using a Composite Collection to hold set of data such as yours via ItemsSource, I end up (like you) with a list of different instance objects, just like your data.
So....
Focus on that point on and see how the converter works to determine what to make visible and what not depending on what the combobox has selected.
Here are the following steps which you will need to implement and understand to make it work in your code:
Make a converter like the one in your project. Note the namespace.
Make a xaml import reference and name it converter. My namespace was WPFStack.Converters so bringing it into Xaml made mine xmlns:converter="clr-namespace:WPFStack.Converters".
Create a static instance in Xaml of the converter you created by specifying it in the page resources <converter:EmployeeVisiblity x:Key="isEmployeeVisibility"/>.
Any visual control on the screen you need to hide, on its Visbility property will bind to the current selected item of the combo (which you may need to provide a name for the binding) and also call your converter to determine if it is shown or not such as this xaml code:
<TextBlock Text="{Binding SelectedItem.EmployeeId, ElementName=mySelectionCombo}"
Visibility="{Binding SelectedItem,
ElementName=mySelectionCombo,
Converter={StaticResource isEmployeeVisibility}}" />
That is the takeaway you need to have from this response.

How Binding Converter to a control's property can access other properties of the control

I am trying to bind a value "MaxLines" to the TextBlock's Height property in WP7 app. There is a converter to the binding which is supposed to multiple the LineHeight with the MaxLines and return the expected height. What I am trying to say is I want to control the number of lines being shown in the TextBlock. How will I be able to access the TextBlock's LineHeight property from the converter.
To make this generic I did not want maintain the LineHeights separately or access them from viewModel
Check out this article, Silverlight data binding and value converters, where he explains how to Databind in Silverlight. In the example he uses a ValueConverter with parametervalue.
I think that is what you need, just bind your LineHeight to the parameter. (You can use Blend for that)
You can use the ConverterParameter:
<TextBlock x:Name="MyTextBlock" Height="{Binding ConverterParameter=Height, ElementName=MyTextBlock, Converter={StaticResource SomeConverter}}" Text="{Binding SomeLongText}" />
or pass the whole textblock:
<TextBlock x:Name="MyTextBlock" Height="{Binding Converter={StaticResource ImageFileConverter}, ElementName=DropdownImage}" Text="{Binding SomeLongText}" />
Then inside the controller:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var image = value as TextBlock;
/*do your magic here*/
}

WPF: multiple controls binding to same property

Hello
I'm trying to change several controls' property according to some environment variables and i want to avoid creating a property for each control in the datacontext, so i thought using a converter which sets the property according to control name. Goal is to use one property for all controls:
<Grid.Resources>
<local:NameToStringConverter x:Key="conv" />
</Grid.Resources>
<TextBlock Name="FordPerfect"
Text="{Binding ElementName="FordPerfect" Path=Name, Converter={StaticResource conv}, Mode=OneWay}"/>
<TextBlock Name="Arthur"
Text="{Binding ElementName="Arthur" Path=Name, Converter={StaticResource conv}, Mode=OneWay}"/>
<TextBlock Name="ZaphodBeeblebrox"
Text="{Binding ElementName="ZaphodBeeblebrox" Path=Name, Converter={StaticResource conv}, Mode=OneWay}"/>
and ...
public class NameToStringConverter : IValueConverter
{
public object Convert(
object value, Type targetType,
object parameter, CultureInfo culture)
{
if (MyGlobalEnv.IsFlavor1 && ((string)value).Equals("ZaphodBeeblebrox")) return "42"
if (MyGlobalEnv.IsFlavor2 && ((string)value).Equals("ZaphodBeeblebrox")) return "43"
if (MyGlobalEnv.IsFlavor1 && ((string)value).Equals("Arthur")) return "44"
return "?";
}
public object ConvertBack(
object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotSupportedException("Cannot convert back");
}
}
I'm sure there's a better and more elegant way... Any ideas?
The point of oneway databinding is just to decouple UI (XAML) from code (CS). Here, your code and UI are tied so tightly together that trying to do this through databinding is really not buying you anything. You might simplify things by writing a method that takes the data value and applies it correctly to each control - still tightly coupled (bad) but at least the code is condensed and easy to follow (less bad).
What you should probably do though is not rely on the control name but define a ConverterParameter. See the bottom 1/3 of this article http://www.switchonthecode.com/tutorials/wpf-tutorial-binding-converters
You may bind directly to environment variable in your situation :
<Window xmlns:system="clr-namespace:System;assembly=mscorlib" ...>
<TextBlock Text="{Binding Source={x:Static system:Environment.OSVersion}}"/>

WPF Localized TreeView with HierarchicalDataTemplate

Here's the thing:
I have a simple WPF Windows application, in which I've included a TreeView, which is being constructed with the help of HierarchicalDataTemplate and fed with some hierarchical data.
The hierarchical data structure is made of FakeRec class, which contains child items in a List<FakeRec>. Each item contains a Title string property.
So in my XAML, I have:
...
<HierarchicalDataTemplate ItemsSource="{Binding Items}" DataType="{x:Type local:FakeRec}">
...
<TextBlock Grid.Column="0" Text="{Binding Path=Title}"/>
...
</HierarchicalDataTemplate>
...
This works fine, and in the generated TreeView I see the title of each node.
Now I want to make this whole tree localizable.
I have my resources in FakeDirResources.Resx (in a separate assembly, but that does not matter).
If I do this:
...
<HierarchicalDataTemplate ItemsSource="{Binding Items}" DataType="{x:Type local:FakeRec}">
...
<TextBlock Grid.Column="0" Text="{Binding Path=Title, Source={StaticResource FakeDirResources}}"/>
...
</HierarchicalDataTemplate>
...
My tree is blank (obviously, because in my FakeDirResources.resx file I don't have a resource with key Title, but I need to use the Title of the other binding, resolve it through the resources, and then somehow bind the result to the tree.
Note that if i just place a TextBlock on the window, without relation to the tree or to the HierarchicalDataTemplate, I can bind it without problem to the resources, like so:
<TextBlock Text="{Binding Path=games, Source={StaticResource FakeDirResources}}"/>;
This works great, fetching the string, and if I change the System.Threading.Thread.CurrentThread.CurrentUICulture and refresh my provider, this string gets changed to to the new language.
The question is how do I combine the two? What am I missing?
I guess there has to be some trick (and with my short experience with WPF it's probably not a straight-forward trick).
Cheers!
Alon.
Potentially you could work through this with an IValueConverter:
public class KeyResourceConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
var key = System.Convert.ToString(value);
var lookup = parameter as ResourceManager;
return lookup.GetString(key, culture);
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Used like so:
<TextBlock Text="{Binding Path=Title,
Converter={StaticResource keyResource}
ConverterParameter={x:Static local:FakeDirResources.ResourceManager}}"
/>

Is it possible to bind to a lambda expression in Silverlight?

I have a listbox that simply binds to a collection. The collection has a child collection (StepDatas). I would like to bind to a count of the child collection but with a WHERE statement. I can bind to ChildCollection.Count but get lost when needing to add the lambda expression. Here's the XAML:
<ListBox Height="Auto" Style="{StaticResource ListBoxStyle1}" Margin="4,46,4,4" x:Name="lstLeftNavigation" Background="{x:Null}" SelectionChanged="lstLeftNavigation_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Width="180" Margin="2,2,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" d:LayoutOverrides="Width" MinHeight="36">
<TextBlock Text="{Binding StepNm}" x:Name="tbStepNm" Margin="10,0,34,0" TextWrapping="Wrap" FontFamily="Portable User Interface" Foreground="White" FontSize="10" FontWeight="Bold" VerticalAlignment="Center"/>
<Image Height="37" HorizontalAlignment="Right" Margin="0" VerticalAlignment="Center" Width="37" Source="Images/imgIcoChecked.png" Stretch="Fill"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
The above works to bind to the count of the child collection. However I wish to show a count of the child collection where a certain condition is met. In this specific case, the child collection has a completed property (bool). So...I want to show the count StepDatas.Where(x => x.Completed == true).Count.
Is this in any way possible? Thanks for any help!
The short answer to the subject question is: no.
The sensible answer is: Ensure the Count you need is made available a property of the data model. E.g., ensure the type exposed by StepDatas has a Count property.
However you do qualify this with "in any way possible?". It is possible to bind to the ListItem data context and using some value converter madness to execute your lambda. However to keep things simple you need to create a converter specifically for your lambda.
Here is what the converter code would look like:-
public class CountCompletedStepDatas : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
YourItemsType item = (YourItemsType)value;
return item.StepDatas.Were(x => x.Completed == true).Count().ToString(culture);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You would the make an instance of this converter avaiable in a Resources property in the XAML, say of convenience in the UserControl:-
<UserControl x:Class="YourNameSpace.ThisControlName"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YourNameSpace;assembly=YourAssemblyName">
<UserControl.Resources>
<local:CountCompletedStepDatas x:Key="Counter" />
</UserContro.Resources>
Now in your binding:-
<TextBlock Text="{Binding Converter={StaticResource Counter} }" ... >
Thanks for the response. After submitting the question, I wrote a converter class to do what you ended up suggesting but discovered that the count property will not cause a rebind when the data changes. This will force a situation where we will have to manually update the binding when changes are made. Getting a reference of the image object inside the listbox in order to update the target is unforntunately a pain in the arse!
Ultimately, I just added a new field to the datasource and bound the image directly to it like you suggested. Much cleaner.
Thanks for the suggestions!
Doug

Resources