WPF, XAML, FindAncestor binding DataTemplate/ControlTemplate hierarchy - wpf

We have the following XAML:
<DataTemplate x:Key="PictureMediaEditorInformationTemplate" DataType="mediaEditorInformation:PictureMediaEditorInformation">
<dxlc:LayoutGroup Orientation="Horizontal">
<dxlc:LayoutItem Label="{Binding LabelContent}">
<dxlc:LayoutGroup Orientation="Vertical">
<dxlc:LayoutItem Label="{Binding LabelTitle}" LabelPosition="Left">
<dxe:TextEdit EditValue="{Binding Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</dxlc:LayoutItem>
<dxlc:LayoutItem>
<dxe:ImageEdit ShowLoadDialogOnClickMode="Empty" ShowBorder="true" AllowDrop="True" Drop="PictureEditor_OnDrop"
Source="{Binding VisibleImage, Converter={StaticResource LexImageToImageConverter},
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="320" Height="320" ShowMenu="true">
<dxmvvm:Interaction.Behaviors>
<dxmvvm:EventToCommand EventName="MouseDoubleClick" Command="{Binding ViewPictureCommand}"/>
</dxmvvm:Interaction.Behaviors>
<dxe:ImageEdit.MenuTemplate>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<dxe:ImageEditClearToolButton ImageSource="{Binding ButtonImage, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dxlc:LayoutGroup}}"/>
And we can't work out the correct way to specify the DataTemplate datacontext for the ImageSource binding. It might be complicated by the fact that the DataTemplate is a resource instantiated in code using:
var template = (DataTemplate) lg.FindResource(item.DataTemplateName);
var uiElement = template.LoadContent() as FrameworkElement;
We've tried various things but with no success so far. I had hoped that setting uiElement.Name could be used in conjunction with ElementName but it fails presumably because it's crossing from a ControlTemplate to a DataTemplate.
NB:In the actual code the property is not called 'Wibble' :)

The solution was to specify a path in the binding:
<dxe:ImageEditClearToolButton ImageSource="{Binding Path=DataContext.Wibble}"

Related

How to display ObserveableCollection<T> in ListBox

I can't bind to my ObservableCollection<T> within my ListBox.
I am using MVVM, WPF.
The binding for the page works. My understanding is, the Grid (not shown in code) is bound to my DataContext, which is my ViewModel. Therefore, my ListBox can bind to my object called Folders via the Itemssource. My Folders object is very simple, it is literally
private ObservableCollection<Folders> _folders;
public ObservableCollection<Folders> Folders
{
get { return _folders; }
set
{
if (value == _folders)
return;
_folders = value;
OnPropertyChanged("Folders");
}
}
and my Folders model is
public class Folders
{
public string SourceFolder { get; set; }
public string DestinationFolder { get; set; }
}
and lastly, my XAML
<ListBox Grid.RowSpan="2" ItemsSource="{Binding Folders, UpdateSourceTrigger=PropertyChanged}" Grid.Row="1" SelectedItem="{Binding SelectedFolderItem}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}, AncestorLevel=1}, Path=DataContext}">
<StackPanel>
<TextBlock Text="{Binding SourceFolder}" />
<TextBlock Text="{Binding DestinationFolder}" />
<Button Content="Edit" Command="{Binding EditCommand}" CommandParameter="{Binding SelectedListItem}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The button binds/executes, but the 2 textblocks do not. I have also tried
<TextBlock Text="{Binding Folders.SourceFolder}" />
<TextBlock Text="{Binding Folders.DestinationFolder}" />
but it is the same issue. The content is not displayed (not binding) because if I add a watch on my ViewModel, I can see the values are as they should be.
If it helps, if I update my code to
<TextBlock Text="{Binding SelectedFolderItem.SourceFolder}" />
<TextBlock Text="{Binding SelectedFolderItem.DestinationFolder}" />
then it works although this is not desired (it just loops the correct number of times but only for the 1 item!).
Can some one point me in the right direction?
You are setting a different DataContext. You don't need that, otherwise you destroy the purpose of the item template.
<StackPanel Orientation="Horizontal">
<StackPanel>
<TextBlock Text="{Binding SourceFolder}" />
<TextBlock Text="{Binding DestinationFolder}" />
<Button Content="Edit"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.EditCommand}"
CommandParameter="{Binding}"/>
</StackPanel>
</StackPanel>
of course if you want the buttons to work aswell, you just move the relative source you currently have to the binding of the button.

Binding in listbox with textblocks not working

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}"/>

Get UserControl DataContext properties to bind to from within the control

Here is the code for me UserControl:
<UserControl x:Class="UZ.ActivitySink.GUI.Views.POSsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Views="clr-namespace:UZ.ActivitySink.GUI.Views">
<DockPanel>
<TreeView ItemsSource="{Binding Types}" x:Name="POSTree" Background="{x:Null}" HorizontalAlignment="Left" FontSize="14"
Visibility="{Binding DataContext.TreeVisibility, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Views:POSsView}}, Mode=TwoWay}">
</TreeView>
<StackPanel x:Name="ErrorPanel"
Visibility="{Binding DataContext.ErrorMessageVisibility, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Views:POSsView}}, Mode=TwoWay}" Margin="20">
</StackPanel>
</DockPanel>
</UserControl>
I am assigning a datacontext object to the control in it's constructor
DataContext = _viewModel;
_viewModel has the properties named TreeVisibility and ErrorMessageVisibility of type Visibility, but still the Visual elements on the screen don't bind their visibility values to these properties.
What is the correct way to reference the controls' viewmodel properties from xaml declaration in my case?
Thank you.
Your bindings are more complicated than necessary.
This one:
Visibility="{Binding DataContext.TreeVisibility,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Views:POSsView}}, Mode=TwoWay}"
in this case should be equivalent to the much simpler
Visibility="{Binding TreeVisibility}"
That said, even though the current bindings are complicated they should still have worked (at least given the information you already provide).
If you still can't get them to work, run your app in the debugger and look at the Output window -- binding errors are reported there by default, and they contain information that will help you get to the root of the problem.

Element Data-Binding in Silverlight

I have a template column in a DataGrid:
<sdk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" >
<TextBlock Text="{Binding Name,ElementName=rsAllSkills}"/>
</StackPanel>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellEditingTemplate>
And in the same xaml file, I have
<riaControls:DomainDataSource QueryName="GetSkillsQuery" AutoLoad="True" x:Name="rsAllSkills">
<riaControls:DomainDataSource.DomainContext>
<domain:XXXX context/>
</riaControls:DomainDataSource.DomainContext>
</riaControls:DomainDataSource>
The DataSource has loaded everything successfully for sure, if I put that TextBlock out side of the DataGrid, it works; but inside the DataGrid, it doesn't load even the Name of rsAllSkills....
Could anybody give me a hint, thank you so much.
Have a dummy converter and check the binding.
What I guess is, the DataTemplate inside the CellEditingTemplate would receive the parent's DataContext, ie., DataGrid's DataContext. So, to work around this you can do one thing.
1) Bind the rsAllSkills to the Tag Property of DataGridTemplateColumn.
2) Now, Bind the TextBlock's Text property with the Tag property like,
<sdk:DataGridTemplateColumn Tag="{Binding Name,ElementName=rsAllSkills}">
<sdk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" >
<TextBlock Text="{Binding Tag}"/>
</StackPanel>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellEditingTemplate>
</sdk:DataGridTemplateColumn>

WPF - simple relative path - FindAncestor

In the XAML below the ToolTip correctly binds to RelativeSource Self. However, I can't for the life of me work out how to get the TextBlock in the commented block to refer to SelectedItem.Description
<Controls:RadComboBoxWithCommand x:Name="cmbPacking"
Grid.Row="2"
Grid.Column="5"
ItemsSource="{Binding PackingComboSource}"
DisplayMemberPath="DisplayMember"
SelectedValuePath="SelectedValue"
SelectedValue="{Binding ElementName=dataGrid1, Path=SelectedItem.PackingID}"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=SelectedItem.Description}"
IsSynchronizedWithCurrentItem="True"
Style="{StaticResource comboBox}">
<!-- <Controls:RadComboBoxWithCommand.ToolTip>-->
<!-- <TextBlock Text="{Binding RelativeSource={RelativeSource Self}, Path=SelectedItem.Description}" TextWrapping="Wrap" Width="50"/>-->
<!-- </Controls:RadComboBoxWithCommand.ToolTip>-->
</Controls:RadComboBoxWithCommand>
I would appreciate any suggestions
Thanks - Jeremy
It seems that since ToolTip does not have a parent, you need to bind to the placement target as below:
<Controls:RadComboBoxWithCommand Grid.Row="2"
Grid.Column="5"
ItemsSource="{Binding PackingComboSource}"
DisplayMemberPath="DisplayMember"
SelectedValuePath="SelectedValue"
SelectedValue="{Binding ElementName=dataGrid1, Path=SelectedItem.PackingID}"
IsSynchronizedWithCurrentItem="True"
Style="{StaticResource comboBox}">
<Controls:RadComboBoxWithCommand.ToolTip>
<ToolTip DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget}">
<TextBlock Text="{Binding SelectedItem.Description}"
TextWrapping="Wrap"
Width="100" />
</ToolTip>
</Controls:RadComboBoxWithCommand.ToolTip>
</Controls:RadComboBoxWithCommand>
Hope this is useful for someone
Jeremy
A relative source of self means the current object, which would be the TextBox itself in this particular case. You want a relative source of find ancestor with an ancestor type of RadComboBoxWithCommand. Alternatively, and perhaps a little simpler, is to give the combo box a name and use ElementName in your binding instead of a relative source:
<ComboBox x:Name="cb" ...>
<ComboBox.ToolTip>
<TextBlock Text="{Binding SelectedItem.Description, ElementName=cb}" .../>

Resources