Bind adorner property to viewmodel property - wpf

I need to display (or not) an adorner depending on a viewmodel's property.
My view is like this :
<ItemsControl x:Name="Items">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
<Setter Property="Width" Value="{Binding Path=Width}" />
<Setter Property="Height" Value="{Binding Path=Height}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<AdornerDecorator>
<Border x:Name="DraggableBorder" Background="{Binding Path=BackgroundColor}">
<!-- contents -->
<i:Interaction.Behaviors>
<behaviors:DragOnCanvasBehavior DraggableItem="{Binding}">
<behaviors:DragOnCanvasBehavior.MouseOverAdornerTemplate>
<DataTemplate>
<Border DataContext="DraggableBorder"
BorderBrush="#B0000000"
Width="{Binding Path=Width}"
Height="{Binding Path=Height}" />
</DataTemplate>
</behaviors:DragOnCanvasBehavior.MouseOverAdornerTemplate>
<behaviors:DragOnCanvasBehavior.SelectedAdornerTemplate>
<DataTemplate>
<Border DataContext="DraggableBorder"
BorderBrush="#FF34619E"
Width="{Binding Path=Width}"
Height="{Binding Path=Height}"
Visibility="{Binding Path=Selected,
ElementName=DraggableBorder,
Converter={StaticResource BooleanToVisibilityConverter}}" />
</DataTemplate>
</behaviors:DragOnCanvasBehavior.SelectedAdornerTemplate>
</behaviors:DragOnCanvasBehavior>
</i:Interaction.Behaviors>
</Border>
</AdornerDecorator>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And the behavior attaches the mouse events to the proper methods, and displays/hides the mouse over adorner.
The mouse over/out events and adorners are working fine, but the selection adorners cause me a bit of trouble. Because only one item should be selected, I want to rely on the Selected property of the viewmodel. I assumed what is in the sample above would work (I tried a few versions), but alas, it is not to be.
How should I write my Visibility property ?
Full code available here : https://github.com/cosmo0/DragSnap/tree/adorners

The Visibility binding binds with a property 'Selected' of DraggableBorder, which does not exist.
Furthermore, the border has a DataContext="DraggableBorder" which in this case, is a simple string.
So, use the following properties on your border:
<Border DataContext="{Binding DataContext, ElementName=DraggableBorder}" Visibility="{Binding Selected, Converter=...}"/>

Related

How to resize ListBoxItems in a horizontal ListBox as the number of items grows [duplicate]

I have a problem with my ListBox in WPF. First of all, I have a horizontal ListBox with custom ItemTemplate. Now, I want to stretch the items, so that the items fits over the complete width of the ListBox. I tried things like setting the HorizontalContentAlignment to Stretch, but this still not working.
Here is my ItemTemplate:
<DataTemplate x:Key="ListViewStepTemplate">
<Grid VerticalAlignment="Stretch">
<TextBlock Text="{Binding Path=Title}"
FontSize="15"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<Image Height="16"
Width="16"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Source="/images/Content/check_16x16.png"
Visibility="{Binding Path=IsDone, Converter={StaticResource BooleantoVisibilityConverter}, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
And this is my ListBox:
<ListBox DockPanel.Dock="Top"
ItemsSource="{Binding AllItemsList}"
SelectedItem="{Binding CurrentItem}"
ItemTemplate="{StaticResource ListViewStepTemplate}"
Height="60"
HorizontalContentAlignment="Stretch">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsEnabled" Value="{Binding IsEnabled, UpdateSourceTrigger=PropertyChanged}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Padding" Value="30 0" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
If there are 4 items, each item should have a width of 25%. If there are 5 items, each item should have a width of 20% and so on.
Is it possible to do what I want to do? I tried a lot of things now, but it does never work.
Instead of using StackPanel use UniformGrid
Provides a way to arrange content in a grid where all the cells in the grid have the same size.
and bind number of columns to number of items in the list and disable horizontal scrolling functionality.
<ListBox
...
ItemsSource="{Binding AllItemsList}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" Columns="{Binding AllItemsList.Count}"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<!-- style -->
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Don't use a StackPanel, use an UniformGrid instead.
<ItemsPanelTemplate>
<UniformGrid Rows="1" Columns="{Binding DataContext.Count, RelativeSource={RelativeSource Self}}"/>
</ItemsPanelTemplate>

Changing mouseover effect on Mahapps Tile

So recently I've started using MahApps.Metro for an application.
It's been going great, but one problem I cannot solve is MouseOver effect on a Tile.
I have a Grid, in which there's an Expander that hosts all the Tiles each of which represent a connection to the specific database. They are bound to an ObservableCollection which I populate from another database.
<Grid>
<Expander Margin="5" Header="Server Connections">
<ListBox ItemsSource="{Binding OmsConnections}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<controls:Tile
Title="{Binding Name}"
controls:ControlsHelper.MouseOverBorderBrush="{DynamicResource BlackBrush}"
Background="{DynamicResource GrayBrush2}"
Command="{Binding DataContext.TileClickCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}"
HorizontalTitleAlignment="Left"
Style="{StaticResource LargeTileStyle}"
TiltFactor="2">
<Image
Width="60"
Height="60"
Source="{Binding OmsConnectionTypeId, Converter={StaticResource ConnectionTypeToIconConverter}}" />
</controls:Tile>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Expander>
</Grid>
This is the style applied via Style
<Style x:Key="LargeTileStyle" TargetType="controls:Tile">
<Setter Property="Height" Value="125" />
<Setter Property="TitleFontSize" Value="14" />
<Setter Property="TextOptions.TextFormattingMode" Value="Display" />
<Setter Property="TextOptions.TextRenderingMode" Value="ClearType" />
<Setter Property="Width" Value="210" />
</Style>
So Whenever I mouseover an item I get the black border as specified, and this Orange Background Color (Which, if I'm not mistaken, is AccentColorBrush3) and I have no idea how to change it.
Here's the Image, since my rep is low and i cannot embed it.
Also, I'm really, really bad with Templates and Styles, so this is pretty much what i scrapped from the internet. ANY Feedback would be much appreciated for both the way I Bound to a collection and how to change the MouseOver color.
You could "override" the AccentColorBrush3 resource by adding a SolidColorBrush resource to the ListBox:
<ListBox ItemsSource="{Binding OmsConnections}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<!-- Specify the highlight brush here: -->
<ListBox.Resources>
<SolidColorBrush x:Key="AccentColorBrush3" Color="Yellow" />
</ListBox.Resources>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<controls:Tile
Title="{Binding Name}"
controls:ControlsHelper.MouseOverBorderBrush="{DynamicResource BlackBrush}"
Background="{DynamicResource GrayBrush2}"
Command="{Binding DataContext.TileClickCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}"
HorizontalTitleAlignment="Left"
Style="{StaticResource LargeTileStyle}"
TiltFactor="2">
<Image Width="60"
Height="60"
Source="{Binding OmsConnectionTypeId, Converter={StaticResource ConnectionTypeToIconConverter}}" />
</controls:Tile>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Change DataTemplate style in ListBoxItem if the item is selected

I have a listbox with an expander inside the ItemTemplate. I managed to bind the expander's IsExpanded property to the ListBoxItem's IsSelected property ok. Now I want to apply a style to the ListBoxItem's content also bound to the IsSelected property.
<ListBox.ItemTemplate>
<DataTemplate>
<Border Name="myBorder">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Description}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="Date:"/>
<TextBlock Text="{Binding Date}"/>
</StackPanel>
<dx:DXExpander Name="expanderDetails"
IsExpanded="{Binding Mode=TwoWay, Path=IsSelected,
RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Count:"/>
<TextBlock Text="{Binding Count}"/>
</StackPanel>
</dx:DXExpander>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
What I want to do is somehow set the style of the "myBorder" Border to "NotSelectedBorderStyle" for unselected ListBoxItems, and to "SelectedBorderStyle" for the SelectedItem (ListBox with single selection).
FYI, the styles define background, border and that kind of stuff, just to make the clear which item is selected, nothing fancy in these styles.
I tried the accepted answer here but if I completely switch styles I loose the nice expanding animation my DXExpander has.
I guess there must be some solution using triggers, but I can't just hit the right spot.
Finally I got it, I'm posting it here in the hope that this will save someone else time and pain :-P
This code does some additional things: the EventSetter and the corresponding Handler method are there to capture clicks to the elements inside the DataTemplate, in order to select the ListBoxItem which contains the element (if you don't you might type text inside an item, while a different one is selected).
The inner Border ("myBorder") is just a container for the stackpanels, I had to wrap everything inside another border ("backgroundBorder") which gets the style changed when the ListBoxItem gets selected.
<Style x:Key="FocusedContainer" TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="LightGray"/>
<EventSetter Event="GotKeyboardFocus" Handler="OnListBoxItemContainerFocused" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="backgroundBorder" Width="Auto" Style="{StaticResource NotSelectedBorderStyle}">
<ContentPresenter Content="{TemplateBinding Content}">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<Border Name="myBorder">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Description}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="Date:"/>
<TextBlock Text="{Binding Date}"/>
</StackPanel>
<dx:DXExpander Name="expanderDetails"
IsExpanded="{Binding Mode=TwoWay, Path=IsSelected,
RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Count:"/>
<TextBlock Text="{Binding Count}"/>
</StackPanel>
</dx:DXExpander>
</StackPanel>
</Border>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="backgroundBorder" Property="Style" Value="{StaticResource SelectedBorderStyle}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then I set the ItemContainerStyle in my ListBox to the above style:
<ListBox Background="#7FFFFFFF" HorizontalContentAlignment="Stretch"
ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True"
ItemContainerStyle="{StaticResource FocusedContainer}"/>
To finish, the code behind for the GotKeyBoardFocus handler:
private void OnListBoxItemContainerFocused(object sender, RoutedEventArgs e)
{
(sender as ListBoxItem).IsSelected = true;
}
A mess in code, but pretty neat in the UI. Hope this helps someone!

Binding a command defined in a datatemplate

I know there are few answers on this topic. But none of them was working in my case.
I have a ListView with a style and an ItemContainerStyle. In the ItemContainer Style, I define some triggers in order to use a different DataTemplate depending if the item in the list is selected or not. Then, finally in the Datatemplate I have a context menu with a command. The problem is how to bind the command to the viewmodel.
This is the ListView:
<ListView
x:Name="lstPersons"
Grid.Row="1"
Style="{StaticResource ListViewStyle}"
ItemContainerStyle="{StaticResource ItemContainerStyle}"
DataContext="{Binding}"
ItemsSource="{Binding Path=Persons}"
Tag="{Binding}"
SelectedItem="{Binding Path=SelectedPerson, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</ListView>
and these are the styles, datatemplates and contextmenu (defined in a resource dictionary).
The commands in the context menu do not work....:
<ContextMenu x:Key="SelectedItemContextMenu">
<MenuItem
Header="Do Something"
Command="{Binding Path=DataContext.DoSomethingCmd, ElementName=LayoutRoot}">
</MenuItem>
<MenuItem
Header="Do Something"
Command="{Binding PlacementTarget.Tag.DoSomethingCmd, RelativeSource={RelativeSource AncestorType=ContextMenu}}">
</MenuItem>
</ContextMenu>
<DataTemplate
x:Key="ItemTemplate">
<Canvas
Margin="4"
Width="60"
Height="60"
Background="LightGray">
<TextBlock
Foreground="Black"
Margin="2 0 0 0"
Opacity="0.5"
FontFamily="Segoe UI"
Text="{Binding Path=FirstName}" />
</Canvas>
</DataTemplate>
<DataTemplate
x:Key="ItemSelectedTemplate">
<Grid>
<Border
BorderBrush="Black"
BorderThickness="1"
Margin="3"
ContextMenu="{DynamicResource SelectedItemContextMenu}">
<Canvas
Width="60"
Height="60"
Background="LightBlue">
<TextBlock
Foreground="Black"
Margin="2 0 0 0"
Opacity="0.5"
FontFamily="Segoe UI"
Text="{Binding Path=FirstName}" />
</Canvas>
</Border>
</Grid>
</DataTemplate>
<!--style of the listviewitem-->
<Style
TargetType="{x:Type ListViewItem}"
x:Key="ItemContainerStyle">
<Setter
Property="ContentTemplate"
Value="{StaticResource ItemTemplate}" />
<Style.Triggers>
<Trigger
Property="IsSelected"
Value="True">
<Setter
Property="ContentTemplate"
Value="{StaticResource ItemSelectedTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
<!--style of the listview-->
<Style
TargetType="{x:Type ListBox}"
x:Key="ListViewStyle">
<Setter
Property="Template">
<Setter.Value>
<ControlTemplate
TargetType="{x:Type ListBox}">
<Grid>
<Border>
<ScrollViewer
Focusable="false">
<WrapPanel
IsItemsHost="True"
Orientation="Horizontal"
Width="{Binding (FrameworkElement.ActualWidth), RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"/>
</ScrollViewer>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Your ContextMenu is used inside a data template. I will be put in a different name scope of "LayoutRoot" and ElementName binding won't work. Also, the PlacementTarget of your context menu is the Border, and you've not setup any Tag on it. So the second command won't work either.
It looks like you are implement the commands on the ListBox level (or LayoutRoot?). It might be easier to put your context menu on the ListBox, and use ListBox.SelectedItem to find the current selection and apply your logic on it.
You can use RelativeSource:
<ContextMenu x:Key="SelectedItemContextMenu">
<MenuItem
Header="Do Something"
Command="{Binding Path=DataContext.DoSomethingCmd, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
</MenuItem>
</ContextMenu>
You should probably be using RoutedCommands instead of VM commands in this case. You would bind the RoutedCommand to the ContextMenu, and since you only need static object references for that, finding them shouldn't be a problem. Then you'd set up appropriate CommandBindings on the controls that should handle the commands (either ListView or ListViewItem, depending on whether you want the List-ViewModel or the Item-ViewModel to handle the command). These controls will know their ViewModels, so binding to them will not be a problem there. Through the process of Command Routing, which is built-in in WPF, the context menu will find the proper target for its command automatically.
For guidance on how to set up CommandBindings in a MVVM-friendly way, you might want to refer to http://wpfglue.wordpress.com/2012/05/07/commanding-binding-controls-to-methods/

WPF: How to access Element in ListView.ItemTemplate?

I need to access element named "PageHost" for the selected list item in the following XAML code from C# codebehind, how to do so please ?
<ListView.Resources>
<p:PageWidthConverter x:Key="PageWidthConverter" />
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate x:Name="PagesViewDataTemplate">
<DataTemplate.Resources>
<Style x:Key="PageHostStyle" TargetType="{x:Type p:PageHost}">
<Setter Property="Width" Value="{Binding Path=ActualWidth,
Converter={StaticResource PageWidthConverter},
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Grid}}}"
/>
</Style>
</DataTemplate.Resources>
<p:PageHost x:Name="PageHost">
</p:PageHost>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" VerticalAlignment="Top">
<WrapPanel.LayoutTransform>
<ScaleTransform ScaleX="1" ScaleY="1"></ScaleTransform>
</WrapPanel.LayoutTransform>
</WrapPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListItem>
</ListItem>
</ListView>
I guess the below idea will work, though I am not verified the code. You need to find out the ContentPresenter Associated with the SelectedItem first, then using the DataTemplate we can find out the pageHost
ContentPresenter lstContent = lstViewInstance.ItemContainerGenerator.ContainerFromIndex(lstViewInstance.SelectedIndex) as ContentPresenter;
DataTemplate pageViewDataTemplate= this.FindResource("PagesViewDataTemplate") as DataTemplate;
PageHost pageHost = pageViewDataTemplate.FindName("PageHost", lstContent) as PageHost;

Resources