How to bind to property of sibling control? - wpf

In a listbox of items, one item can be designated as the primary item. In the template, I have a button bound to a parameterized command (the specific item in the collection is the parameter passed to the command in the datacontext) that is visible only if the item is not currently the primary item. If the item IS the primary item, I want to display a static image. Since I can't bind the image to the command to witch I am binding the button, I figured I could bind the Visibility property of the image to the "inverse" of the Visibility property of the button. (i.e. when the button is visible, the image is hidden and vice versa.) But I can't figure out how to do this. The button is a sibling of the image within a grid within the template. Here's my template...
<DataTemplate>
<StackPanel Orientation="Vertical">
<Grid>
<Grid.ColumnDefinitions>
...
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=FormattedNumber}" Style="{StaticResource FieldDataTextBlock}" FontWeight="Bold" />
<!-- How can I make this image aware of the following button's state? -->
<Image Grid.Column="2" Source="/Resources/Star.Pressed.png" Visibility="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path={}}" Width="20" Height="20" />
<Button Grid.Column="2" x:Name="btnMakePrimary" Style="{StaticResource StarButton}" Command="{Binding ElementName=lstPhoneNumbers, Path=DataContext.MakePrimaryPhoneNumberCommand}" CommandParameter="{Binding}" ToolTip="Set as display number." Visibility="{Binding Path=IsEnabled, RelativeSource={RelativeSource Self}, Converter={StaticResource BoolVisibility}}" />
<Button Grid.Column="4" Style="{StaticResource DetailsButton}" Command="{Binding ElementName=lstPhoneNumbers, Path=DataContext.ViewPhoneNumberCommand}" CommandParameter="{Binding}" />
<Button Grid.Column="6" Style="{StaticResource DeleteButton}" Command="{Binding ElementName=lstPhoneNumbers, Path=DataContext.DeletePhoneNumberCommand}" CommandParameter="{Binding}" />
</Grid>
<Grid >
<Grid.ColumnDefinitions>
...
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=PhoneTypeString}" Style="{StaticResource FieldDataTextBlock}" />
<TextBlock Grid.Column="2" Text="(notes)" Foreground="Blue" ToolTip="{Binding Path=PhoneNumberNote}" Visibility="{Binding Path=HasNote, Converter={StaticResource BoolVisibility}}" />
</Grid>
</StackPanel>
</DataTemplate>
Either that, or is there a way to bind the image to a method in the parent datacontext that takes a parameter?
Thanks.
J

Never mind... I was able to make it happen via the ElementName attribute of the binding and fixing an issue with the included image resource which was messing with my visuals:
<Button Grid.Column="2" Name="btnMakePrimary" Style="{StaticResource StarButton}" Command="{Binding ElementName=lstPhoneNumbers, Path=DataContext.MakePrimaryPhoneNumberCommand}" CommandParameter="{Binding}" ToolTip="Set as display number." Visibility="{Binding Path=IsEnabled, RelativeSource={RelativeSource Self}, Converter={StaticResource BoolVisibility}}" />
<Image Grid.Column="2" Source="/Resources/Star.Pressed.png" Visibility="{Binding ElementName=btnMakePrimary, Path=IsEnabled, Converter={StaticResource BoolVisibilityReverse}}" Width="20" Height="20" />

Related

Binding to the Title of a Window does not always work

I need to databind two TextBlocks to the Window.Title property. The first one works via:
RelativeSource FindAncestor, AncestorType=Window}"
But the second one does not (it's deeply nested within a button ToolTip).
How can I change the second one to make it also display the Title of the Window?
<Window ...>
<Border ...>
<Grid ...>
<Grid ...>
<!-- TEXTBLOCK BELOW WORKS -->
<TextBlock Grid.Column="2" Text="{Binding Title, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
HorizontalAlignment="Stretch" VerticalAlignment="Center" Foreground="White" FontSize="18px" FontStretch="UltraExpanded" />
<Button Grid.Column="3" HorizontalAlignment="Right" VerticalAlignment="Stretch"
Background="Transparent" BorderBrush="Transparent" Foreground="Transparent"
ToolTipService.InitialShowDelay="0" ToolTipService.BetweenShowDelay="0" ToolTipService.ShowDuration="60000">
<Button.ToolTip>
<ToolTip x:Name="helpButtonTooltip" Width="240" ToolTipService.InitialShowDelay="0">
<!-- TEXTBLOCK BELOW DOES NOT WORK; HOW CAN I MAKE IT WORK? -->
<TextBlock Text="{Binding Title, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
HorizontalAlignment="Stretch" VerticalAlignment="Center" Foreground="White" FontSize="18px" FontStretch="UltraExpanded" />
The tool tip is displayed in a popup and is not part of the same visual tree as the button or your window. Consequently, RelativeSource and ElementName bindings do not work.
What you can do is bind the window title to the Tag property of your button and then bind the Text of the tool tip TextBlock to the Tag property of the PlacementTarget (which is the button).
<Button Tag="{Binding Title, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}">
<Button.ToolTip>
<ToolTip>
<TextBlock Text="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource AncestorType={x:Type ToolTip}}}"/>
</ToolTip>
</Button.ToolTip>
</Button>

Wpf Expander Collection, how to have parent ViewModel know which Expander object current?

In a view which contains an item having a collection of child items, I have an ItemsControl which hosts the child items collection. The individual items are contained in an Expander. When a child item is expanded, I need the parent view model to be aware of which child is being acted upon. I was able to implement an Event Trigger which passes the child object as a parameter to the parent view model, and the parent view model can then set a SelectedChildObject property. That is what I need but where it falls short is when multiple items are expanded, and the user acts on an item which is not in the most recently expanded item. When this happens the item they are interacting with does not match the SelectedChildObject property, since only the most recently expanded object would be the property value.
I have seen the solutions which use a ListBox to contain the Expander, and then set the Expander IsExpanded based on the ListBox IsSelected, but I don't like this solution because it only allows one expander open at a time, plus it does not seem possible to have the Expander stretch to fill all of the space in the ListBox, and this look does not look good.
Is there a way I can always let the parent view model know which child object is being acted upon?
<ItemsControl Grid.Row="0" Grid.Column="0"
ItemsSource="{Binding Slots, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Padding="10">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Expanded">
<i:InvokeCommandAction Command="{Binding DataContext.ExpandedCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Expander.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Row="0" Grid.Column="0"
Height="16" Width="16" Source="/WpfApp1;component/Assets/Images/Warning.ico" />
<StackPanel Grid.Row="0" Grid.Column="1"
Orientation="Horizontal">
<Label Content="Slot: " />
<Label Content="{Binding SlotNumber, Mode=TwoWay, ValidatesOnNotifyDataErrors=False}" />
</StackPanel>
</Grid>
</Expander.Header>
<StackPanel Margin="20">
<StackPanel Orientation="Horizontal">
<Label Content="Slot Number:" Margin="0 5 2 0" Width="100" />
<TextBox Text="{Binding SlotNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True}"
Style="{DynamicResource Configuration.Input.TextBox}"
Width="30" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Modules:" Margin="0 5 2 0" Width="100" />
<ListBox ItemsSource="{Binding Source={x:Static local:SlotsViewModel.AllowedModules}}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}"
Command="{Binding DataContext.AddRemoveAllowedModuleCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</StackPanel>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
What kind of actions are you trying to perform on Slots?
Something like removing the last manipulated slot? If so then you will probably have to write a behavior or a control that will capture PreviewMouseDown events and send a command/event to notify your parent View Model. But it's not ideal.
And any way you will probably want the "Selected slot" to be highlighted befor it can be removed. You could also try to remove background of the expander and use the ListBox. So that when you click on the background of your Slot it selects and highlights the ListBoxItem. In this way you can bind SelectedItem to the VM.

Modifying TabControl's header

So I'm adding my views directly to the TabControl's Items collection at runtime (instead of creating TabItems around them and addings those TabItems to TabControl). The views expose a property (wrapper around a ViewModel property of the same name) named HasChanges that I want to bind to TabItem's Header to show a Asterisk (*) sign to identify tabs with unsaved changes, just like VS does. I have already tried using DataTemplates but am having trouble accessing the view object in the DataTemplate. What's the correct way of doing this? Here's one of my several attempts:
<TabControl.ItemTemplate>
<DataTemplate DataType="UserControl">
<StackPanel Orientation="Horizontal" Margin="0" Height="22">
<TextBlock VerticalAlignment="Center" Text="{Binding HeaderText, RelativeSource={RelativeSource AncestorType=UserControl}}" />
<TextBlock Text="*" Visibility="{Binding HasChanges, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource B2VConverter}}" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
Note that I'm trying two different binding methods for the two TextBlocks, none of which is working. My views inherit from UserControl and expose properties HasChanges and HeaderText.
OK. I solved it myself. For anyone else trying to implement a VS-like Close button and unsaved changes asterisk, here's the template:
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="HeaderTemplate" >
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0" Height="22">
<TextBlock VerticalAlignment="Center" Text="{Binding RelativeSource={RelativeSource AncestorType=TabItem}, Path=Content.HeaderText}" />
<TextBlock Text=" *" ToolTip="Has unsaved changes" Visibility="{Binding Content.DataContext.HasChanges, RelativeSource={RelativeSource AncestorType=TabItem}, Converter={StaticResource B2VConverter}}" />
<Button Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" Width="18" Height="18"
Margin="6,0,0,0" Padding="0" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" HorizontalAlignment="Center"
Command="{Binding DataContext.TabClosingCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}"
VerticalAlignment="Center" Focusable="False">
<Grid Margin="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Line StrokeThickness="3" StrokeStartLineCap="Round" StrokeEndLineCap="Round" Stroke="Gray" X1="1" Y1="1" X2="9" Y2="9" HorizontalAlignment="Center" VerticalAlignment="Center" />
<Line StrokeThickness="3" StrokeStartLineCap="Round" StrokeEndLineCap="Round" Stroke="Gray" X1="1" Y1="9" X2="9" Y2="1" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</Button>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
Results in an elegant drawing-based button with a flat-look. Your View must implement Boolean HasChanges and HeaderText properties, plus you need to define a BooleanToVisibilityConverter in your resources section, named B2VConverter.

Can I use a DataTemplate for toolbar buttons and still make the name meaningful?

I have a Toolbar whose ItemSource is a collection of toolbarItems which contain the bitmap text and other info for the button and the xaml includes a DataTemplate to bind the data to the button.
Our app now needs to become 508 compliant and when I run the Accessible Event Watcher it is listing all the toolbar button names as "Unknown".
Can someone tell me how to provide a meaningful name to the buttons?
Here's the portion of xaml applying to this issue:
<ToolBar.ItemTemplate>
<DataTemplate DataType="{x:Type src:toolBarItem}">
<DataTemplate.Resources>
<src:toolBarItemConverter x:Key="buttonConverter" />
<src:booleanToVisibilityConverter x:Key="boolToVisibilityConverter" />
<src:toolBarButtonFormatConverter x:Key="toolBarFormatDisplayConverter" />
<src:stringToVisibilityConverter x:Key="stringToVisibilityDisplayConverter" />
</DataTemplate.Resources>
<StackPanel Orientation="Horizontal">
<Border Style="{StaticResource SeparatorStyle}" Visibility="{Binding menuSeparator, Converter={StaticResource boolToVisibilityConverter}}"/>
<Button x:Name="listButton" Height="{Binding menuHeight, Mode=OneWay}" Width="{Binding menuWidth}" VerticalAlignment="Top" HorizontalAlignment="Center" Visibility="{Binding isActiveButton, Converter={StaticResource boolToVisibilityConverter}}" Tag="{Binding}"
ToolTip="{Binding menuTooltip}" IsEnabled="{Binding isEnabled}" >
<UniformGrid VerticalAlignment="Center" HorizontalAlignment="Center" Rows="{Binding menuText,Converter={StaticResource toolBarFormatDisplayConverter}}" >
<!-- button image -->
<Image Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" Source="{Binding menuImage, Converter={StaticResource buttonConverter}}"/>
<!-- button name -->
<Viewbox StretchDirection="DownOnly" HorizontalAlignment="Center" VerticalAlignment="Bottom" Visibility="{Binding menuText, Converter={StaticResource stringToVisibilityDisplayConverter}}" >
<TextBlock x:Name="buttonName" FontFamily="Segoe UI" Width="{Binding menuWidth}" FontSize="12" Grid.Row="1" TextAlignment="Center" TextWrapping="Wrap" HorizontalAlignment="Center" VerticalAlignment="Bottom" Text="{Binding menuText}" Foreground="Black" />
</Viewbox>
</UniformGrid>
<!-- </StackPanel> -->
</Button>
</StackPanel>
</DataTemplate>
</ToolBar.ItemTemplate>
Thanks,
Ron
OK we figured this out. Need to simply bind your names to the AutomationProperties.Name
<Button x:Name="listButton" AutomationProperties.Name="{Binding menuText}"
Height="{Binding menuHeight, Mode=OneWay}" Width="{Binding menuWidth}"
VerticalAlignment="Top" HorizontalAlignment="Center"
Visibility="{Binding isActiveButton,
Converter={StaticResource boolToVisibilityConverter}}"
Tag="{Binding}" ToolTip="{Binding menuTooltip}" IsEnabled="{Binding isEnabled}" >

Item rendered via a DataTemplate with any Background Brush renders selection coloring behind item

I have a ListBox which uses a DataTemplate to render databound items. The XAML for the datatemplate is as follows:
<DataTemplate x:Key="NameResultTemplate">
<WrapPanel x:Name="PersonResultWrapper" Margin="0" Orientation="Vertical" Background="{Binding Converter={StaticResource NameResultToColor}, Mode=OneWay}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<cmd:EventToCommand x:Name="SelectPersonEventCommand" Command="{Binding Search.SelectedPersonCommand, Mode=OneWay, Source={StaticResource Locator}}" CommandParameter="{Binding Mode=OneWay}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBlock x:Name="txtPersonName" TextWrapping="Wrap" Margin="0" VerticalAlignment="Top" Text="{Binding PersonName}" FontSize="24" Foreground="Black" />
<TextBlock x:Name="txtAgencyName" TextWrapping="Wrap" Text="{Binding AgencyName}" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="0" FontStyle="Italic" Foreground="Black" />
<TextBlock x:Name="txtPIDORI" TextWrapping="Wrap" Text="{Binding PIDORI}" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="0" FontStyle="Italic" Foreground="Black" />
<TextBlock x:Name="txtDescriptors" TextWrapping="Wrap" Text="{Binding DisplayDescriptors}" Margin="0" VerticalAlignment="Top" Foreground="Black"/>
<Separator Margin="0" Width="400" />
</WrapPanel>
</DataTemplate>
Note that there is a value converter called NameResultToColor which changes the background brush of the rendered WrapPanel to gradient brush depending on certain scenarios.
All of this works as I'd expect, except when you click on any of the rendered ListBox items. When you click one, there is only the slightest sign of the selection coloring (the default bluish color). I can see a trace bit of it underneath my gradient-brushed item. If I reset the background brush to "no brush" then the selection rendering works properly. If I set the background brush to a solid color, it also fails to render as I'd expect.
How can I get the selection coloring to be on top? What is trumping the selection rendering?
The problem is that your item's template is being drawn over the selection being drawn by the ListBoxItem. If you want to ensure that the color is kept, you can add a DataTrigger to set the background of the WrapPanel to null when the item is selected:
<DataTemplate x:Key="NameResultTemplate">
<WrapPanel x:Name="PersonResultWrapper">
...
</WrapPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" Value="True">
<Setter TargetName="PersonResultWrapper" Property="Background" Value="{x:Null}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>

Resources