Binding in listbox with textblocks not working - wpf

I have the following xaml code:
<ListBox Foreground="{Binding MyColor, Converter={local:ColorConverter}}" ItemsSource="{Binding LogCollection, Mode=TwoWay}" Grid.Row="1">
</ListBox>
This changes the foreground color for the entire listbox, so I modified the code in this way:
<ListBox ItemsSource="{Binding LogCollection, Mode=TwoWay}" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Foreground="{Binding MyColor, Converter={local:ColorConverter}}" Text="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In this way I wanted to set the foreground for an item instead for the entire listbox, but it is not working. How do I find the right datacontext ? MyColor is a property on my MainViewModel.
LATER EDIT WITH THE SOLUTION
Jens's answer was the one that showed me where I was wrong. Instead of storing simple message log strings in the ObservableCollection, I created a new class (LogItems) which contains a Message and a Color members. Now the LogCollection is typeof LogItems instead of strings.
I populate the listbox with the following code in my viewmodel:
LogItems logitem = new LogItems(myMessage, myColor);
LogCollection.Insert(0, logitem);
And the view has the following form. Also it doesn't require anymore to use RelativeSource, because the datacontext is the same.
<ListBox ItemsSource="{Binding LogCollection, Mode=TwoWay}" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Foreground="{Binding Path=Color, Converter={local:ColorConverter}}" Text="{Binding Path=Message}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Thank you all for your answers which lead me to this solution.

The DataContext of generated container in a listbox is automatically set to the corresponding item, therefore your Binding does not find the Property MyColor. You need to use a RelativeSource binding to bind to the DataContext of the containing list:
<TextBlock Foreground="{Binding DataContext.MyColor,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBox}},
Converter={local:ColorConverter}}"
Text="{Binding}"/>

Related

WPF ComboBox ItemTemplate binding to a string collection

I have a combobox in wpf which is bound to a List<string>. All works well, but now for some reason I need to bind to an item template. The XAML for the combo box is
<ComboBox ItemsSource="{Binding Tracks}" SelectedItem="{Binding SelectedTrack}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding **WhatShouldBeHere**}"></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
If my data source is a custom collection then binding is easy, I should just pass the property name from custom collection, but as the binding source is a list of string, what should the binding property be?.
It should be
<TextBlock Text="{Binding}"/>
which is equivalent to
<TextBlock Text="{Binding Path=.}"/>
See the Remarks section on the Binding.Path MSDN page for further details.

Bind the Text Property of Textblock to a Combobox selectedItem with different DataContext in Silverlight

I have a Data Grid that is bound to a object of type MyStaff. Apart from other properties MyStaff contains a column named LookupID. Now, in my ViewModel I have a collection Named Lookups that have a description for each LookupID.
I have a Template column that has a Textblock in Cell Template and Combobox in CellEdit Template. How do I bind the Textblock so that it dsiplays the description from ComboBox based on LookupID.
I know it would be pretty simple if the datacontext for both the Textblock and ComboBox were simple but that is not the case.
I have tried this but this doesn't work. Any suggestions? Also would appreciate any information on how to best use different Data Context for different controls in Silverlight. For this I have added a static resource pointing to the ViewModel Class.
<sdk:DataGridTemplateColumn Header="Action Point"
Width="500"
CanUserReorder="False"
HeaderStyle="{StaticResource dthFull2}"
IsReadOnly="False">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding ElementName=LookupList,
Path=SelectedItem.Description}"
MinHeight="24"
VerticalAlignment="Top"
Padding="2"
TextTrimming="WordEllipsis"/>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
<sdk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<my:AutoCompleteComboBox x:Name="LookupList"
FilterMode="Custom" Margin="2,0,0,0"
SelectedValue="{Binding LookupID, Mode=TwoWay}"
SelectedValuePath="LookupID"
ItemsSource="{Binding Path=AnalysisLookupList.Values,
Source={StaticResource ViewModel}}"/>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellEditingTemplate>
</sdk:DataGridTemplateColumn>

Binding ListBox Items using Templates

I am going crazy trying to figure this out without success.
I have a DependencyObject, ("UserObject"). It has a "DataItems" DependecyProperty that is an ObservableCollection. "UserDefiniton" is a DependencyObject with a DependencyProperty of "Data". Data has two properties: DataType (an enumeration) and Value (a string).
I am trying to define a ListBox in XAML that uses the "DataItems" property as its ItemsSource. In the ItemTemplate, I have several different controls. For simplicity of this issue, I am using a CheckBox and a TextBox. I want CheckBox to be available and visible when DataType is 0, while I want the TextBox to be available and visible when the DataType is 1. Only one control can be available and visible at a time.
This works:
<ListBox
ItemsSource={Binding DataItems, Mode=OneWay}>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox
Visibility="{Binding Path=Data.DataType, Mode=OneWay, Converter={StaticResource VisibilityConverter}, ConverterParameter=0}"
IsChecked="{Binding Path=Data.Value, Mode=TwoWay, Converter={StaticResource StringToBoolean}}" />
<TextBox
Visibility="{Binding Path=Data.DataType, Mode=OneWay, Converter={StaticResource VisibilityConverter}, ConverterParameter=1}"
Text="{Binding Path=Data.Value, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
<Listbox.ItemTemplate>
</ListBox>
The problem is that even though only one is visible, both are fighting over the Data.Value property (the boolean of the checkbox will show in the textbox, even though the checkbox is hidden).
Basically, though, the binding in this case is working--but the implementation is incorrect.
So, I switched to using Templates. The problem I am having is that I can't get the binding to work.
This is the code that I have for the Template. The Template selector is working correctly, but the Text property of the TextBox and the IsChecked property of the checkbox are not binding to Data.Value:
<DataTemplate x:Key="TextBoxItem">
<TextBox
Text="{Binding Path=Data.Value, Mode=TwoWay}" />
</DataTemplate>
<DataTemplate x:Key="CheckBoxItem">
<CheckBox
IsChecked="{Binding Path=Data.Value, Mode=TwoWay, Converter={StaticResource StringToBoolean}}" />
</DataTemplate>
...
<ListBox
ItemsSource={Binding DataItems, Mode=OneWay}>
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl
Content="{Binding Path=Data.DataType, Mode=OneWay}"
ContentTemplateSelector="{DynamicResource UserDefinitionTemplateSelector}"/>
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
So how do I fix the binding?
Content should be set to {Binding}, since the Content will be the DataContext of the data-templates, hence you should just pass on the current DataContext. If you need to pass specific data to the template selector you can just drill down in the whole object.
There also is a template selector on the level of the ListBox, so you do not really need the internal ContentControl.
(You might also be interested in generic methods of debugging data bindings.)

WPF DataGrid, in a Binding in a ColumnTemplate access a Element outside of the DataGrid

I'd like to access a Object from my UserControl from within the Datgrids ColumnTemplate.
This doesn't work. Now I've read it's because of the Datacontext.
I found this Example, which should fix this: http://blog.errorok.com/2010/09/09/212/
But the Event: ColumnDataContextChanged is never called in my Project!
here's a part of my XAML:
<DataGridTemplateColumn Header="Database-Fieldtype" Width="Auto" IsReadOnly="False" SortMemberPath="DatabaseFieldType">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DatabaseFieldType}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ConfigurationTool:EditProtocolDatasets}}, Path=grdDatasets.SelectedItem.Storage.DatabaseFieldTypes}"
SelectedItem="{Binding DatabaseFieldType}"
VerticalAlignment="Top" Width="179" Padding="0" Margin="0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
ConfigurationTool:EditProtocolDatasets is my UserControl, grdDatasets is another Datagrid, to which SelectedItem I'd like to bind!
Okay, I'm going to suggest a completely different direction than my first one. My guess is that you have the ItemsSource for grdDatasets bound to something.
For the item that's going to act as your datacontext for the control, make sure it has the following characteristics, or at least a comparable structure:
public class ListOfDataSets : DependencyObject
{
public IEnumerable<DataSetOption> Items
{
get
{
...Whatever you normally use to get your DataSetOptions...
}
}
public DataSetOption SelectedItem
{
get { return (DataSetOption)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(DataSetOption), typeof(ListOfDataSets), new PropertyMetadata(null));
}
The key here is that you have one property that is a list of your choices, and another property that represents one of those items.
Then, in your XAML, your control can have the following structure:
<UserControl>
<UserControl.Resources>
<ConfigurationTool:ListOfDatasets x:Key=DataSetOptions />
</UserControl.Resources>
<StackPanel Name="LayoutRoot">
<DataGrid Name="grdDatasets"
ItemsSource="{Binding Source={StaticResource DataSetOptions}, Path=Items}"
SelectedItem="{Binding Source={StaticResource DataSetOptions}, Path=SelectedItem}"
...
</DataGrid>
...
<DataGrid Name="OtherDataGrid" ItemsSource="{Binding OtherSource}">
<DataGrid.Columns>
...
<DataGridTemplateColumn Header="Database-Fieldtype" Width="Auto" IsReadOnly="False" SortMemberPath="DatabaseFieldType">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DatabaseFieldType}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{StaticResource DataSetOptions}, Path=SelectedItem.Storage.DatabaseFieldTypes}" SelectedItem="{Binding DatabaseFieldType, Mode=TwoWay}"
VerticalAlignment="Top" Width="179" Padding="0" Margin="0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</Datagrid.Columns>
</DataGrid>
</StackPanel>
</UserControl>
I actually tried this structure out, and the databinding works fine. If the DataSetOptions change a lot, though, this solution may not work, unless you're using MVVM, and the ViewModel is good at tracking what options are available, and presents them properly to the View.
Hopefully this makes sense. I actually tried this one before answering.
I was not correct with my original answer, and overestimated the capabilities of RelativeSource before I experimented with it.
---Original text below---
I think I can help, but I need a few more details. For now I'm just going to work off assumptions.
Assumption 1: You're in a WPF UserControl with a DataGrid that has a defined ItemsSource.
Assumption 2: The UserControl has another element that you want a column within your DataGrid to have access to.
If these two assumptions are correct, it is a much better problem to have in WPF than in Silverlight.
Each row in your DataGrid is going to be working from within a DataContext that consists of the Item for that row. But, you can reach outside of the cell's (or any) DataContext with a RelativeSource.
So, if you wanted to go up the Visual Tree to get to your control's Width, you would use:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MyUserControl}}, Path=Width}
This will trace upward in the Visual Tree until an object of type "MyUserControl" is found, at which point it will grab the "Width" property and bind to it.
The Path doesn't have to be only one item deep, either. You can run up an down your visual tree as required. As this gets more complex, though, your code is going to be more fragile.
If this isn't correct, please post your XAML (or something similar) and say so, and I'll spin up a test environment and edit my post.

Access property of DataContext inside ItemTemplate

I have a really nasty problem with bindings. I know that there are other topics regarding binding itmes inside itemtemplate to datacontext of an object outside the template. However, this just won't work, i.e. the first textblock display 'Test' as desired whereas the same textbox inside the itemtemplate shows nothing.
<TextBlock Text="{Binding DataContext.Test, ElementName=myList}"/>
<ItemsControl x:Name="myList" ItemsSource="{Binding AllItems}"
Margin="0,0,0,0" VerticalAlignment="Top" HorizontalAlignment="Center">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel Orientation="Horizontal"
ItemHeight="170" ItemWidth="140"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image x:Name="{Binding KeyName}"
Source="{Binding ImagePath}"
Width="128"
Height="128">
</Image>
<TextBlock Text="{Binding DataContext.Test, ElementName=myList}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I would appreciate some help here folks as this is really a problem for me.
Inside the itemtemplate, the binding is initialized to the context of the current item in AllItems.
Update
Outside of the ItemTemplateyour bindings are relative to the DataContext of the page.**
Once inside an ItemTemplate then bindings are limited to the scope of the item specifically being evaluated at that time.
So, if we assume the following (based on the code in your question):
<ItemsControl x:Name="myList" ItemsSource="{Binding AllItems}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock x:Name="tb1"
Text="{Binding DataContext.Test, ElementName=myList}"/>
<TextBlock x:Name="tb2" Text="{Binding KeyName}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
tb1 cannot access the DataContext object directly.
tb2 cann access KeyName - assuming that whatever object AllItems is an IEnumerable of contains a property with that name.
As I understand it, inside an itemtemplate, the item past from the enumeration controls the binding source and this can't be overridden (by setting ElementName or otherwise).
If you need the value from Test in every object in your enumeration then you'll need to add it as a property of the object in the enumeration.
I'm sure someone more knowledgeable than me could explain why this is or give a better explanation but that's the gist of it.
** Assuming no other nesting of ItemsControls (or equivalent)

Resources