make binding more understandable - wpf

I created style with ItemTemplate for ListView.
<Style x:Key = "Thumbnails"
TargetType = "{x:Type ListView}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Image Width="128"
Height="128"
Margin="8"
Stretch="UniformToFill">
<Image.Source>
<MultiBinding Converter="{StaticResource UriToBitmapConverter}">
<Binding Path="FullPath"/> <!-- how create this binding more understandable -->
<Binding RelativeSource="{RelativeSource Mode=Self}" Path="Width"/>
<Binding RelativeSource="{RelativeSource Mode=Self}" Path="Height"/>
</MultiBinding>
</Image.Source>
</Image>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
On ItemSource property I bind ObservableCollection<Photos>. Photos contains only one property FullPath.
<ListView x:Name="Thumbnails"
Style="{StaticResource Thumbnails}"
ItemsSource="{Binding Photos,Mode=OneWay, UpdateSourceTrigger=PropertyChanged, IsAsync=True}">
My problem is in multibinding in DataTemplate.
<Binding Path="FullPath"/>
I would like created this binding more understandable and tell that I bind property FullPath of object collection which is bind to ListView.ItemSource.
Any ideas? Thank

Related

How to use the DisplayMemberPath in a ListView with an ItemContainerStyle?

Within a ListView control, how to use the DisplayMemberPath property when I want to change the ItemContainerStyle?
I used a ListView control in the ControlTemplate of my control and the DisplayMemberPath property is set via Binding from outside of control.
<ListView ItemsSource="{TemplateBinding ItemsSource}"
DisplayMemberPath="{TemplateBinding DisplayMemberPath}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Item:" />
<TextBlock Text="{Binding}" />
<TextBlock Text=", " />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Is it possible to solve this in XAML? How is it solved in the original ControlTemplate?
I try to solve it with a MultiValueConverter, where I bind the Collection Item and DisplayMemberPath.
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource NameToValueConverter}">
<Binding />
<Binding Path="DisplayMemberPath" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ListView}}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Is it necessary? Or is it the display value already resolved by the original ListView control?
Is it possible to solve it by XAML?
No, you use either a DisplayMemberPath or an ItemTemplate/ItemContainerStyle but not both.
Also, the DisplayMemberPath property is supposed to be set to a string that specifies a name of a property of the underlying data class. It is not a dependency property that you can bind to.

Binding Combobox does not work when a Converter is used

I'm trying to bind a ComboBox to DataContext.
<ComboBox ItemsSource="{Binding Path=Numbers}"
SelectedValue="{Binding Path=CurrentNumber,Mode=TwoWay}">
</ComboBox>
The above code works, but when I try to change how the items are displayed using a converter implementing IMultiValueConverter and MultiBinding nothing is displayed. I have debugged the method implementing the IMultiValueConverter and it is not getting executed. What could be the problem?
<ComboBox ItemsSource="{Binding Path=Numbers}"
SelectedValue="{Binding Path=CurrentNumber,Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MultiUnitConverter}" ConverterParameter="{x:Static enumerations:Quantity.Length}" >
<Binding Path="."/>
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}" Path="DataContext.CurrentUnit"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Update:
I tried the following instead of the ComboBox, the converter is fired and the data is loaded but not displayed!
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MultiUnitConverter}" ConverterParameter="{x:Static enumerations:Quantity.Length}" >
<Binding Path="CurrentNumber"/>
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}" Path="DataContext.CurrentUnit"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
The following works though:
<TextBlock>
<TextBlock.Text>
<Binding Path="CurrentNumber"></Binding>
</TextBlock.Text>
</TextBlock>
For all who may get stuck with this in the future and ruin their entire evening here is the solution I found!
It seems adding StringFormat solves the problem!
<ComboBox ItemsSource="{Binding Path=Numbers}" SelectedItem="{Binding Path=Number, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource MultiUnitConverter}"
ConverterParameter="{x:Static enumerations:Quantity.Length}"
StringFormat="{}{0:0.###}">
<Binding Path="."/>
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}" Path="DataContext.CurrentUnit"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Did you define the converter resource somewhere else in your xaml? If not, you should do so. For instance, if your ComboBox lives in a UserControl you could add:
<UserControl.Resources>
<local:MultiUnitConverter x:Key="multiUnitConverter"/>
</UserControl.Resources>
And of course you would need to update your Converter StaticResource to match the case-sensitive Key above.

WPF variable number of dragable objects that do not scale on resize

The Problem:
I have a non-fixed number of items to paint on a canvas with following requirement:
The items are complex: A circle with centered text, tooltips, some extra text arround.
The items may change during runtime (calls for Binding...)
The items have coordinates that need to be scaled to drawing coordinates (for example items with x coordinates in the range of from 500 to 5000 must be drawn on screen from 0 to 190)
The items itselves must not resize when the containing Canvas resizes. Only position of the item should resize.
For every item a horizontal and vertical grid line has to be drawn and the grid line has to be labeld at the axis.
The items should be draggable: The position should be updated in the underlying data.
I have a construction with ItemsControl which satisfis requirements 1, 2, 3, 4 and 5 but I have no idea about how to get it draggable and it's became rather complex and ugly.
I tried it whith ItemsSource with each a Canvas as ItemsPanel, positioning the items with a Style, Transforms and Converters.
The Questions:
How could this be done in another way, especially without a Style in the ItemsControl?
How can I get my items draggable?
My Code:
(In my Code, the property Points is the ObservableCollection if the to be drawn items)
Items template (exmaple, selected with a TemplateSelector):
<DataTemplate x:Key="inactiveViewableControlPointTemplate" DataType="{x:Type stationary:ViewableControlPoint}">
<Canvas LayoutTransform="{StaticResource horizontalFlipTransform}">
<Canvas.ToolTip>
<StackPanel>
<TextBlock Text="{Binding PointString, StringFormat={}Point {0}}"/>
<TextBlock Text="{Binding Speed, StringFormat={}Speed: {0}%}"/>
<TextBlock Text="{Binding Load, StringFormat={}Load: {0}%}"/>
<TextBlock Text="{Binding WeightsJoined, StringFormat={}Weigthingfactor: {0}}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding WeightsJoined}"
Value="{x:Static sys:String.Empty}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</Canvas.ToolTip>
<Path Stroke="Black" StrokeThickness="2" Fill="White">
<Path.Data>
<EllipseGeometry RadiusX="20" RadiusY="20" Center="0,0"/>
</Path.Data>
</Path>
<TextBlock Canvas.Bottom="0" Canvas.Left="0" TextWrapping="Wrap"
FontFamily="Helvetica" FontSize="10"
stationary:Mover.MoveToMiddle="Both" Text="{Binding Path=PointString}"/>
<TextBlock Canvas.Bottom="10" Canvas.Left="20" TextWrapping="Wrap"
FontFamily="Helvetica" FontSize="9" FontStyle="Italic"
Text="{Binding Path=WeightsJoined}" Background="#7fff"/>
</Canvas>
</DataTemplate>
The items (Points) ItemsControl, child of a general Canvas:
<ItemsControl Name="pointItems1" Canvas.Top="0" Canvas.Left="0"
ItemTemplateSelector="{StaticResource PointTemplateSelector}"
ItemSource="{Binding Points}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="{Binding ElementName=stationaryUI, Path=Width}" Height="{Binding ElementName=stationaryUI, Path=Height}">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left">
<Setter.Value>
<MultiBinding Converter="{StaticResource ResourceKey=PositionTransformXConverter}">
<Binding ElementName="stationaryUI" Path="DataContext.Transform"/>
<Binding Path="X"/>
<Binding Path="Y"/>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Canvas.Top">
<Setter.Value>
<MultiBinding Converter="{StaticResource ResourceKey=PositionTransformYConverter}">
<Binding ElementName="stationaryUI" Path="DataContext.Transform"/>
<Binding Path="X"/>
<Binding Path="Y"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
The PositionTransformConverters apply the transform and return either a Double or a Point. The Transform is held in the resources, could also be part of the DataContext's ModelView object.
The grid lines are painted with:
<ItemsControl Name="verticalLinesItems"
ItemsSource="{Binding Speeds}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="{Binding ElementName=stationaryUI, Path=Width}" Height="{Binding ElementName=stationaryUI, Path=Height}" >
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line Stroke="Black" StrokeThickness="1" StrokeEndLineCap="Square">
<Line.X1>
<MultiBinding Converter="{StaticResource PositionTransformXConverter}" ConverterParameter="v X1">
<Binding ElementName="stationaryUI" Path="DataContext.Transform"/>
<Binding Path="Position"/>
<Binding Source="0"/>
</MultiBinding>
</Line.X1>
<Line.Y1>
<MultiBinding Converter="{StaticResource PositionTransformYConverter}" ConverterParameter="v Y1">
<Binding ElementName="stationaryUI" Path="DataContext.Transform"/>
<Binding Path="Position"/>
<Binding Source="0"/>
</MultiBinding>
</Line.Y1>
<Line.X2>
<MultiBinding Converter="{StaticResource PositionTransformXConverter}" ConverterParameter="v X2">
<Binding ElementName="stationaryUI" Path="DataContext.Transform"/>
<Binding Path="Position"/>
<Binding Source="100"/>
</MultiBinding>
</Line.X2>
<Line.Y2>
<MultiBinding Converter="{StaticResource PositionTransformYConverter}" ConverterParameter="v Y2">
<Binding ElementName="stationaryUI" Path="DataContext.Transform"/>
<Binding Path="Position"/>
<Binding Source="100"/>
</MultiBinding>
</Line.Y2>
</Line>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Speeds is a Property with only a get that iteerates over my Points items singles out duplicate entries in the x-axis. A comparable property exists for the y-axis. The labes are drawn like the grid lines, only with text instead of lines.
On Canvas resize, the Transform is updated and all ItemsControls repaint.

Do Style Setters support two way bindings (for attached properties)?

Here's my XAML:
<DataGridTemplateColumn Width="*"
CanUserResize="True"
CanUserSort="True"
Header=" Заголовок "
SortMemberPath=".">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Helpers:FocusHelper.IsFocused" Value="{Binding IsEnvelopeFocused, Mode=TwoWay}"/>
</Style>
</DataGridTemplateColumn.CellStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Name="txtTitle" VerticalAlignment="Center">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TitleConverter}" UpdateSourceTrigger="PropertyChanged">
<Binding Path="." />
<Binding Path="DataContext.Language" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=UserControl}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
FocusHelper starts listening for the changes of IsFocused property. But passing the value to IsEnvelopeFocused happens only once.
I cannot find the cause of this behavior.
Yes, the do.
The problem was in the binding but I have no idea why.

WPF GroupBox HeaderTemplate and DataBinding

I define a headertemplate into a wpf groupbox and the databinding doesn't work. I don't understand why.
<GroupBox>
<GroupBox.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Image Source="/PopuAssuNetApplication.UI.Control;component/Images/Members.png" Width="24" />
<TextBlock VerticalAlignment="Center">
<TextBlock.Text>
<MultiBinding StringFormat="{x:Static Member=resx:Resources.PersonsInContractGroupBox}">
<Binding Path="CurrentContract.Federation" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}">
</Binding>
<Binding Path="CurrentContract.Type" Converter="{StaticResource contractTypeConverter}" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}">
</Binding>
<Binding Path="CurrentContract.Number" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}">
</Binding>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<WpfComponent:WaitControl Margin="7,0,0,0" VerticalAlignment="Top" Width="24" Height="24" MarginCenter="4">
<WpfComponent:WaitControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsMembersOfContractBusy, UpdateSourceTrigger=PropertyChanged, ElementName=PersonsInContract}" Value="true">
<Setter Property="WpfComponent:WaitControl.Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsMembersOfContractBusy, UpdateSourceTrigger=PropertyChanged, ElementName=PersonsInContract}" Value="false">
<Setter Property="WpfComponent:WaitControl.Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</WpfComponent:WaitControl.Style>
</WpfComponent:WaitControl>
</StackPanel>
</DataTemplate>
</GroupBox.HeaderTemplate>
The problem is that the HeaderTemplate is used for templating the Header thus within the HeaderTemplate your DataContext is whatever you bind or assign to the Header property of your GroupBox.
Think of the Header property as almost like the DataContext for the header of the control. Normally the DataContext property inherits its value from its parent but since not every control has a Header the Header is blank unless you set it.
By binding your Header explicitly to the current DataContext Header="{Binding}" your example should work as you expect. To help illustrate how this works I've created a simple example below that shows how the Header and DataContext work independently from each other for providing data to either the body or header of the control.
<GroupBox Header="HEADER TEXT" DataContext="BODY TEXT">
<GroupBox.HeaderTemplate>
<DataTemplate>
<Button Content="{Binding}"
Background="LightGreen" />
</DataTemplate>
</GroupBox.HeaderTemplate>
<CheckBox HorizontalAlignment="Center"
VerticalAlignment="Center" Content="{Binding}" />
</GroupBox>
This will yield a GroupBox that looks like the following.
I think that by default in databinding, wpf always gets data from the DataContext property. Seems not in datatemplate
Your assumption is correct about DataContext and it does work in the DataTemplate as I've demonstrated it's just that in the Header's template the DataContext is the value from the Header Property and not the DataContext itself.
The GroupBox does not have a member called "CurrentContract". Most probably, you want to accesss a property called "CurrentContract" from the corresponding ViewModel?! The ViewModel is the GroupBox's DataContext, so you have to change the Binding Paths to something like...
<Binding Path="DataContext.CurrentContract.Type" Converter="{StaticResource contractTypeConverter}" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}">
<GroupBox >
<GroupBox.HeaderTemplate>
<DataTemplate>
<RadioButton Content="myR"
IsChecked="{Binding rIsChecked, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}}" />
</DataTemplate>
</GroupBox.HeaderTemplate>
<GroupBox.Content>
<Grid IsEnabled="{Binding rIsChecked}">
</Grid>
</GroupBox.Content>
</GroupBox>
Just propagate the GroupBox DC to the DataTemplate content...works like a charm...
The lesson learned above is useful in general for DataTemplates, but I actually found out recently there is a better way to change the header of a groupbox:
<GroupBox>
<GroupBox.Header>
<CheckBox IsChecked="{Binding Path=mSomeBoolean}"/>
</GroupBox.Header>
</GroupBox>
This way there is no need to define a relative source in the bindings.
Also please note this issue with GroupBoxes and the header.
This is what worked for me:
<HeaderedContentControl Header="{Binding}" Style="{StaticResource TallHeaderedContentStyle}">
<HeaderedContentControl.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=HeaderText"} />
</DataTemplate>
</HeaderedContentControl.HeaderTemplate>

Resources