Conditional styling of an element in visual structure - wpf

I have trouble making a universal solution for next problem:
Imagine a custom TreeView control that a;;pws theming - there is a list of elements in separate xaml file that is used to build a visual structure for TreeView control. There is onm Border element that I would like to paint its background based on type of data that is dsiplayed. This border element is part of "+" sign, and is not exposed through a style.
I can do this using code behind, and subscribing to some of the events that this control provides, then find this border in child elements, and change its background. However, this code will be repeated in many views, sometimes exactly the same, sometimes with slightly different modifications (ex only a different data element is checked for type).
Is there a way I can do this using any other technique? It seems that style selectors can be used here, since visual structure is not built at that poinr, so I cannot search this element by its name. And this element is not exposed through any property on the control.
Edit:
currently I am having my oqwn control that inherit this control, if you find thazt it can be done using Attached/depenency propertyies:
public class MyTreeView : CustomTreeView
{
}
If I ere to poaint a background for a row I would create a style selector:
<local:ProductRowtyleSelector x:Key="productRowStyleSelector"
DefaultStyle="{StaticResource defaultProductRowStyle}"
GoodStyle="{StaticResource goodProductRowStyle}"
ScrapStyle="{StaticResource reworkProductRowStyle}" />
Where GoodStyle would inherit the style for a row that exists in control template. This way I would use style selecor anywhere I displayt list of Products.
Is there a similar way that I would do the same for the Border element that I added in control template?

Edit
Ok if I get what you need, you can try this
Create a IsTypeOf IValueConverter like this :
public class IsTypeOfConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Type parameterType = parameter as Type;
if (parameterType == null)
throw new ArgumentException();
return parameterType.IsAssignableFrom(value.GetType());
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
Then in your xaml, put a DataTrigger in the applicable style
<DataTrigger Binding="{Binding Converter={StaticResource IsKindOfConverter}, ConverterParameter={x:Type Person}}">
<Setter Property="Backgroud" Value="Red" />
</DataTrigger>

Related

Pass control to binding by value

I have a converter that I use to determine the size of a child element based on the size of its container (parent).
<Setter Property="Width" Value="{Binding ActualWidth, ElementName=rowsContainer,
Converter={StaticResource sizer}}"/>
public class CellSizeConverter : IValueConverter
{
private readonly int _maxNum = 7;
private readonly int _margin = 0;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return null;
}
double width = (double)value;
return (width / _maxNum) - _margin;
}
}
This works, and when I resize my window, the size of the components change. Hooray!
I want to factor in the height as well, so I've tried passing in the control itself and getting its properties inside the converter:
<Setter Property="Width" Value="{Binding ElementName=rowsContainer, Converter={StaticResource sizer}}"/>
public class CellSizeConverter : IValueConverter
{
private readonly int _maxNum = 7;
private readonly int _margin = 0;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return null;
}
ItemsControl rowsContainer = (ItemsControl)value;
return (rowsContainer.ActualWidth / _maxNum) - _margin;
}
}
However, when I do this the size never changes. It seems like the control gets passed to the converter once but never updates. I imagine it has something to do with passing by reference rather than passing by value. Or something like that.
I know I can pass multiple values into the converter using a MultiBinding and an IMultiValue Converter but since all that values I'd want are already wrapped up in a nice little object (the control) it would be so much cleaner in my xaml to just bind it this way.
How can I pass the control such that it updates?
The converter only gets called when the property that you actually bind to is set to a new value.
This means that you cannot bind to the rowsContainer control itself as it never gets when the ActualWidth or ActualHeight property is updated.
A MultiBinding to both ActualWidth or ActualHeight is the way to go here.
How can I pass the control such that it updates?
You can't for the reason already mentioned.
It seems like the control gets passed to the converter once but never updates. I imagine it has something to do with passing by reference rather than passing by value.
Property updates in WPF aren't magic, code has to explicitly call into WPF to tell it a value has been updated.
When you bind ActualWidth, that's actually a dependency property that has a changed event WPF can hook into and be notified by the code changing it. However, when you bind the control itself, that is not a dependency property, and the control doesn't implement INotifyPropertyChanged, so WPF never knows its properties have changed.
I know I can pass multiple values into the converter using a MultiBinding and an IMultiValue Converter but [...]
A MultiBinding allows you to bind to multiple dependency properties and have WPF listen to all their property change notifications at once. I stopped the quote at "but" because it doesn't matter what comes after, that's what you need to do.

Is it possible to concatenate an imagesourece Uri in xaml

Hi I've been looking at ways to dynamically change an image Uri in xaml and in my research came across the following answer, which has certainly given me hope that what I really want to do might just be possible. In the original question the questioner wanted to swap the image itself, in my case I want to swap the directory where the image is located.
So when one looks at the answer that #Clemens provided one ends up with an images source being bound to a dependency property that is dynamically set when the form loads.
What I'd like to know is whether it would be feasible to set the location part of the uri dynamically (as per the logic that #Clemens is advocating and then simply append the Image name in the actual binding statement so that it might look something like this:
<Image Source="{Binding ImageUri & myImage.png}"/>
Essentially I have a number of buttons to which I would like to assign a default image og a size to be determined by the end user. To that end the Images would be stored in different folders in the application (in fact its a custom control) and then the relevant path bit of the URI would be set as per the suggestion in the referenced answer and I'd just append the Image name (which would be the same for the button irrespective of the size) and it would then have the correct image to display.
MainWindow.xaml.cs :
namespace MainWindowNamespace
{
public sealed class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
string fullPath = Path.GetFullPath((string)value);
return new BitmapImage(new Uri(fullPath));
}
catch { return null; }
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{ throw new NotImplementedException(); }
}
}
MainWindow.xaml :
<Window
xmlns:imgConvert="clr-namespace:MainWindowNamespace">
<Window.Resources>
<imgConvert:ImageConverter x:Key="ImageConverter" />
</Window.Resources>
<Image ImageSource="{Binding ImagePath, Converter={StaticResource ImageConverter}}" />
</Window>

Silverlight Custom Button content

What would be a good way to have the text (content) of buttons per-client configurable in a SL 4 app? I'm still pretty novice w/ SL so this may seem trivial.
The issue isn't new. The system currently has a static XAML attribute for ButtonA's content as "Do Stuff" (Content="DoStuff"). Now one client wants that to read "Do Things". This will continue to crop up on occasion in arbitrary places across the system.
I have a dictionary available that will contain the custom text, but would LIKE (if possible) to be able to have a default value and only override if there is a dictionary entry.
Conceptually, it would be nice to be able to have:
<Button Content="Do Stuff" OverrideContentKey="ButtonAOverrideContent" />
where if the dictionary has a key of ButtonAOverrideContent, it will override it, but otherwise "Do Stuff" will show.
Is there a way to perhaps write a converter and make some entries in App.xaml that would then allow all buttons to conditionally override the content? What I've seen of converters looks like there's not a smooth way to pass information about the control (e.g. the override key) to them.
You can use a ConverterParameter property of a Binding to pass your override content key to a converter.
public class ReplaceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string key = (string)parameter;
var someDictionary = GetYourReplacementDictionary();
if (someDictionary.ContainsKey(key))
{
return someDictionary[key];
}
else
{
return value;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
In your App.Xaml resources:-
<local:ReplaceConverter x:Key="replacer" />
Then on a button:-
<Button Content="{Binding Source='Do Stuff', ConverterParameter=ButtonAOverrideContent, Converter={StaticResource replacer}}" />

WPF Radio Button Selected Value Command Binding

In WPF I have a stack panel whose items control defines a data template. This data template is a radio button. The items source is a collection of names on my view model.
So for each name in the view model, a radio button appears on the stack panel with the text beside it for that name (using the content property).
All of these radio buttons have the group set to "name" so selection is mutually exclusive.
My question is, what are my options for binding the content of the selected radio button to a property on my view model "selectedName"?
Ideally I want a UI binding, that is code-free.
Thankyou
I am not sure if you can make use of the mutually exclusiveness there without any event handling. Normally i have this problem with MenuItems or button groups and my approach is to use Multibindings with a EqualityConverter, e.g.
<Setter Property="IsChecked">
<Setter.Value>
<MultiBinding Converter="{StaticResource EqualityComparisonConverter}" Mode="OneWay">
<!-- This binding should find your VM and bind to your property -->
<Binding RelativeSource="{RelativeSource AncestorType=Window}"
Path="DataContext.SelectedName"/>
<!-- Binds to the item being templated -->
<Binding />
</MultiBinding>
</Setter.Value>
</Setter>
A converter (it's not very safe, throws exception if one of the values is null, might want to improve it):
public class EqualityComparisonConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length < 2) throw new Exception("At least two inputs are needed for comparison");
bool output = values.Aggregate(true, (acc, x) => acc && x.Equals(values[0]));
return output;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
Actually it's somewhat of a mystery to me how that even works (considering that the binding is one-way)...
You could create a ViewModel for each RadioButton. It should expose property to bind RadioButton's Checked and some events to notify master ViewModel about it.

WPF - Dynamically access a specific item of a collection in XAML

I have a data source ('SampleAppearanceDefinitions'), which holds a single collection ('Definitions'). Each item in the collection has several properties, including Color, which is what I'm interested in here.
I want, in XAML, to display the Color of a particular item in the collection as text. I can do this just fine using this code below...
Text="{Binding Source={StaticResource SampleAppearanceDefinitions}, Path=Definitions[0].Color}"
The only problem is, this requires me to hard-code the index of the item in the Definitions collection (I've used 0 in the example above). What I want to do in fact is to get that value from a property in my current DataContext ('AppearanceID'). One might imagine the correct code to look like this....
Text="{Binding Source={StaticResource SampleAppearanceDefinitions}, Path=Definitions[{Binding AppearanceID}].Color}"
...but of course, this is wrong.
Can anyone tell me what the correct way to do this is? Is it possible in XAML only? It feels like it ought to be, but I can't work out or find how to do it.
Any help would be greatly appreciated!
Thanks!
AT
MultiBinding is your friend here:
Assuming you have a TextBlock:
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource AppearanceIDConverter}">
<Binding Source="{StaticResource SampleAppearanceDefinitions}" />
<Binding Path="AppearanceID" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
And define a MultiValueConverter to return what you wish to see:
public class AppearanceIDConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
List<item> items = (List<item>)values[0]; //Assuming its items in a List
int id = (int)values[1]; //Assuming AppearanceID is an integer
return items.First(i => i.ID == id).Color; //Select your item based on the appearanceID.. I used LINQ, but a foreach will work just fine as well
}
public object[] ConvertBack(object value, System.Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new System.NotImplementedException();
}
#endregion
}
Of course, you will need to set the converter as a resource in your Resource dictionary, like you did SampleAppearanceDefinitions. You can also ditch the multibinding and use a regular binding to AppearanceID with a IValueConverter, if you can get to the SampleAppearanceDefinitions collection through code ;).
Hope this helps
Even if it could be possible you'd better not do that this way, but instead use a dedicated property in your view model or in the code behind of your view if it has only a pure graphical meaning.
This property, say "CurrentAppearance", would expose a Color property you could bind from your Xaml :
Text="{Binding CurrentAppearance.Color}"
which is more understandable.
As a general advice : avoid to spoil your Xaml with plumbing code : Xaml should be as readable as possible,
particularly if you work with a team of designers that have no coding skills and do not want to be concerned with the way you are managing the data.
Moreover, if later you decide to change the way data are managed you would not have to change your Xaml.
MultiBinding might actually work if your list is on a viewmodel instead of a staticresource. I was suprised myself to see that the object passed on to the view is actually a pointer to the object on the model, so changing the object in the view (eg. typing in new test in the textbox) directly affects the model object.
This worked for me. The ConvertBack method is never useed.
public class PropertyIdToPropertyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length == 2)
{
var properties = values[0] as ObservableCollection<PropertyModel>;
if (properties != null)
{
var id = (int)values[1];
return properties.Where(model => model.Id == id).FirstOrDefault();
}
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Resources