Silverlight DataGrid styling - c#

I have a DataGrid whose ItemsSource is bound to a changing Observable Collection. Inside of this collection is a Business Object. Based on some of the values of the Business Object's properties, I would like to be able to modify the color of the text for each item displayed in my DataGrid once the ItemsSource is created.
Has anyone done this before or ran across something similar? Thanks in advance.
<DataTemplate x:Key="MyTemplate">
<Grid x:Name="LayoutRoot">
<TextBlock Text="{Binding MyText}"
Foreground="{Binding MyStatus, Converter={StaticResource colorConverter}}" />
</Grid>
</DataTemplate>
I added the above code and inserted the TemplateColumn to the grid as below:
<data:DataGridTemplateColumn Header="Testing"
CellTemplate="{StaticResource MyTemplate}"/>
The code works fine and pulls out the correct text but the converter never fires and the Binding of the foreground is never called from the get on it.
Any ideas?

Yes. Use a Value Converter when databinding.
<UserControl.Resources>
<myconverters:BackColor x:Key="BackColor" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="{Binding SomeValue, Converter={StaticResource BackColor}" >
</Grid>
Then have your converter class implement IValueConverter and return a Brush object of some kind. You usually don't have to implement ConvertBack()

Adding to BC's answer:
You can make a DataGridTemplateColumn and specify a data template for cells in a column. In the data template you can bind the text colour.
<swcd:DataGrid ... >
<swcd:DataGrid.Columns>
<swcd:DataGridTemplateColumn Header="MyColumn" CellTemplate="{StaticResource MyColumnDataGridCellTemplate}"/>
...
in resources:
<DataTemplate x:Key="MyColumnDataGridCellTemplate">
<Grid>
<TextBlock Text="{Binding someproperty}" Foreground="{Binding someotherproperty, Converter={StaticResource MyConverter}}"/>
...

Related

Bind ListView in ControlTemplate

I have a ControlTemplate in my App.xaml like bellow:
<ControlTemplate x:Key="MyTempplate">
<ListView x:Name="MyListView"
AutomationId="SelectRelationListViewId"
ItemsSource="{Binding MyListViewData}"
SelectedItem="{Binding MySelectedItem}"
BackgroundColor="White"
RowHeight="60"
SeparatorColor="White"
IsGroupingEnabled="False"
VerticalOptions="FillAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid BackgroundColor="#E7E4E0"
Padding="20,0,20,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Text="{Binding MyName}"
VerticalOptions="CenterAndExpand"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ControlTemplate>
My page is bound with my ViewModel and if I copy/paste the code from my template to my pagem, data and events are correctly handled but nothing happend when I try to use my template, the ListView is empty and the events are not triggered.
I already tried "{TemplateBinding DataContext.MyPropertyNameToBind}".
We need more details on your code and your intention on creating a control template but here are the steps that can help you:
1- First: ControlTemplate utility,
in Xamarin Forms, a 'ControlTemplate' is used to customize appearance of a control. More precisely it defines the layout in which the content will be displayed.
So it should contain a 'ContentPresenter' object.
It can be applied only on 'ContentPage' and 'ContentView' objects
--> On which UI 'Element' are you trying to bind your ControlTemplate to ?
2- Then using TemplateBindings (I suspect a problem here)
If I'm right, TemplateBindings can only be used to bind to 'Parent' Bindable Properties.
When you are doing:
<ControlTemplate x:Key="MyTemplate">
<ListView ItemsSource={TemplateBinding BindingContext} />
...
It's ok because 'BindingContext' is a BindableProperty, but if you do:
<ControlTemplate x:Key="MyTemplate">
<ListView ItemsSource={TemplateBinding BindingContext.MyItems} />
...
It's ko because 'BindingContext.MyItems' is not a BindableProperty (I presume)
3- Finally, what to do ?
Solution 1
Create in your page that owns the templated control, intermediate BindableProperties that bind to your ViewModel.
Then your 'TemplateBindings' should reference these properties.
A good example using 'TemplateBinding' & 'BindableProperty' here:
Xamarin TemplateBinding documentation.
Solution 2
For me, I will prefer to extract to App.xaml,
the ListView DataTemplate to a new data template resource
the Listview properties into a new 'ListView' Style
And in my page I would have:
<Page ...>
<ListView x:Name="myList"
Style="{StaticResource MyListViewStyle}"
ItemsSource="{Binding vmItems}"
DataTemplate="{StaticResource MyListViewDataTemplate}"
/>
In fact you can include the 'DataTemplate' property inside your Style directly...
Let me know if it helps.

Select different bindings in DataContext based on another binding

I have a fairly dynamic ObservableCollection of view models that is used by two different ListBox elements in XAML. Each view model contains properties for two different model objects of type Card called Primary and Secondary, as well as other properties. In one ListBox I'd like to display properties from Primary and in the other I'd like to display properties from Secondary. I'd like to use the same XAML UserControl file when displaying the ListBoxItems for both.
My first thought was to create an entry in UserControl.Resources that gives a name to the "right" card based on a RelativeSource reference from the parent view model which indicates Primary or Secondary, but I've not created an entry like that before. Is this the right approach? If so, what would the entry look like?
I've made up some XAML to help illustrate (may have typos). First, the Primary ListBox control:
<UserControl x:Class="Project.Cards.ListPrimary" d:DataContext="{Binding Main.Cards.Primary, Source={StaticResource Locator}}">
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type vms:CardViewModel}">
<views:Card />
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<ListBox x:Name="CardListBox"
SelectedItem="{Binding SelectedCard}"
ItemsSource="{Binding Cards}" />
</Grid>
</UserControl>
And the secondary:
<UserControl x:Class="Project.Cards.ListSecondary" d:DataContext="{Binding Main.Cards.Secondary, Source={StaticResource Locator}}">
... (same) ...
</UserControl>
And the card view (where I need to replace "Primary.Direction" with something that lets me select Primary/Secondary):
<UserControl x:Class="Project.Cards.Card">
<UserControl.Resources>
... perhaps something here ...
</UserControl.Resources>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Description}" />
<TextBlock Text="{Binding Primary.Direction}" />
</StackPanel>
</UserControl>
If you want two instances of the same UserControl that differ in one respect, you figure out how to parameterize that. There are a couple of ways, but the simplest I thought of that fits your case was to just bind the differing value to a property of the View. This moves the specification of the different value to the owner.
We'll do that by defining a dependency property on the UserControl. It's a string, though it could be an object, and in the future you might want to make it one. Since we're using the view in a DataTemplate, we can bind a property of the DataContext to it there.
public partial class Card : UserControl
{
public Card()
{
InitializeComponent();
}
public String Direction
{
get { return (String)GetValue(DirectionProperty); }
set { SetValue(DirectionProperty, value); }
}
public static readonly DependencyProperty DirectionProperty = DependencyProperty.Register("Direction",
typeof(String), typeof(Card), new PropertyMetadata(null));
}
...and we'll use that in the UserControl like this:
<Grid>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Description}" />
<TextBlock
Text="{Binding Direction, RelativeSource={RelativeSource AncestorType=UserControl}}"
/>
</StackPanel>
</Grid>
The RelativeSource stuff tells the Binding to look for that Direction property on the UserControl object itself, rather than on the DataContext as it would otherwise do by default.
If Card.Direction were object instead of string, you'd make that TextBox a ContentControl and bind to its Content property. Then you could put anything in there -- XAML, a whole other viewmodel, literally anything that XAML can figure out how to display.
And here's how it looks in the wild:
<DataTemplate DataType="{x:Type vms:CardViewModel}">
<views:Card Direction="{Binding Primary.Direction}" />
</DataTemplate>
And here's my whole mainwindow content from my test code. I didn't bother creating user controls for the listboxes; the above template is an exact match for the way you're doing it.
<Window.Resources>
<DataTemplate x:Key="PrimaryItemTemplate" DataType="{x:Type vms:CardViewModel}">
<views:Card Direction="{Binding Primary.Direction}" />
</DataTemplate>
<DataTemplate x:Key="SecondaryItemTemplate" DataType="{x:Type vms:CardViewModel}">
<views:Card Direction="{Binding Secondary.Direction}" />
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<ListBox
SelectedItem="{Binding SelectedCard}"
ItemsSource="{Binding Cards}"
ItemTemplate="{StaticResource PrimaryItemTemplate}"
/>
<ListBox
SelectedItem="{Binding SelectedCard}"
ItemsSource="{Binding Cards}"
ItemTemplate="{StaticResource SecondaryItemTemplate}"
/>
</StackPanel>
</Grid>
I originally thought of a more elaborate scheme where you give the view a DataTemplate instead, and it worked, but this is simpler. On the other hand, that was more powerful. I actually used that in the first version of the answer, before I came to my senses; it's in the edit history.
Thanks for a fun little projectlet.

WinRT XAML Databinding: How to bind to a property to the parent's data context when binding within an ItemTemplate?

I have a GridView for which I set the data context programmatically to the view model instance.
The GridView's ItemsSource is bound to an observable collection (PagesToRead) which is a property on the view model.
Within the GridView.ItemTemplate, binding goes against the observable collection in the ItemsSource, but I want to bind the StackPanel's Background element to a different property on the view model.
I'm looking for the magic <Background="{Binding Path=BackgroundColor, Source=???}"> that will escape the current ItemsSource and bind to the BackgroundColor property on the view model.
Here's the elided XAML:
<Grid>
<GridView x:Name="MainGrid" CanReorderItems="True" CanDragItems="True"
ItemsSource="{Binding Path=PagesToRead}"
<GridView.ItemTemplate>
<DataTemplate >
<StackPanel>
<Background="{Binding Path=BackgroundColor, Source=???}">
<TextBlock Text="{Binding Path=Title}"
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Grid>
I got an answer via another avenue (thanks Karl Erickson). What you do is:
<StackPanel Background="{Binding Path=DataContext.TileBackgroundColor,
ElementName=MainGrid">

How can I bind a datatemplate to a observablecollection so that they all show up?

How can I bind a datatemplate to reproduce itself for each item within an observablecollection so that they all show up? Or must I add these as individual controls, at runtime?
So I have a datatemplate:
<UserControl.Resources>
<DataTemplate x:Key="QueueItem">
<StackPanel Orientation="Vertical">
<Label Content="{Binding caseNumber}"></Label>
<!--TODO: put my other controls for this template here...-->
</StackPanel>
</DataTemplate>
</UserControl.Resources>
And I implement it in xaml here:
<ContentControl
Content="{Binding TestItems}"
ContentTemplate="{StaticResource QueueItem}">
</ContentControl>
And in my viewmodel there is a instance TestItems of some class I made, and one member of that is caseNumber, so the code above works just fine for me... But now I would like to have an observable collection of that same class, so is it possible/ is there xaml syntax to bind to the collection instance to reproduce the above for each index?
Thanks!
You could use an ItemsControl and use the ItemsTemplate property for your template:
<ContentControl>
<ItemsControl
ItemsSource="{Binding YourTestItemList}"
ItemTemplate="{StaticResource QueueItem}"/>
</ContentControl>
Use ItemsControl instead of ContentControl
Click here for sample code

Displaying Image in ComboBox's selected item

I have a custom template for my WPF windows ComboBoxes, which display items that inherit from ComboBoxItem that also have an Image property (so that my items can display both text and an image). Both the text and image display as expected in the ComboBox's popup menu for any given item, but I haven't been able to display an image in the currently selected item.
The ContentPresenter within the ComboBox template that displays the currently selected item has its Content property properly bound to {TemplateBinding SelectionBoxItem}, and its ContentTemplate is bound to the following static resource:
<DataTemplate x:Key="SelectionBoxItemTemplateTextAndImage" DataType="{x:Type SB:SBComboBoxItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Source="{Binding Image}"/>
<TextBlock Grid.Column="1" Text="{Binding}"/>
</Grid>
</DataTemplate>
SBComboBoxItem is the custom control that inherits from ComboBox and contains an "Image" property properly registered as a DependencyProperty.
I have attempted alternative implementations of that DataTemplate, including using:
{Binding Path=Image, RelativeSource={RelativeSource TemplatedParent}}
as the image's source. That didn't work, even though the text still displays when i set the TextBlock's Text property to:
{Binding Path=Content, RelativeSource={RelativeSource TemplatedParent}}
I've played around and found numerous implementations that display the text, but the equivalents never work for the image. I don't understand why this doesn't work, as setting up the popup to display both images and text was a breeze.
Edit: Here's the added functionality to ComboBoxItem to handle the image, in case there's something I did wrong there:
public static readonly DependencyProperty ImageProperty = DependencyProperty.Register("Image", typeof(Image), typeof(SBComboBoxItem));
public Image Image { get { return (Image)GetValue(ImageProperty); } set { SetValue(ImageProperty, value); } }
And here is where I have a ComboBox with the items added:
<ComboBox SelectedIndex="1">
<SB:SBComboBoxItem Content="Text">
<SB:SBComboBoxItem.Image>
<Image Source="..\Images\Table.png"/>
</SB:SBComboBoxItem.Image>
</SB:SBComboBoxItem>
<SB:SBComboBoxItem Content="MoreText">
<SB:SBComboBoxItem.Image>
<Image Source="..\Images\Euclidian.png"/>
</SB:SBComboBoxItem.Image>
</SB:SBComboBoxItem>
</ComboBox>
Despite the fact that I was using a DataTemplate to expose the underlying type of my selected combobox item through DataTemplate's DataType property, the SelectionBoxItem property of my ComboBox was apparently returning something that could not be cast as my custom ComboBoxItem. I don't know why this is, nor why it did not fire any runtime errors, but I found that by binding to ComboBox's SelectedItem property instead of SelectionBoxItem in the ContentPresenter allowed me to access user defined properties.
Does the DataTemplate even work? As far I remember, any WPF element that can be rendered, wont use a DataTemplate. And your custom ComboBoxItem can be rendered without the DataTemplate.
So the first question is, is the DataTemplate even applied? (Just change the background of DataTemplates' grid and find that out, if it works).
Let's assume the DataTemplate actually works, what I would try:
{Binding Path=Image, RelativeSource={RelativeSource AncestorType={x:Type SB:SBComboBoxItem}}
or RelativeSource as TemplatedParent with Content.Image
Okay, on the DataTemplate part, you have to bind against Image's Source property, not against Image itself.
Like so:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<BitmapImage x:Key="validImg" UriSource="test.png" />
<DataTemplate x:Key="TEMP" DataType="ComboBoxItem">
<StackPanel Orientation="Horizontal">
<!--Not working Content(Image) is not valid for Source property I think-->
<Image Source="{Binding Path=Content}" />
<!--Working -->
<Image Source="{Binding Path=Content.Source}" />
<TextBlock Text="{Binding Path=Content}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentPresenter
Content="{Binding Items[0], ElementName=test}"
ContentTemplate="{StaticResource TEMP}">
</ContentPresenter>
<ComboBox x:Name="test" Height="50" Margin="0, 50, 0, 0">
<ComboBoxItem >
<ComboBoxItem.Content>
<Image Source="{StaticResource validImg}" />
</ComboBoxItem.Content>
</ComboBoxItem>
</ComboBox>
</Grid>

Categories

Resources