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}}"/>
Related
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!
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" />
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}}"
/>
i got some xaml here and what i m trying to do it's simply bind a property call Property (not the real name) on the width of a rectangle and to convert the value of this property with the converter name Conv and it's working perfectly with {TemplateBinding Property} or DataContext={TemplateBinding Property} or with a relative source (like in the code sample).
My problem is that the converterParameter should also be a binding property, but i m not able to bind any property in the converterParameter. So the 30 in the sample should be something like {Binding Path=SecondProperty}. If anyone got that problem or maybe if anyone got some other way to bind stuff in custom control thanks a lot ;)
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:RatingControl">
<Style TargetType="controls:Ctr">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:Ctr">
<Grid>
<Grid.Resources>
<controls:Converter x:Name="Conv" />
</Grid.Resources>
<Rectangle x:Name="rect" Width="{Binding Path=Property, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource Conv}, ConverterParameter=30}" Height="20" />
It doesn't look like that's possible: http://msdn.microsoft.com/en-us/library/system.windows.data.binding.converterparameter(VS.95).aspx
You can add a property to the Converter class and bind to that.
You can't bind to a property of the Binding object, since it isn't a DependencyProperty in fact Binding isn't a DependencyObject. This is understandable can you imagine the complexity of managing dependency trees and the possiblity of recursive or circular bindings in bindings.
However you could use a Specialised converter for the task:-
public class MySpecialConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Ctr obj = (Ctr)value;
var val = obj.Property;
var param = obj.SecondProperty;
// Do your intended code with val and param here.
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException("This converter only works for one way binding");
}
}
now your Xaml looks like:-
<Rectangle x:Name="rect" Height="20"
Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource Conv}" />
It's a really good solution but it's not working bcs my first property must be bind (twoWay) because if i got any change on it the converter must convert again the value so i get the result back and show the real result.
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