Is it possible to bind to a lambda expression in Silverlight? - 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

Related

How to bind a DataTemplate item to a single entity in a navigational property

I am working on a knowledgebase application which manages articles.
An article consists of a header (Id, Author ... etc.) and a set of text fragments, one of which contains the Title (TextType==2).
There is a listbox intended to display Id and Title but I have been unable correctly to bind the title to a textblock.
I have working code elsewhere to load a title entity
ArticleText te = _header.ArticleTexts.Where(at => at.TextType == 2).FirstOrDefault();
so the property of the entity yielding the title would be te.Body
I set the ItemsSource of my listbox in code
public ObservableCollection<ArticleHeader> HeaderCollection
{
get
{
return (ObservableCollection<ArticleHeader>)articlesListBox.ItemsSource;
}
set
{
articlesListBox.ItemsSource = value;
}
}
which correctly displays the Id but cannot seem to work out a way to bind to the (lazy loaded) title string.
My (simplified) xaml is as follows
<ListBox Name="articlesListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="{Binding Path=Id}"/>
<TextBlock TextWrapping="WrapWithOverflow" Margin="0,0,2,0"/>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I am aware there may be a number of solutions, but what is the next step ?
Same as you did for ID?
<TextBlock Text={Binding Path=Body}/>
or
<TextBlock Text={Binding Path=Body.Title} />
Might have misunderstood your issue...
The
ArticleTexts.Where(TextType==2)
part is something that should happen in your viewmodel/controller, not in XAML.
Instead of binding your ListBox to a collection of ArticleHeader objects you can create a new collection of anonymous objects that only hold the information you need (id and title), expose it as a property and bind your ListBox to that.
Edit:
There's always the alternative of using a BindingConverter, but that would entail creating a brand new class, which would be even more of a hassle than creating a new collection. Maybe you should add the solution you have in mind in your question and explicitly ask whether a better alternative exists.
I cannot help thinking there must be other (better) ways, but I was able to achieve what I asked by defining a value converter.
[ValueConversion(typeof(ArticleHeader), typeof(String))]
public class HeaderToTitleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ArticleHeader ah = value as ArticleHeader;
ArticleText textEntity = null;
using (KnowledgeManagementEntities ctx = KBContext.NewContext())
{
ctx.ArticleHeaders.Attach(ah);
textEntity = ah.ArticleTexts.Where(at => at.TextType == KBConstants.TITLE_TYPE).FirstOrDefault();
}
if (textEntity == null)
return "";
if (String.IsNullOrEmpty(textEntity.Body))
return "";
return textEntity.Body;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Then in App.Xaml
<Application x:Class="SupportKB.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml"
xmlns:local="clr-namespace:SupportKB" xmlns:Properties="clr-namespace:SupportKB.Properties">
<Application.Resources>
<ResourceDictionary>
<local:HeaderToTitleConverter x:Key="headerToTitleConverter" />
</ResourceDictionary>
</Application.Resources>
</Application>
so the xaml now looks like this
<DataTemplate>
<DockPanel Margin="0,2">
<TextBlock DockPanel.Dock="Left" Text="{Binding Path=Id}" Width="50" />
<TextBlock TextWrapping="WrapWithOverflow"
Margin="0,0,2,0"
Text="{Binding Converter={StaticResource headerToTitleConverter}}"/>
</DockPanel>
</DataTemplate>
In the end, though, I am going to rework the design so that a list of titles is master, and the headers are selected from that.

Binding ContentTemplate

Quite new to WPF and MVVM and I'm trying to bind a ContentTemplate (or ItemTemplate, neither have worked) to a DataTemplate property in a C# WPF program. I'm doing this because I have a config file that defines different "entry display types" for each "entry" in an attempt to not have to make countless views/viewmodels (right now, there is only one generic entry viewmodel that keeps track of the label, data, and display type and I'd prefer to keep it that way to avoid unnecessary bloat of the class structure). Is there any way to make this work?
This is example of one of the things I have tried:
XAML:
<ItemsControl IsTabStop="False" ItemsSource="{Binding Path=FNEntries}"Margin="12,46,12,12">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl ContentTemplate="{Binding Path=TypeView}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
CS (inside the entry view model class constructor which has (DataTemplate)TypeView and (string)PropertyName):
NodeTypeView = (DataTemplate)Application.Current.FindResource("TypeTest");
Resource XAML:
<DataTemplate x:Key="TypeTest">
<TextBlock Margin="2,6">
<TextBlock Text="{Binding Path=PropertyName}" />
</TextBlock>
When I run with that, nothing shows up. However, if I put the contents of the resource data template directly in place of the content control, things show up just fine (except that it isn't data-driven in the way I want). Any help/advice would be greatly appreciated. Thanks!
I'd genuinely say you are mostly doing it wrong =)
Having templates stored in the ViewModel is generally a bad idea, because you would be storing graphical objects in your VM. This should be done ll on the View side
If you want a variable DataTemplate according to the type of your items or whatever, here are a few alternative, "cleaner" solutions:
Prerequisite: All of your Templates are defined as resource somewhere.
Let's say you have a ResourceDictionary somewhere with the following, for test purposes:
<DataTemplate x:Key="Template1" />
<DataTemplate x:Key="Template2" />
<DataTemplate x:Key="Template3" />
Solution 1 : use ItemTemplateSelector
(cleanest solution imho)
For this matter, I'd redirect you to this excellent tutorial which taught me how to use it
If I could understand it, no way you can't =D
Solution 2 : use a converter in your Binding
Let's slightly change your Binding, by making it binding on the current object itself, with a converter
<DataTemplate>
<ContentControl ContentTemplate="{Binding Converter={StaticResource MyConverter}}" />
</DataTemplate>
Here is what your converter would look like (note: the value object here is the bound object, in your case you are working with its type, so this example is about Types as well)
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value.GetType() == typeof(WhateverYouWant))
{
return (DataTemplate)Application.Current.FindResource("OneTemplate");
}
else if (value.getType() == typeof(AnotherTypeHere))
{
return (DataTemplate)Application.Current.FindResource("AnotherTemplate");
}
// other cases here...
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value; //We don't care about this!
}
}
This would do the trick for you
I guess both of these solutions would work, and are cleaner, but beware that this is the exact aim of ItemTemplateSelector. The Converter approach is the one I used bfore I knew about those template selectors, I don't use it anymore
Cheers!

Bind to parent object in xaml

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" />

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}}"
/>

Resources