ZIndex on ObservableCollection bound ListBox - wpf

I have a ListBox bound to an ObservableCollection, with a canvas as an ItemsPanel. Everything works as expected - I've implemented dragging of the items succesfuly - but the problem is that I can't set the ZIndex of the clicked item. Debuging shows that all the items have a ZIndex of 0, which looks strange to me. What I want is to bring the item to front when clicked and send to back when released. Could someone give me any ideas? Please feel free to ask for any code that might be useful.
Update: This is the ItemsContainerStyle, defined as a Window Resource
<Style x:Key="MediaContainerStyle" TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding MediaPosition.X,UpdateSourceTrigger=PropertyChanged}"/>
<Setter Property="Canvas.Top" Value="{Binding MediaPosition.Y,UpdateSourceTrigger=PropertyChanged}"/>
<Setter Property="Panel.ZIndex" Value="{Binding ZIndex,UpdateSourceTrigger=PropertyChanged}"/>
</Style>
and the template for the item
<DataTemplate x:Key="MediaDataTemplate">
<views:MediaItemView MouseDown="OnMediaItemMouseDown"
MouseMove="OnMediaItemMouseMove"/>
</DataTemplate>
where MediaItemView is a user control.
In the code behind, I do
void OnMediaItemMouseDown(Object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == Pressed)
{
FrameworkElement feItem = sender as FrameworkElement;
MediaViewModel vmItem = feItem.DataContext as MediaViewModel;
vmItem.ZIndex = vm.MainMedia.Count;
// Keep the click point
pClick = e.GetPosition(feItem);
}
}
where vm is an instance of my underlying viewmodel, containing a Double property ZIndex

And, of course, it was right before my eyes! The answer, (taken from Modify ZIndex of an Items in an ItemsControl) was to add a trigger to my ItemContainerStyle (which has a ListBoxItem as TargetType) for IsSelected property. So...
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Panel.ZIndex" Value="99"/>
</Trigger>
</Style.Triggers>
will do!

Related

Changing DataGrid Row Styles while adding Items

I'm trying to change row style while adding items to DataGrid.
<DataGrid x:Name="datagrid1" HorizontalAlignment="Stretch" Height="auto" Margin="10,64,0,0"
VerticalAlignment="Stretch" Width="auto" AutoGenerateColumns="False" DataContext="{Binding}" ItemsSource="{Binding}">
<DataGrid.ItemContainerStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding UIvisibility}" Value="-2">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
<DataTrigger Binding="{Binding UIvisibility}" Value="-1">
<Setter Property="Background" Value="Blue"/>
<Setter Property="Foreground" Value="White"/>
</DataTrigger>
<DataTrigger Binding="{Binding UIvisibility}" Value="-0">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.ItemContainerStyle>
</DataGrid>
kMS(i).line(7) has "-2","-1","-0" or "1" for each kMS(i) as String, And will be assigned to Item.Visibility
For Instance
kMS(1).line(7)="-1"
kMS(4).line(7)="-0"
kMS(369).line(7)="1"
kMS(897).line(7)="-2"
Codes are following
Dim c3 As DataGridTextColumn = New DataGridTextColumn
c3.Binding = New System.Windows.Data.Binding("UIvisibility")
c3.Visibility = 1
datagrid1.Columns.Add(c3)
Dim Additem(kMS.Length - 1) As Item
For i = 0 To kMS.Length - 1
Additem(i) = New Item
Additem(i).Callback = kMS(i).line(8)
Additem(i).Keyboard = "No Assign"
If kMS(i).line(3).Remove(0, 2) <> "FFFFFFFF" Then
Additem(i).Keyboard = kMS(i).line(3).Remove(0, 2)
End If
Additem(i).UIvisibility = kMS(i).line(7)
datagrid1.Items.Add(Additem(i))
Next
 
Public Class Item
Public Property Callback As String
Public Property Keyboard As String
Public Property UIvisibility As String
End Class
It's not working well and the result would be only rows with transparent background and black foreground.
Actutal image of the application
What am I missing?
It would help if you post a screenshot of your problem.
My guess is that you change the property of the wrong WPF control. For changing the background, DataGridRow is ok only for the part of the row which is not covered by any row. This can happen when the DataGrid is wider than all columns together.
For the columns, DataGridCell, i.e. TextBox gets painted over the DataGridRow. So if the DataGridRow is transparent, but the TextBox is white, you will see a white background.
This kind of problem WPF often avoids using graphical inheritance, meaning when a property is set in a container, all controls who don't write their own value for that property will inherit the value from the container.
Unfortunately, DataGrid behaves quite differently. Usually, your problems go away if you don't depend on graphical inheritance but style the correct control.
2 Recommendations
1) Don't use triggers to change the style. You can easily do it using bindings:
<datagrid.rowstyle>
<style targettype="DataGridRow">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self},
Path=Item.Quantity, Converter={StaticResource QuantityToBackgroundConverter}}"/>
</style>
</datagrid.rowstyle>
The Foreground could be done like this:
<Setter Property="Foreground"
Value="{Binding
RelativeSource={RelativeSource Self},
Path=Text,
Converter={StaticResource QuantityToForegroundConverter}}" />
2) Formatting the DataGrid is a major headache. I spent weeks trying to understand it. Read my article where I provide a lot of background information and many samples how important formattings can be done:
www.codeproject.com: Guide to WPF DataGrid formatting using bindings

Grid Hide/Show animation

I've got some User Control, which is grid with transparent background, on top of that there's another grid that is suppose to change its width when triggers are fired (ultimately it'll be a in/out animation). Purpose was to simply use something different than Visibility that was instantly hiding entire control without any animations Here's the property it's based on:
public static readonly DependencyProperty IsOpenedProperty = DependencyProperty.Register ("IsOpened", typeof (Boolean), typeof (SidePanel), new PropertyMetadata (false));
public bool IsOpened {
get {
if (GetValue (IsOpenedProperty) != null)
return (bool)GetValue (IsOpenedProperty);
else
return false;
}
set {
SetValue (IsOpenedProperty, value);
Console.WriteLine(value);
}
}
And the Grid itself:
<Grid>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Setter Property="Width" Value="50"/>
<Setter Property="Background" Value="Gray"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsOpened}" Value="True">
<Setter Property="Width" Value="350"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsOpened}" Value="False">
<Setter Property="Width" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding ElementName=Side, Path=Width}"/>
</Grid>
Console gives me corect output but triggers don't fire at all.
Just checked one more thing and when I set trigger for x:Null value then it's fired all the time, how is it possible as Get shouldn't ever return null value here?
Alright I just gave up on dependency property here, created private variable to hold the value and added data context with INotifyPropertyChanged interface implementation and now it works.

Binding a WPF Style Trigger to a custom dependency property

I have found numerous similar threads here, but none that seem to address my specific issue.
I need to highlight the background of a textbox under certain conditions. I have created a Highlight property and tried using a trigger in a style to set it but it doesn't actually ever highlight the text.
Here is my Style, simplified:
<Style x:Key="TextBoxStyle" BasedOn="{StaticResource CommonStyles}">
<Style.Triggers>
<Trigger Property="Elements:DataElement.Highlight" Value="True">
<Setter Property="Control.Background"
Value="{DynamicResource EntryBoxHighlightBackground}"/>
</Trigger>
</Style.Triggers>
</Style>
Elements is defined as:
xmlns:Elements="clr-namespace:MDTCommon.Controls.Forms.Elements">
Then I have the section where the style is applied:
<!-- Applies above style to all TextBoxes -->
<Style TargetType="TextBox" BasedOn="{StaticResource TextBoxContentHolder}" >
<Setter Property="Validation.ErrorTemplate" Value="{x:Null}" />
<!-- Overrides the default Error Style -->
</Style>
In the code behind of the DataElement class is the following:
public static readonly DependencyProperty HighlightProperty =
DependencyProperty.Register("Highlight", typeof(bool), typeof(DataElement));
public bool Highlight
{
get { return (bool)base.GetValue(HighlightProperty); }
set { base.SetValue(HighlightProperty, value); }
}
A DataElement ultimately derived from UserControl and it contains a reference to TextBox object as well as othe objects.
In the CustomForm class that houses all of the DataElement objects I have the following to set the color.
Resources["EntryBoxHighlightBackground"] = Brushes.Yellow;
So, the first issue is that setting the Highlight property for the DataElement doesn't cause the textbox background to draw in yellow.
The other issue is that I realize that I am applying this style to all textboxes and I could have textboxes in other areas that are not actually contained within a DataElement, which may cause a binding issue.
Try converting your trigger to a DataTrigger, and add a binding that will look directly at the DataElement control, like so:
<DataTrigger Binding="{Binding Path=Highlight, RelativeSource={RelativeSource AncestorType={x:Type Elements:DataElement}}}" Value="True">
<Setter Property="Control.Background" Value="{DynamicResource EntryBoxHighlightBackground}"/>
</DataTrigger>

TreeView SelectedItem return type

I've got a TreeView that uses a HierarchicalDataTemplate and a view model as data context on different nodes. I want to access some TreeViewItem properties from TreeView.SelectedItem - but this returns a view model object not a TreeViewItem.
How to get a TreeViewItem ref to selected item?
(Ive the same problem in SelectedItemChanged handlers - object sender is a view model - how to get TreeViewItem?)
[There is a TreeView property SelectedContainer which returns a TreeViewItem but its not accessable :-( ]
This is the kind of frustrating thing about WFP is that is easy to get stuck on this kind of "detail" and it seems like there must be an easy/obvious solution but...
Once you've bound your TreeView to a data context, you will always get back view-model objects. If you want to manipulate TreeViewItem objects in response to events, you need to do it through bindings. For example, the IsExpanded, IsSelected properties can be tied to view-model properties by using styles. The following code automatically bolds the selected tree item and binds the aforementioned properties to view-model properties where I can manipulate/read them.
<TreeView x:Name="treeEquipment"
ItemsSource="{Binding RootEquipment}"
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="TreeViewItem.MouseRightButtonDown"
Handler="TreeViewItem_MouseRightButtonDown"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
The property I was trying to set was IsSelected which I always wanted false because I manage multiple selection my self. Following StrayPointers advice that works with a binding on the view mode:
class TreeNodeViewMode {
public bool no_selection {
get { return false; }
set { RaisePropertyChanged(); }
}
}
XAML:
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding no_selection, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
Another approach is to handle TreeViewItem.Selected event which unlike TreeView.SelectedItemChanged (which gets a view model passed in) this does get us the TreeViewItem via:
TreeViewItem item = e.OriginalSource as TreeViewItem;
Which enables the setting of properties eg
TreeViewItem item = e.OriginalSource as TreeViewItem;
if (item != null) {
item.Focus();
item.IsSelected = false;
}

How to use IsKeyboardFocusWithin and IsSelected together?

I have a style defined for my ListBoxItems with a trigger to set a background color when IsSelected is True:
<Style x:Key="StepItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="Border" Padding="0" SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="#40a0f5ff"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This style maintains the selected item even when the ListBox and ListBoxItem loses focus, which in my case is an absolute must.
The problem is that I also want the ListBoxItem to be selected when one of its TextBox's child gets focused. To achieve this I add a trigger that sets IsSelected to true when IsKeyboardFocusWithin is true:
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
When I add this trigger the Item is selected when the focus is on a child TextBox, but the first behaviour disappears. Now when I click outside the ListBox, the item is de-selected.
How can I keep both behaviours?
When your listbox looses focus, it will set selected item to null because of your trigger. You can select on focus using some code behind that will not unselect when you loose focus.
XAML:
<Window x:Class="SelectedTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<StackPanel>
<TextBox Text="Loose focus here" />
<ListBox Name="_listBox" ItemsSource="{Binding Path=Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" GotFocus="OnChildGotFocus">
<TextBox Text="{Binding .}" Margin="10" />
<TextBox Text="{Binding .}" Margin="10" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="Border" SnapsToDevicePixels="true" Background="Transparent">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="Red"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</StackPanel>
</Window>
Code behind:
private void OnChildGotFocus(object sender, RoutedEventArgs e)
{
_listBox.SelectedItem = (sender as StackPanel).DataContext;
}
"When I add this trigger the Item is selected when the focus is on a child TextBox, but the first behaviour disappears. Now when I click outside the ListBox, the item is de-selected."
Actually, I don't think it has lost that original behavior. What I suspect is happening is you're clicking directly in the textbox from somewhere else so the underlying ListBoxItem never actually became selected. If it did however, you'd see the selection would still remain after you left as you want.
You can test this by forcing the ListBoxItem to be selected by clicking directly on it (side-note: you should always give it a background, even if just 'transparent' so it can receive mouse clicks, which it won't if it's null) or even just hitting 'Shift-Tab' to set the focus there, back from the textbox.
However, that doesn't solve your issue, which is that the TextBox gets the focus but doesn't let the underlying ListBoxItem know about it.
The two approaches you can use for that are an event trigger or an attached behavior.
The first is an event trigger on the IsKeyboardFocusWithinChanged event where you set 'IsSelected' to true if the keyboard focus changed to true. (Note: Sheridan's answer does a faux-change-notification but it should not be used in cases where you can multi-select in the list because everything becomes selected.) But even an event trigger causes issues because you lose the multi-select behaviors such as toggling or range-clicking, etc.
The other (and my preferred approach) is to write an attached behavior which you set on the ListBoxItem, either directly, or via a style if you prefer.
Here's the attached behavior. Note: You again would need to handle the multi-select stuff if you want to implement that. Also note that although I'm attaching the behavior to a ListBoxItem, inside I cast to UIElement. This way you can also use it in ComboBoxItem, TreeViewItem, etc. Basically any ContainerItem in a Selector-based control.
public class AutoSelectWhenAnyChildGetsFocus
{
public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached(
"Enabled",
typeof(bool),
typeof(AutoSelectWhenAnyChildGetsFocus),
new UIPropertyMetadata(false, Enabled_Changed));
public static bool GetEnabled(DependencyObject obj){ return (bool)obj.GetValue(EnabledProperty); }
public static void SetEnabled(DependencyObject obj, bool value){ obj.SetValue(EnabledProperty, value); }
private static void Enabled_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var attachEvents = (bool)e.NewValue;
var targetUiElement = (UIElement)sender;
if(attachEvents)
targetUiElement.IsKeyboardFocusWithinChanged += TargetUiElement_IsKeyboardFocusWithinChanged;
else
targetUiElement.IsKeyboardFocusWithinChanged -= TargetUiElement_IsKeyboardFocusWithinChanged;
}
static void TargetUiElement_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var targetUiElement = (UIElement)sender;
if(targetUiElement.IsKeyboardFocusWithin)
Selector.SetIsSelected(targetUiElement, true);
}
}
...and you simply add this as a property setter in your ListBoxItem's style
<Setter Property="behaviors:AutoSelectWhenAnyChildGetsFocus.Enabled" Value="True" />
This of course assumes you've imported an XML namespace called 'behaviors' that points to the namespace where the class is contained. You can put the class itself in a shared 'Helper' library, which is what we do. That way, everywhere we want it, its a simple property set in the XAML and the behavior takes care of everything else.
I figured out that IsKeyboardFocusWithin is not the best solution.
What I did in this case was to set the style on all of the controls used as DataTemplate to send the GotFocus-event to be handled in code behind. Then, in code behind, I searched up the visual tree (using VisualTreeHelper) to find the ListViewItem and set IsSelected to true. This way it does not "touch" the DataContext and works just with the View elements.
<Style TargetType="{x:Type Control}" x:Key="GridCellControlStyle">
...
<EventSetter Event="GotFocus" Handler="SelectListViewItemOnControlGotFocus"/>
...
private void SelectListViewItemOnControlGotFocus(object sender, RoutedEventArgs e)
{
var control = (Control)sender;
FocusParentListViewItem(control);
}
private void FocusParentListViewItem(Control control)
{
var listViewItem = FindVisualParent<ListViewItem>(control);
if (listViewItem != null)
listViewItem.IsSelected = true;
}
public static T FindVisualParent<T>(UIElement element) where T : UIElement
{
UIElement parent = element;
while (parent != null)
{
var correctlyTyped = parent as T;
if (correctlyTyped != null)
{
return correctlyTyped;
}
parent = VisualTreeHelper.GetParent(parent) as UIElement;
}
return null;
}

Resources