Dynamically setting background colour of a Silverlight control (Listbox) - silverlight

How do I set the background colour of items in a list box dynamically? i.e. there is some property on my business object that I'm binding too, so based on some business rules I want the background colour to be different?
<ListBox Background="Red">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Background" Value="Red"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
Margin="5">
<TextBlock VerticalAlignment="Bottom"
FontFamily="Comic Sans MS"
FontSize="12"
Width="70"
Text="{Binding Name}" />
<TextBlock VerticalAlignment="Bottom"
FontFamily="Comic Sans MS"
FontSize="12"
Width="70"
Text="{Binding Age}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
EDIT: It says here
In Silverlight, you must add x:Key
attributes to your custom styles and
reference them as static resources.
Silverlight does not support implicit
styles applied using the TargetType
attribute value.
Does this impact my approach?

Ok - if you need custom logic to determine the background then I would look into building a simple IValueConverter class. You just need to implement the IValueConverter interface and, in its Convert method, change the supplied value into a Brush.
Here's a quick post from Sahil Malik that describes IValueConverters - it might help:
http://blah.winsmarts.com/2007-3-WPF__DataBinding_to_Calculated_Values--The_IValueConverter_interface.aspx

To bind your background to more than one property, you can use IMultiValueConverter. It's just like IValueConverter except that it works with MultiBinding to pass more than one value into a class and get back a single value.
Here's a post I found with a run-through on IMultiValueConverter and MultiBinding:
http://blog.paranoidferret.com/index.php/2008/07/21/wpf-tutorial-using-multibindings/
Edit: If IMultiValueConverter isn't available (it looks like Silverlight only has IValueConverter) then you can always pass your entire bound object (eg your Person object) to an IValueConverter and use various properties from that to return your Brush.

#Matt Thanks for the reply. I'll look into triggers.
My only problem is that, the logic for determining whether a row should be coloured is slightly more involved so I cant just checking a property, so I actually need to run some logic to determine the colour. Any ideas?
I guess I could make a UI object with all the relevant fields I need, but I kinda didnt want to take the approach.

You could try binding something in your controltemplate (ie a border or something) to the TemplateBackground. Then set the background on your listbox to determine the colour it will be.
<Border Margin="-2,-2,-2,0" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1,1,1,0" CornerRadius="11,11,0,0">

Related

How to add a template in TextBox? WPF

I'm creating a custom TextBox with a Title and I'm trying do it with a template.
The problem is that I don't want to have a textbox inside of textbox.
For example:
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<StackPanel Height="30" VerticalAlignment="Top" Background="green">
<TextBlock Text="Title"/>
<TextBox Text="{Binding Text, ElementName=txtBox}"/> <!-- How I can do it without this? -->
</StackPanel>
</ControlTemplate>
</TextBox.Template>
This way works like I want but I don't want to recreate the TextBox with binding.
Or is this the right way?
You're not putting "a textbox inside a textbox", you're putting it inside a template. Very different thing. There's still only one textbox, you're just extending its appearance.
If you want fundamental TextBox behavior then you have to use a TextBox in your template, there's not really any way around that. I suspect though that the real problem here isn't so much the TextBox as it is the explicit binding. If that's what you're trying to avoid then you can use a TemplateBinding to bind to the templated control's Text property instead:
<TextBox Text="{TemplateBinding Text}"/>
EDIT: just as a follow-up to this, TemplateBinding is one-way only. If you need two-way binding then use Text="{Binding Text, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" instead.

Map control within Listbox

I am trying to display multiple maps within an Listbox.
<Grid Name="MainGrid">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Visible" >
<WrapPanel Name="wrap" >
<ListBox ItemsSource="{Binding MyList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border Margin="5" MinWidth="500" MinHeight="400" BorderThickness="2" BorderBrush="Black"
Width="200"
Height="200" >
<esri:MapView MouseDown="MapView_MouseDown" MouseUp="MapView_MouseUp" >
<esri:Map >
<esri:ArcGISTiledMapServiceLayer ID="BaseMap" ServiceUri="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"/>
</esri:Map>
</esri:MapView>
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</WrapPanel>
</ScrollViewer>
</Grid>
The map does not get displayed. Only the logo "esri" gets displayed. But if I remove the listbox, it works fine. What could be the issue?
I am very sure that there are items in my listbox otherwise "esri" would not have appeared.
I have tried Itemscontrol as well but its the same result.
In WPF, we don't put UI elements into collections, we put data objects into collections. Each data item should contain whichever properties are needed to data bind to the UI control that you actually want to display. So your MyList collection should contain a collection of data objects.
Once you have created a custom class to contain these required properties, you will then need to declare a DataTemplate that defines which UI element(s) to display. (See the Data Templating Overview page on MSDN for further information on this).
<ControlTemplate x:Key="YourCustomControlTemplate">
<!-- Define your UI content here -->
</ControlTemplate>
Once you have declared your custom ControlTemplate for your custom data class, you should then put it into a Style for your data object:
<Style TargetType="{x:Type YourXamlPrefix:YourCustomDataClass}">
<Setter Property="Template" Value="{StaticResource YourCustomControlTemplate}" />
</Style>
Note that I have omitted the x:Key directive on this Style... you can add one, but without one (and as long as this Style has been declared in scope of the UI), the Style will be automatically applied to your data objects in the collection.

ControlTemplate for adding UIElements to original control

I want to add an exclamation mark Image to the left of the built-in TextBox and make it visible whenever the TextBox Validation.HasError attached property is true, otherwise hide it.
How can I use ControlTemplate to add the Image without having re-bind all the TextBox properties?
<StackPanel>
<StackPanel.Resources>
<ControlTemplate x:Key="TextBoxWithIndicator" TargetType="{x:Type TextBox}">
<StackPanel Orientation="Horizontal">
<!-- Re-bind {Binding Path=Property}, including some that I may miss -->
<TextBox Text="{TemplateBinding Text}" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}"/>
<Image Source="resources/exclaim.png" Visibility="{TemplateBinding Validation.HasError}"/>
</StackPanel>
</ControlTemplate>
</StackPanel.Resources>
<TextBox Template="{StaticResource TextBoxWithIndicator}" Width="120">Happy Go Lucky</TextBox>
</StackPanel>
Note The preceding block of code represents my futile effort in WPF so far. It is probably also wrong on several counts, e.g. probably need a ValueConverter for Visibility <--> Validation.HasError; Setting Width="120" on TextBox seems to adjust the StackPanel width instead of TextBox width despite the TemplateBinding, etc.
I would suggest looking into Adorners. These are special FrameworkElements that are rendered in a special Adorner Layer on top of visual elements, and are intended to provide visual cues to the user.
The above link provides a summary of Adorners as well as an example of a Custom Adorner.

Partially templated ListBox.ItemTemplate

I'm creating a custom control and I'm trying to create partially specified template for list box items. The template has some predefined parts and there should be another part that can be templated when using the control.
For this I have created a dependency property named SuggestionItemTemplate like so:
public static readonly DependencyProperty SuggestionItemTemplateProperty =
DependencyProperty.Register("SuggestionItemTemplate",
typeof(DataTemplate),
typeof(AutoSuggestTextBox),
new PropertyMetadata(null));
In my custom controls' generic.xaml I have:
<Style TargetType="local:AutoSuggestTextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:AutoSuggestTextBox">
<Grid>
<ListBox x:Name="ItemsControl">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"
ContentTemplate="{TemplateBinding SuggestionItemTemplate}"
Content="{Binding}" />
<ToggleButton Grid.Column="1"
x:Name="DetailsHover"
ClickMode="Hover"
Style="{StaticResource DetailsToggleButtonStyle}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Unfortunatelly, this does not work as it's not possible to use TemplateBinding from inside ContentPresenter nested into DataTemplate. (The member "SuggestionItemTemplate" is not recognized or is not accessible.)
I also tried to use ancestor binding (available in Silverlight 5) like:
<ContentPresenter Grid.Column="0"
ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:AutoSuggestTextBox}, Path=SuggestionItemTemplate}"
Content="{Binding}" />
But this results in binding error:
Error: System.Exception: BindingExpression_CannotFindAncestor
I suppose this happens because I'm inside ControlTemplate of my custom control and "local:AutoSuggestTextBox" is not defined anywhere in the style.
The third option that I tried was to apply ContentTemplate in OnApplyTemplate override but this also doesn't work:
var cp = itemsControlElement.ItemTemplate.LoadContent() as ContentPresenter;
cp.ContentTemplate = SuggestionItemTemplate;
In all cases, I get my grid with two columns, toggle button is visible but content presenter simple prints out view model's type name. (I believe this is the default behavior if the ContentTemplate is null).
Is this even possible to do? Are there any other ways to specify a partial template and then only add customized template part when necessary?
As a workaround for now, I can specify
ItemTemplate="{TemplateBinding SuggestionItemTemplate}"
for the list box and then copy/paste the generic template everywhere I use this control. But this is the behavior I'm hoping to avoid in the first place.
Thanks!
edit: I used the code tags for all blocks of code, but they're not highlighted for some reason. :/
It is possible to walk through Visual Ancestors in the OnApplyTemplate method, find your ContentPresenter(s) and set the ItemTemplate on that. To my mind, this is fine for a single item, but not so much in an ItemsControl scenario.
You could achieve what you are after using your own custom Control. Just give it a Content dependency property of type Object, and a Template DP of type DataTemplate (and multiples of the two if you fancy), and you can set up the root visual style and templates in the default style for your Control.
In this specific case, I would suggest that the best approach is to put your ToggleButton in the ListBoxItem template instead by customising the ListBox.ItemContainerStyle. It is easy to modify the default Control Template using Expression Blend, and the DataContext of the ToggleButton will not change, so the changes to your own logic should be minimal.
Edit: If you mean to use a number of different data templates, perhaps Implicit Data Templates will be more suitable.
I managed to solve this using a different approach. I used ancestor binding but instead of trying to reach the root control (my AutoSuggestTextBox) from the DataTemplate, I ask for a reference to my ListBox (here named ItemsControl).
However, since the ListBox doesn't have the SuggestionItemTemplate property, I sub-classed it to my own CustomListBox where I implemented that property. It all comes down to this code snippet:
<Style TargetType="local:AutoSuggestTextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:AutoSuggestTextBox">
<Grid>
<local:CustomizableListBox x:Name="ItemsControl"
SuggestionItemTemplate="{TemplateBinding SuggestionItemTemplate}">
<local:CustomizableListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"
ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:CustomizableListBox}, Path=SuggestionItemTemplate}"
Content="{Binding}" />
<ToggleButton Grid.Column="1"
x:Name="DetailsHover"
ClickMode="Hover"
Style="{StaticResource DetailsToggleButtonStyle}" />
</Grid>
</DataTemplate>
</local:CustomizableListBox.ItemTemplate>
</local:CustomizableListBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

WPF - Freezable in a style of a button not inheriting DataContext

I am modeling an attached command pattern after the AttachedCommandBehavior library here. My button looks like this:
<Button>
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="vms:Attached.Behaviors">
<Setter.Value>
<vms:Behaviors>
<vms:Behavior Event="Click"
Command="{Binding ClickCommand}" />
</vms:Behaviors>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
Everything works great, but when the setter on the Behavior is executed, the Command is null.
Behavior is a Freezable, and Behaviors is a FreezableCollection<Behavior>. It just doesn't seem to be inheriting the DataContext from the Button.
On the other hand, this works correctly:
<Button>
<vms:Attached.Behaviors>
<vms:Behavior Event="Click" Command="{Binding ClickCommand}" />
</vms:Attached.Behaviors>
</Button>
Unfortunately I can't do it this way, because I need to target generated ListViewItems using ItemContainerStyle.
Is there some way to get the DataContext in the Style?
The Attached Command Behavior library is the germ of the idea that became Blend Behaviors. The Blend Behaviors are much more powerful and standardized and so I recommend you switch to using them. But whether you are using Attached Command Behavior or Blend Behaviors, the problem is essential the same: they don't work as expected when trying to set them using a style. I've solved this problem for Blend Behaviors with full support for binding in this StackOverflow answer:
How to add a Blend Behavior in a Style Setter
Without testing it, I guess you have to move the ACB behavior to a resource marked with x:Shared="False" in order to get the binding to work.
I had the same problem, and using RelativeSource did the trick. I'll show you my before and after code...
Before: (This DIDN'T work)
<DataTemplate x:Key="MenuNodeWithChildrenTemplate">
<StackPanel Orientation="Horizontal"
behaviors:EventCommand.CommandToRun="{Binding OpenMenuItem}"
behaviors:EventCommand.EventName="MouseLeftButtonUp">
<Label Content="{Binding Title}"/>
<Label Content="{Binding Description}"/>
</StackPanel>
</DataTemplate>
After: (This DOES work)
<DataTemplate x:Key="MenuNodeWithChildrenTemplate">
<StackPanel Orientation="Horizontal"
behaviors:EventCommand.CommandToRun="{Binding Path=DataContext.OpenMenuItem, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemsControl}}}"
behaviors:EventCommand.EventName="MouseLeftButtonUp">
<Label Content="{Binding Title}"/>
<Label Content="{Binding Description}"/>
</StackPanel>
</DataTemplate>
You'll obviously have to tweak the parameters of the Relative Source to your specific situation. It seems that, for whatever reason, attached properties don't inherit the data context, so you have to tell if how to.

Resources