One particular thing about FindAncestor confuses me, have a look at the example below:
<Expander.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Name="headerLabel" Content="Show Contents" Padding="0" VerticalAlignment="Center" />
<Button Name="headerButton" Margin="6,0,0,0" Content="Button" Padding="6,1" />
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Expander}}, Path=IsExpanded}" Value="True">
<Setter TargetName="headerLabel" Property="Content" Value="Hide Contents" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Expander.HeaderTemplate>
I use the xaml above to change the text of my custom expander header. My question is, when do I actually need to explicitly use FindAncestor when I want to use a property of an ancestor in my binding? Because the following three bindings appear to yield the same result in my scenario at least:
Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Expander}}, Path=IsExpanded}"
Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Expander}}, Path=IsExpanded}"
Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type Expander}}, Path=IsExpanded}"
I have seen lots of examples of all three, is it just a matter of personal taste?
From the MSDN page about the RelativeSource.Mode property:
If this property is
not set explicitly, setting the AncestorType or the AncestorType and
the AncestorLevel properties will implicitly lock this property value
to FindAncestor.
Related
I have many components in my XAML, I need fire Trigger when one or many components has your value changed.
I not want use one PropertyChangedTrigger for each components, I want use one Trigger for all components.
thanks.
Yes, use a MultiDataTrigger. Here's an example from the linked MSDN documentation about how you can use this on multiple properties:
<Window.Resources>
<c:Places x:Key="PlacesData"/>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=State}" Value="WA">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=Name}" Value="Portland" />
<Condition Binding="{Binding Path=State}" Value="OR" />
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="Cyan" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
<DataTemplate DataType="{x:Type c:Place}">
<Canvas Width="160" Height="20">
<TextBlock FontSize="12"
Width="130" Canvas.Left="0" Text="{Binding Path=Name}"/>
<TextBlock FontSize="12" Width="30"
Canvas.Left="130" Text="{Binding Path=State}"/>
</Canvas>
</DataTemplate>
</Window.Resources>
<StackPanel>
<TextBlock FontSize="18" Margin="5" FontWeight="Bold"
HorizontalAlignment="Center">Data Trigger Sample</TextBlock>
<ListBox Width="180" HorizontalAlignment="Center" Background="Honeydew"
ItemsSource="{Binding Source={StaticResource PlacesData}}"/>
</StackPanel>
EDIT: Not the cleanest solution ever, but you could do something like this. Basically use the MultiDataTrigger to execute whenever any of the properties change. Then, you use a converter for a simple null check (or perhaps you could always return true in your case). That way, your value in the MultiDataTrigger is just True instead of a specific value.
I have a Xceed DataGrid, and in one of the Columns there is a text that can be very long. I want to show just part of the text, and if the user hovers the mouse on top of it, a Tooltip will show the entire text.
I am trying to set the ToolTip's MaxWidh like this:
<DataTemplate x:Key="TooltipTextBlockTemplate">
<TextBlock Text="{Binding}" x:Name="DataTextBlock">
<TextBlock.ToolTip>
<TextBlock Text="{Binding}" TextWrapping="Wrap" MaxWidth="{Binding ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type xcdg:Column}}}" />
</TextBlock.ToolTip>
</TextBlock>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding}" Value="">
<Setter TargetName="DataTextBlock" Property="ToolTipService.IsEnabled" Value="False" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
I have tried AncestorType={x:Type xcdg:Column}}}" and AncestorType={x:Type TextBlock}}}" but it just expands to the whole screen.
I want to display an image in a first tab header of a TabControl in a currently fully working Prism MVVM WPF application.
Complete description as follows:
When the user select an item from the Category list in the left region it displays “More Details” and “Related Products” on the right region. This right region contains a TabControl inside a UserControl.
First Tab shows “More category Details” while second tab shows “Related Products”. Data is shown correctly. Now I want to display category thumbnail and the Category name in First tab header only.
I tried Using a HeaderTemplate on the First tab as follows
<TabControl VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" >
<TabItem Name="tabItemCategoryMoreInfo" >
<TabItem.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image x:Name="viewImage" Height="20" Width="20" Margin="0,0,2,0"
Source="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl} }, Path=Content.DataContext.SelectedParent.PictureBinary}"/>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem} }, Path=Content.DataContext.SelectedParent.CategoryName}"
VerticalAlignment="Center" FontSize="14" FontWeight="SemiBold" />
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem} }, Path=Content.DataContext.SelectedParent.PictureBinary}" Value="{x:Null}" >
<Setter TargetName="viewImage" Property="Source" Value="/CatalogModule;component/Images/ItemIcon.png" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</TabItem.HeaderTemplate>
<ContentControl prism:RegionManager.RegionName="CategoryMoreDetailsRegion" />
</TabItem>
<TabItem Header="Products" Name="tabItemCategoryProducts">
<ContentControl prism:RegionManager.RegionName="CategoryProductsRegion" />
</TabItem>
It didn’t show the product name or the product image. But it show only default image, so the Triggers looks working. Can some please help?
EDIT:
Initially I used the TabItem instead of TabControl in the image data path:
<Image x:Name="viewImage" Height="20" Width="20" Margin="0,0,2,0"
Source="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabItem} }, Path=Content.DataContext.SelectedParent.PictureBinary}"/>
The Paths in your DataTemplate Bindings are different... that may explain why it doesn't work. If I understand you correctly, you say that the Binding Path in the DataTrigger works, so perhaps changing your Binding Path for the ImageSource and TextBlock.Text properties might work?:
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image x:Name="viewImage" Height="20" Width="20" Margin="0,0,2,0"
Source="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabItem}}, Path=Content.DataContext.SelectedParent.PictureBinary}"/>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Content.DataContext.SelectedParent.CategoryName}"
VerticalAlignment="Center" FontSize="14" FontWeight="SemiBold" />
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Content.DataContext.SelectedParent.PictureBinary}" Value="{x:Null}" >
<Setter TargetName="viewImage" Property="Source" Value="/CatalogModule;component/Images/ItemIcon.png" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Just in case you can't see the difference, you were using this in your Image.Source Binding.Path:
AncestorType={x:Type TabControl}
Following is the section from My Shell:
<StackPanel x:Name="stack" Orientation="Horizontal" Height="25" HorizontalAlignment="Right" Margin="0,4,0,0" Grid.Row="0">
<Button DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type StackPanel}}, Path=DataContext}" Content="Back" prism:Click.Command="{Binding Path=GoBackCommand}"/>
<Button DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type StackPanel}}, Path=DataContext}" Content="Forward" prism:Click.Command="{Binding Path=GoForwardCommand}" Margin="10,0,0,0"/>
</StackPanel>
<ContentControl x:Name="ActionContent" prism:RegionManager.RegionName="{x:Static inf:RegionNames.WorkspaceRegion}" Grid.Row="1">
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl" >
<Grid >
<Controls:RoundedBox/>
<ContentPresenter Margin="10,0,10,0" Content="{TemplateBinding Content}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasContent" Value="false">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
<DataTrigger Binding="{Binding ElementName=stack}">
<Setter Property="DataContext" Value="{Binding RelativeSource={RelativeSource Self},
Path=Content.DataContext}" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ContentControl.Template>
In ContentControl views are injecting from Modules (using ribbon tab). I want two button inside StackPanel may use ViewModels (DataContext) of injected views, for backward and Forward navigation.
Please Help, thanks!
I would start by having a good read at Prism Navigation Chapter,
and how to use the navigation journal.
Edit:
I don't think you can do what you want directly in XAML, but i can see 2 workarounds:
Define the commands as global CompositeCommand in the shell viewmodel, then have your views reister and unregister their own implementations as they are navigated.
Define a new Region in wich the views can inject their own navigation buttons.
Hope this can help you.
I am trying to pass a listbox's Selected Index property as a command paramater to a context menu item, I have the command binding working (thanks to Will # ElementName Binding from MenuItem in ContextMenu) but I'm have trouble with my command paramater.
<UserControl>
<ListBox ItemsSource="{Binding myItems}">
<ListBox.Resources> <!-- The selected item is the item the mouse is over -->
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseOver,RelativeSource={RelativeSource Self}}"
Value="True">
<Setter Property="IsSelected" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="Edit" Grid.Column="4" Grid.Row="0" Tag="{Binding DataContext, ElementName=ProductBacklog}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove"
Command="{Binding PlacementTarget.Tag.RemoveStoryClickCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
CommandParameter="{Binding <!--I NEED TO BIND TO THE LISTBOX-->, Path=SelectedIndex}"/>
</ContextMenu>
</Button.ContextMenu>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
You can set the CommandParameter="{Binding }" to pass the current data item in that row to your Command
Edit
Just noticed your command is in a ContextMenu. ContextMenus are not part of WPF's default Visual Tree, so bindings do not work the same way. To bind to the current item, use the following:
<MenuItem Header="Remove"
Command="{Binding PlacementTarget.Tag.RemoveStoryClickCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
CommandParameter="{Binding PlacementTarget.DataContext,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ContextMenu}}}" />
This will bind to the DataContext of whatever control the ContextMenu is placed on, so in this case it will be Button.DataContext