So I have pretty much followed every example I can find on how to set the drag/drop effect on a WPF control to None to show the circle with a line symbol, but to no avail.
Here is the code I have copied from various examples, modifying it to get it work:
This is the code behind for the Window:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace TreeViewWithCheckBoxes
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
FooViewModel root = this.tree.Items[0] as FooViewModel;
base.CommandBindings.Add(
new CommandBinding(
ApplicationCommands.Undo,
(sender, e) => // Execute
{
e.Handled = true;
root.IsChecked = false;
this.tree.Focus();
},
(sender, e) => // CanExecute
{
e.Handled = true;
e.CanExecute = (root.IsChecked != false);
}));
this.tree.Focus();
}
private void TreeViewItemDragEnter(object sender, DragEventArgs e)
{
e.Effects = DragDropEffects.None;
if (e.Data.GetDataPresent(typeof(FooViewModel)))
{
var folderViewModel = e.Data.GetData(typeof(FooViewModel))
as FooViewModel;
var treeViewItem =
VisualTreeHelperUtils.FindParent<TreeViewItem>
((DependencyObject)e.OriginalSource);
if (treeViewItem == null)
{
return;
}
var dropTarget = treeViewItem.Header as FooViewModel;
if (dropTarget == null || folderViewModel == null)
{
return;
}
if (dropTarget.Parent == folderViewModel.Parent)
e.Effects = e.AllowedEffects;
else
{
e.Effects = DragDropEffects.None;
}
}
}
private void TreeViewItemDrop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(typeof(FooViewModel)))
{
var folderViewModel = e.Data.GetData(typeof(FooViewModel))
as FooViewModel;
var treeViewItem =
VisualTreeHelperUtils.FindParent<TreeViewItem>
((DependencyObject)e.OriginalSource);
var dropTarget = treeViewItem.Header as FooViewModel;
if (dropTarget == null || folderViewModel == null)
return;
if (dropTarget.Parent == folderViewModel.Parent)
{
var dropType = dropTarget.Name;
var dragType = folderViewModel.Name;
var parent = dropTarget.Parent;
if (parent != null)
{
var dropLocation = -1;
var dragLocation = -1;
for (int index = 0; index < parent.Children.Count; ++index)
{
if (parent.Children[index].Name == dropType) dropLocation = index;
if (parent.Children[index].Name == dragType) dragLocation = index;
if (dropLocation != -1 && dragLocation != -1)
break;
}
if (dropLocation != -1 && dragLocation != -1)
{
parent.Children[dropLocation] = folderViewModel;
parent.Children[dragLocation] = dropTarget;
}
}
}
}
}
private void TreeViewItemMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
var mousePos = e.GetPosition(null);
var diff = StartPoint - mousePos;
if (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance
|| Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
{
var treeView = sender as TreeView;
var treeViewItem =
VisualTreeHelperUtils.FindParent<TreeViewItem>((DependencyObject)e.OriginalSource);
if (treeView == null || treeViewItem == null)
return;
var folderViewModel = treeView.SelectedItem as FooViewModel;
if (folderViewModel == null)
return;
var dragData = new DataObject(folderViewModel);
DragDrop.DoDragDrop(treeViewItem, dragData, DragDropEffects.Move);
}
}
}
private void TreeViewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
StartPoint = e.GetPosition(null);
}
public Point StartPoint { get; set; }
private void TreeViewItemDragOver(object sender, DragEventArgs e)
{
var container = sender as FrameworkElement;
if (container == null)
{
return;
}
var scrollViewer = VisualTreeHelperUtils.GetFirstVisualChild<ScrollViewer>(container);
if (scrollViewer == null)
{
return;
}
double tolerance = 30;
double verticalPos = e.GetPosition(container).Y;
double offset = 20;
if (verticalPos < tolerance) // Top of visible list?
{
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - offset); //Scroll up.
}
else if (verticalPos > container.ActualHeight - tolerance) //Bottom of visible list?
{
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + offset); //Scroll down.
}
e.Effects = DragDropEffects.None;
var source = e.OriginalSource as DependencyObject;
if (source == null)
{
return;
}
if (e.Data.GetDataPresent(typeof(FooViewModel)))
{
var folderViewModel = e.Data.GetData(typeof(FooViewModel))
as FooViewModel;
var treeViewItem =
VisualTreeHelperUtils.FindParent<TreeViewItem>((DependencyObject)e.OriginalSource);
if (treeViewItem == null)
{
return;
}
var dropTarget = treeViewItem.Header as FooViewModel;
if (dropTarget == null || folderViewModel == null)
{
return;
}
if (dropTarget.Parent == folderViewModel.Parent)
e.Effects = e.AllowedEffects;
else
{
e.Effects = DragDropEffects.None;
}
}
}
}
}
And here is the XAML for it, showing that it has registered with the proper events:
<Window
x:Class="TreeViewWithCheckBoxes.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TreeViewWithCheckBoxes"
xmlns:dw="clr-namespace:DrWPF.Windows.Controls"
FontSize="13"
Title="TreeView with CheckBoxes"
Width="300" Height="300"
WindowStartupLocation="CenterScreen"
>
<Window.Resources>
<ResourceDictionary>
<!-- Load this specific theme because the Aero theme for CheckBox has issues. -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/PresentationFramework.Royale;V3.0.0.0;31bf3856ad364e35;component\themes\royale.normalcolor.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style x:Key="TreeViewItemStyle" TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True" />
<Setter Property="IsSelected" Value="{Binding IsInitiallySelected, Mode=OneTime}" />
<Setter Property="KeyboardNavigation.AcceptsReturn" Value="True" />
<Setter Property="dw:VirtualToggleButton.IsVirtualToggleButton" Value="True" />
<Setter Property="dw:VirtualToggleButton.IsChecked" Value="{Binding IsChecked}" />
</Style>
<HierarchicalDataTemplate
x:Key="CheckBoxItemTemplate"
ItemsSource="{Binding Children, Mode=OneTime}"
>
<StackPanel Orientation="Horizontal">
<!-- These elements are bound to a FooViewModel object. -->
<CheckBox
Focusable="False"
IsChecked="{Binding IsChecked}"
VerticalAlignment="Center"
/>
<ContentPresenter
Content="{Binding Name, Mode=OneTime}"
Margin="2,0"
/>
</StackPanel>
</HierarchicalDataTemplate>
</ResourceDictionary>
</Window.Resources>
<Window.DataContext>
<ObjectDataProvider
MethodName="CreateFoos"
ObjectType="{x:Type local:FooViewModel}" />
</Window.DataContext>
<TabControl>
<TabItem Header="Original">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height=".2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button
Grid.Row="3"
Command="Undo"
Content="Generate Report"
HorizontalAlignment="Center"
Margin="0,2"
Padding="8,0"
/>
<TreeView
Grid.Row="0"
x:Name="tree"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"
ItemsSource="{Binding Mode=OneTime}"
ItemTemplate="{StaticResource CheckBoxItemTemplate}"
AllowDrop="True"
PreviewMouseLeftButtonDown="TreeViewMouseLeftButtonDown"
PreviewMouseMove="TreeViewItemMouseMove"
DragEnter="TreeViewItemDragEnter"
DragOver="TreeViewItemDragOver"
Drop="TreeViewItemDrop"
/>
<ScrollViewer Grid.Row="2" IsEnabled="{Binding ElementName=tree, Path=SelectedItem.IsChecked}">
<TextBlock Text="Oh Hai"></TextBlock>
<ScrollViewer.Style>
<Style TargetType="ScrollViewer">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5"></Setter>
<Setter Property="VerticalScrollBarVisibility" Value="Hidden"></Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Opacity" Value="1"></Setter>
<Setter Property="VerticalScrollBarVisibility" Value="Auto"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</ScrollViewer.Style>
</ScrollViewer>
</Grid>
</TabItem>
<TabItem Header="Modified">
<DockPanel>
<Button
DockPanel.Dock="Bottom"
Command="Undo"
Content="Uncheck All"
HorizontalAlignment="Center"
Margin="0,2"
Padding="8,0"
/>
<TreeView
x:Name="modified"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"
ItemsSource="{Binding Mode=OneTime}"
ItemTemplate="{StaticResource CheckBoxItemTemplate}"
/>
</DockPanel>
</TabItem>
</TabControl>
</Window>
I have not attached any code for the helper methods... those all work fine, and the basic idea of scrolling up/down while dragging, and exchanging items within a block works all fine.
However, the silly icon refuses to change for me. :)
I even tried to set it to None at the very top of the handler, and only set it back to AllowedEffects IF the parents are the same.
Essentially, the icon should switch to the circle with the line through it (None) if dragging to a parent that is not the same...
I have even set breakpoints to ensure that it is going into the case of the parents not being the same and setting the effect to None. Somehow, something must be switching it back, but I've no idea what...
Apparently, you also have to set e.Handled = true within the handler after setting e.Effects. After doing this, it works perfectly.
Related
I am trying to create a custom panel that allows users to arrange controls as they see fit. This seems to be working expect that I can't get vertical scrolling to work. I have tried setting the height of my panel, using the scrollviewer etc. Horizontal scrolling works like a charm.
Could you please help me figure out what I am missing. I am wondering if it is a templating issue or something of the sort. The SignalLayoutPanel is the panel that I would like to scroll.
Main window xaml:
<dock:DockingManager x:Name="Dock1" Grid.Row="1"
AllowMixedOrientation="True"
DocumentsSource="{Binding Path=VisibleDockWindows}"
AnchorablesSource="{Binding Path=Tools}"
ActiveContent="{Binding ActiveDocument, Mode=TwoWay, Converter={StaticResource ActiveDocumentConverter}}"
>
<dock:DockingManager.Theme>
<dock:AeroTheme/>
</dock:DockingManager.Theme>
<dock:DockingManager.LayoutItemTemplateSelector><!--Section determines which View is used-->
<pane:PanesTemplateSelector>
<pane:PanesTemplateSelector.FileViewTemplate>
<DataTemplate>
<local:SignalWindow AllowDrop="True" Background="LightGray" Height="Auto"/> <!--Panel not scrolling-->
</DataTemplate>
</pane:PanesTemplateSelector.FileViewTemplate>
<pane:PanesTemplateSelector.SignalExplorerViewTemplate>
<DataTemplate>
<local:SignalExplorerTree/>
</DataTemplate>
</pane:PanesTemplateSelector.SignalExplorerViewTemplate>
<pane:PanesTemplateSelector.PanelExplorerViewTemplate>
<DataTemplate>
<local:PanelExplorerWindow/>
</DataTemplate>
</pane:PanesTemplateSelector.PanelExplorerViewTemplate>
<pane:PanesTemplateSelector.PropertiesViewTemplate>
<DataTemplate>
<local:PropertyViewer1/>
</DataTemplate>
</pane:PanesTemplateSelector.PropertiesViewTemplate>
</pane:PanesTemplateSelector>
</dock:DockingManager.LayoutItemTemplateSelector>
<dock:DockingManager.LayoutItemContainerStyleSelector>
<pane:PanesStyleSelector>
<pane:PanesStyleSelector.ToolStyle>
<Style TargetType="{x:Type dock:LayoutAnchorableItem}">
<Setter Property="Title" Value="{Binding Model.Title}"/>
<Setter Property="Visibility" Value="{Binding Model.IsVisible, ConverterParameter={x:Static Visibility.Hidden}, Converter={StaticResource btvc}, Mode=TwoWay}"/>
<Setter Property="CloseCommand" Value="{Binding Model.CloseCommand}"/>
<Setter Property="IsActive" Value="{Binding Model.IsActive, Mode=TwoWay}"/>
<Setter Property="ContentId" Value="{Binding Model.ContentID}"/>
</Style>
</pane:PanesStyleSelector.ToolStyle>
<pane:PanesStyleSelector.FileStyle>
<Style TargetType="{x:Type dock:LayoutItem}">
<Setter Property="Title" Value="{Binding Model.Title}"/>
<Setter Property="CloseCommand" Value="{Binding Model.CloseCommand}"/>
<Setter Property="ContentId" Value="{Binding Model.ContentID}"/>
</Style>
</pane:PanesStyleSelector.FileStyle>
</pane:PanesStyleSelector>
</dock:DockingManager.LayoutItemContainerStyleSelector>
</dock:DockingManager>
SignalWindow.xaml:
<UserControl x:Class="ControllerInterfaceWithExplorer2.SignalWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ControllerInterfaceWithExplorer2"
xmlns:lv ="clr-namespace:ControllerInterfaceWithExplorer2.View"
xmlns:VM="clr-namespace:ControllerInterfaceWithExplorer2.ViewModel"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="100"
AllowDrop="True">
<UserControl.Resources>
<VM:SignalTemplateSelector x:Key="myDataTemplateSelector"/>
<DataTemplate x:Key="DID">
<local:SignalViewer/>
</DataTemplate>
<DataTemplate x:Key="RID">
<local:RoutineViewer/>
</DataTemplate>
<DataTemplate x:Key="IO">
<local:IOViewer/>
</DataTemplate>
</UserControl.Resources>
<Grid Height="Auto" Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="80"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
Grid.Row="0" Height="80">
<ItemsControl Grid.Row="0" ItemsSource="{Binding Signals}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}">
<ItemsControl.ItemsPanel >
<ItemsPanelTemplate>
<!--Panel that won't scroll vertically-->
<lv:SignalLayoutPanel Height="Auto" AllowDrop="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
<!--<StackPanel Grid.Row="1" Orientation="Horizontal">
<Button Content="Read" Click="Button_Click"/>
<Button Content="Write" Click="Button_Click_1"/>
</StackPanel>-->
<!--<DockPanel x:Name="dockPanel" MouseDown="dockPanel_MouseDown" MouseMove="dockPanel_MouseMove" MouseUp="dockPanel_MouseUp">
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
<Button Content="Read" Click="Button_Click"/>
<Button Content="Write" Click="Button_Click_1"/>
</StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" CanContentScroll="True" Height="150" Width="Auto">
<ItemsControl DockPanel.Dock="Top" ItemsSource="{Binding Signals}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}" MouseDown="dockPanel_MouseDown">
<ItemsControl.ItemsPanel >
<ItemsPanelTemplate>
<lv:SignalLayoutPanel AllowDrop="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</DockPanel>-->
</Grid>
SignalLayoutPanel.cs:
public class SignalLayoutPanel:Panel
{
public SignalLayoutPanel()
{
this.Background = Brushes.Chartreuse;
this.MouseMove += SignalLayoutPanel_MouseMove;
this.MouseEnter += SignalLayoutPanel_MouseEnter;
this.MouseLeave += SignalLayoutPanel_MouseLeave;
this.DragEnter += SignalLayoutPanel_DragEnter;
this.MouseDown += SignalLayoutPanel_MouseDown;
this.MouseUp += SignalLayoutPanel_MouseUp;
this.Drop += SignalLayoutPanel_Drop;
this.SourceUpdated += SignalLayoutPanel_SourceUpdated;
this.TargetUpdated += SignalLayoutPanel_TargetUpdated;
}
private void SignalLayoutPanel_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_currentControl = null;
InvalidateArrange();
}
private StackPanel _currentControl;
private void SignalLayoutPanel_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var result = VisualTreeHelper.HitTest(this, e.GetPosition(this));
_currentControl = GetCurrentStackPanel(result);
}
private void SignalLayoutPanel_TargetUpdated(object sender, System.Windows.Data.DataTransferEventArgs e)
{
}
private void SignalLayoutPanel_SourceUpdated(object sender, System.Windows.Data.DataTransferEventArgs e)
{
}
private void SignalLayoutPanel_Drop(object sender, DragEventArgs e)
{
var data = e.Data.GetData(typeof(ViewModel.SignalViewModel));
if (data != null)
{
ViewModel.SignalViewModel vm = data as ViewModel.SignalViewModel;
ViewModel.SignalWindowViewModel svw = this.DataContext as ViewModel.SignalWindowViewModel;
if (vm.Model.GetType() == typeof(DataModel.DIDClass))
{
double tempX = e.GetPosition(this).X;
double tempY = e.GetPosition(this).Y;
foreach (ViewModel.SignalViewModel signalView in vm.Children)
{
signalView.PositionX = tempX;
signalView.PositionY = tempY;
//Signals.Add(signalView);
svw.Add(signalView);
tempY += 40;
}
}
else
{
vm.PositionX = e.GetPosition(this).X;
vm.PositionY = e.GetPosition(this).Y;
svw.Add(vm);
}
}
}
private void SignalLayoutPanel_DragEnter(object sender, DragEventArgs e)
{
e.Effects = DragDropEffects.None;
var data = e.Data.GetData(typeof(ViewModel.SignalViewModel));
//get signal
if (data != null)
{
e.Effects = DragDropEffects.All;
}
else
{
}
}
private void SignalLayoutPanel_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
}
private void SignalLayoutPanel_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
}
private void SignalLayoutPanel_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if(e.LeftButton == System.Windows.Input.MouseButtonState.Pressed && _currentControl != null)
{
if(_currentControl.DataContext is ViewModel.SignalViewModel)
{
ViewModel.SignalViewModel svm = _currentControl.DataContext as ViewModel.SignalViewModel;
svm.PositionX = e.GetPosition(this).X;
svm.PositionY = e.GetPosition(this).Y;
}
}
}
protected override Size MeasureOverride(Size availableSize)
{
Size idealSize = new Size(0, 0);
// Allow children as much room as they want - then scale them
Size size = new Size(Double.PositiveInfinity, Double.PositiveInfinity);
foreach (UIElement child in Children)
{
child.Measure(size);
idealSize.Width += child.DesiredSize.Width;
idealSize.Height = 20;// Math.Max(idealSize.Height, child.DesiredSize.Height);
}
// EID calls us with infinity, but framework doesn't like us to return infinity
if (double.IsInfinity(availableSize.Height) || double.IsInfinity(availableSize.Width))
return idealSize;
else
return availableSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
//double x = 5;
//double y = 5;
//for(int i=0; i < InternalChildren.Count; i++)
//{
// InternalChildren[i].Arrange(new Rect(new Point(x, y), InternalChildren[i].DesiredSize));
// y += InternalChildren[i].DesiredSize.Height;
//}
foreach(UIElement child in InternalChildren)
{
if( ((System.Windows.FrameworkElement)child).DataContext is ViewModel.SignalViewModel)
{
ViewModel.SignalViewModel svm = ((System.Windows.FrameworkElement)child).DataContext as ViewModel.SignalViewModel;
child.Arrange(new Rect(new Point(svm.PositionX, svm.PositionY), child.DesiredSize));
}
}
return finalSize;
}
private StackPanel GetCurrentStackPanel(HitTestResult hitTest)
{
StackPanel retVal = null;
var element = hitTest.VisualHit as FrameworkElement;
FrameworkElement temp = element;
while (temp != null && !(temp is StackPanel))
if (temp != null)
temp = temp.Parent as FrameworkElement;
if (temp != null)
retVal = temp as StackPanel;
return retVal;
}
}
I Have a ComboBox With 3 Items (Room,Class,HighSchool) And a TextBox.
I Want When i Write Room IN TextBox Immediately Combobox.
SelectedItem = Room
You'd be better off using a custom control. I use an intellisense text box that you bind to a list, when you start typing it will automatically select any item in the list with that character.
window xaml below;
<UserControl.Resources>
<Style x:Key="ListBoxItemStyle" TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="_Border"
Padding="2"
SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="_Border" Property="Background" Value="Gray"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<TextBox Name="textbox" Grid.Column="1" Grid.Row="2" GotMouseCapture="textbox_GotMouseCapture"
GotFocus="textbox_GotFocus" PreviewKeyDown="textbox_PreviewKeyDown" TextChanged="textbox_TextChanged"
DataContext="{Binding ElementName=intelliWin}"
Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="{Binding ElementName=intelliWin, Path=Width}" TextAlignment="Center" Height="20"/>
<Popup Name="popup" Height="Auto" Width="Auto" MinWidth="180" StaysOpen="False" Placement="Bottom"
PlacementTarget="{Binding ElementName=textbox}" HorizontalAlignment="Left">
<Popup.Style>
<Style TargetType="Popup">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="IsOpen" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>
</Popup.Style>
<Grid>
<ListBox Name="listbox" ScrollViewer.HorizontalScrollBarVisibility="Hidden"
MouseUp="listbox_MouseUp" ItemContainerStyle="{StaticResource ListBoxItemStyle}">
</ListBox>
</Grid>
</Popup>
</Grid>
to use it in the window you need to do this
<local:IntellisenseStyleTextbox Grid.Column="1" Grid.Row="6" Text="{Binding YOUR LIST DATA, UpdateSourceTrigger=PropertyChanged}" x:Name="intelliseText" Width="200" HorizontalContentAlignment="Center"/>'
then add this to the .cs file
public partial class IntellisenseStyleTextbox : UserControl, INotifyPropertyChanged
{
public IntellisenseStyleTextbox()
{
InitializeComponent();
(this.Content as FrameworkElement).DataContext = this;
}
public List<string> PossibleItems { get; set; }
public event EventHandler ItemSelected;
protected void OnItemSelected(EventArgs e)
{
EventHandler handler = ItemSelected;
if (handler != null)
{
handler(this, e);
}
}
// Bindable Text property from http://blog.jerrynixon.com/2013/07/solved-two-way-binding-inside-user.html
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValueDp(TextProperty, value);
}
}
// Required to allow items to bind to Text property
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string),
typeof(IntellisenseStyleTextbox), null);
public event PropertyChangedEventHandler PropertyChanged;
void SetValueDp(DependencyProperty property, object value,
[System.Runtime.CompilerServices.CallerMemberName] string p = null)
{
SetValue(property, value);
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
public List<string> GetDisplayedModelNames()
{
// Only display items that have the current text at the start of them
List<string> toDisplay = new List<string>();
string match = textbox.Text.ToLower();
foreach (string possibleItem in PossibleItems)
{
string lower = possibleItem.ToLower();
if (lower.Contains(match))
{
toDisplay.Add(possibleItem);
}
}
return toDisplay;
}
private void textbox_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
switch (e.Key)
{
// Up and down arrow keys move up and down list box
case Key.Down:
listbox.SelectedIndex++;
listbox.ScrollIntoView(listbox.SelectedItem);
break;
case Key.Up:
// Make sure item is always selected
if (listbox.SelectedIndex > 0)
{
listbox.SelectedIndex--;
listbox.ScrollIntoView(listbox.SelectedItem);
}
break;
// Enter key fills in OutputName with selected text
case Key.Enter:
SelectListBoxItem();
break;
case Key.Back:
popup.IsOpen = true;
break;
}
base.OnPreviewKeyDown(e);
}
private void SelectListBoxItem()
{
if (listbox.SelectedIndex < 0) return;
List<string> outputNames = GetDisplayedModelNames();
if (listbox.SelectedIndex < outputNames.Count)
{
this.Text = outputNames[listbox.SelectedIndex];
// Move caret to end of text
textbox.CaretIndex = this.Text.Count();
popup.IsOpen = false;
OnItemSelected(new EventArgs());
}
}
private void listbox_MouseUp(object sender, MouseButtonEventArgs e)
{
SelectListBoxItem();
}
private void textbox_GotMouseCapture(object sender, MouseEventArgs e)
{
OpenPopup();
}
private void textbox_GotFocus(object sender, RoutedEventArgs e)
{
OpenPopup();
}
private void OpenPopup()
{
listbox.ItemsSource = GetDisplayedModelNames();
popup.IsOpen = true;
}
private void textbox_TextChanged(object sender, TextChangedEventArgs e)
{
OnPropertyChanged(new DependencyPropertyChangedEventArgs(TextProperty, textbox.Text, textbox.Text));
// Update listbox with relevant names
if (PossibleItems == null) return;
listbox.ItemsSource = this.GetDisplayedModelNames();
}
}
Is it possible to do something like this with WPF's ItemsControl: Demo
I am trying to freeze the GroupedItems rather than the GridView Columns.
Resources:
<Window.Resources>
<CollectionViewSource x:Key="data" Source="{Binding}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Date"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
ListView:
<ListView Grid.Column="0" ItemsSource="{Binding Source={StaticResource data}}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Col 1" DisplayMemberBinding="{Binding Col1}" Width="100"/>
<GridViewColumn Header="Col 2" DisplayMemberBinding="{Binding Col2}" Width="100"/>
<GridViewColumn Header="Col 3" DisplayMemberBinding="{Binding Col3}" Width="100"/>
</GridView.Columns>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<TextBlock Background="Beige" FontWeight="Bold" Text="{Binding Path=Name, StringFormat={}{0}}"/>
</Grid>
<DockPanel Grid.Row="1">
<ItemsPresenter Grid.Row="2"></ItemsPresenter>
</DockPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
Code behind:
public MainWindow()
{
InitializeComponent();
List<String> colList1 = new List<string>() { "Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7" };
List<String> colList2 = new List<string>() { "1", "2", "3", "4", "5", "6" };
ObservableCollection<Data> dataCollection = new ObservableCollection<Data>();
for (var a = 0; a < 100; a++)
{
Random rnd = new Random();
int min = rnd.Next(5000);
int rnd1 = rnd.Next(0, 6);
int rnd2 = rnd.Next(0, 5);
dataCollection.Add(new Data()
{
Date = DateTime.Now.AddMinutes(min).ToString("hh:MM tt"),
Col1 = colList1[rnd2],
Col2 = String.Format("Col2: {0}", "X"),
Col3 = colList2[rnd2]
});
}
this.DataContext = dataCollection;
}
public class Data
{
public string Date { get; set; }
public string Col1 { get; set; }
public string Col2 { get; set; }
public string Col3 { get; set; }
}
My solution uses a TextBlock overlay that shares the group header style. The positioning and correct HitTesting is the tricky part, but I'm quite confident this does not break for small changes in layout or logic.
I was not sure if you want to hide the ColumnHeader or not, but this is easy and doesn't need any other adjustments than what is depicted here.
Code behind:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace WpfApplication1
{
public partial class FreezingGroupHeader : UserControl
{
private double _listviewHeaderHeight;
private double _listviewSideMargin;
public FreezingGroupHeader()
{
InitializeComponent();
List<String> colList1 = new List<string>() { "Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7" };
List<String> colList2 = new List<string>() { "1", "2", "3", "4", "5", "6" };
ObservableCollection<Data> dataCollection = new ObservableCollection<Data>();
Random rnd = new Random();
for (var a = 0; a < 100; a++)
{
int min = rnd.Next(5000);
int rnd1 = rnd.Next(0, 6);
int rnd2 = rnd.Next(0, 5);
dataCollection.Add(
new Data()
{
Date = DateTime.Now.AddMinutes(min).ToString("hh:MM tt"),
Col1 = colList1[rnd2],
Col2 = String.Format("Col2: {0}", "X"),
Col3 = colList2[rnd2]
}
);
}
this.DataContext = dataCollection;
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
// Position frozen header
GetListViewMargins(this.listview1);
Thickness margin = this.frozenGroupHeader.Margin;
margin.Top = _listviewHeaderHeight;
margin.Right = SystemParameters.VerticalScrollBarWidth + _listviewSideMargin;
margin.Left = _listviewSideMargin;
this.frozenGroupHeader.Margin = margin;
UpdateFrozenGroupHeader();
}
private void listview1_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
UpdateFrozenGroupHeader();
}
/// <summary>
/// Sets text and visibility of frozen header
/// </summary>
private void UpdateFrozenGroupHeader()
{
if (listview1.HasItems)
{
// Text of frozenGroupHeader
GroupItem group = GetFirstVisibleGroupItem(this.listview1);
if (group != null)
{
object data = group.Content;
this.frozenGroupHeader.Text = data.GetType().GetProperty("Name").GetValue(data, null) as string; // slight hack
}
this.frozenGroupHeader.Visibility = Visibility.Visible;
}
else
this.frozenGroupHeader.Visibility = Visibility.Collapsed;
}
/// <summary>
/// Sets values that will be used in the positioning of the frozen header
/// </summary>
private void GetListViewMargins(ListView listview)
{
if (listview.HasItems)
{
object o = listview.Items[0];
ListViewItem firstItem = (ListViewItem)listview.ItemContainerGenerator.ContainerFromItem(o);
if (firstItem != null)
{
GroupItem group = FindUpVisualTree<GroupItem>(firstItem);
Point p = group.TranslatePoint(new Point(0, 0), listview);
_listviewHeaderHeight = p.Y; // height of columnheader
_listviewSideMargin = p.X; // listview borders
}
}
}
/// <summary>
/// Gets the first visible GroupItem in the listview
/// </summary>
private GroupItem GetFirstVisibleGroupItem(ListView listview)
{
HitTestResult hitTest = VisualTreeHelper.HitTest(listview, new Point(5, _listviewHeaderHeight + 5));
GroupItem group = FindUpVisualTree<GroupItem>(hitTest.VisualHit);
return group;
}
/// <summary>
/// walk up the visual tree to find object of type T, starting from initial object
/// http://www.codeproject.com/Tips/75816/Walk-up-the-Visual-Tree
/// </summary>
private static T FindUpVisualTree<T>(DependencyObject initial) where T : DependencyObject
{
DependencyObject current = initial;
while (current != null && current.GetType() != typeof(T))
{
current = VisualTreeHelper.GetParent(current);
}
return current as T;
}
public class Data
{
public string Date { get; set; }
public string Col1 { get; set; }
public string Col2 { get; set; }
public string Col3 { get; set; }
}
}
}
Xaml:
<UserControl x:Class="WpfApplication1.FreezingGroupHeader"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
>
<UserControl.Resources>
<CollectionViewSource x:Key="data" Source="{Binding}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Date"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style x:Key="GroupHeaderStyle1" TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="Beige" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
</UserControl.Resources>
<Grid>
<ListView x:Name="listview1" Grid.Column="0" ItemsSource="{Binding Source={StaticResource data}}" ScrollViewer.ScrollChanged="listview1_ScrollChanged" >
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Col 1" DisplayMemberBinding="{Binding Col1}" Width="100"/>
<GridViewColumn Header="Col 2" DisplayMemberBinding="{Binding Col2}" Width="100"/>
<GridViewColumn Header="Col 3" DisplayMemberBinding="{Binding Col3}" Width="100"/>
</GridView.Columns>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Style="{StaticResource GroupHeaderStyle1}" Text="{Binding Name, StringFormat={}{0}}" />
</Grid>
<DockPanel Grid.Row="1">
<ItemsPresenter Grid.Row="2"></ItemsPresenter>
</DockPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
<TextBlock x:Name="frozenGroupHeader" Style="{StaticResource GroupHeaderStyle1}" VerticalAlignment="Top"/>
</Grid>
</UserControl>
As i just ran into a similar issue and the 'hack-ish' solution did not fit my needs and i generally dont like 'hack-ish' stuff in production environments, i developed a generic solution to this which i'd like to share.
The attached Class has following key-features:
MVVM compatible
no Code-Behind
compatible with ListView, GridView, ItemsControl, even static xaml! - should work with anything using a ScollViewer ...
Uses attached property to declare the group item
xaml usage (just your inner ControlTemplate):
<ControlTemplate TargetType="{x:Type GroupItem}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" local:StickyScrollHeader.AttachToControl="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Grid}}">
<TextBlock Background="Beige" FontWeight="Bold" Text="{Binding Path=Name, StringFormat={}{0}}"/>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
<DockPanel Grid.Row="1">
<ItemsPresenter Grid.Row="2"></ItemsPresenter>
</DockPanel>
</Grid>
</ControlTemplate>
The Class (put anywhere, add xaml-namespace if necessary):
public static class StickyScrollHeader
{
public static FrameworkElement GetAttachToControl(FrameworkElement obj)
{
return (FrameworkElement)obj.GetValue(AttachToControlProperty);
}
public static void SetAttachToControl(FrameworkElement obj, FrameworkElement value)
{
obj.SetValue(AttachToControlProperty, value);
}
private static ScrollViewer FindScrollViewer(FrameworkElement item)
{
FrameworkElement treeItem = item;
FrameworkElement directItem = item;
while (treeItem != null)
{
treeItem = VisualTreeHelper.GetParent(treeItem) as FrameworkElement;
if (treeItem is ScrollViewer)
{
return treeItem as ScrollViewer;
}
else if (treeItem is ScrollContentPresenter)
{
return (treeItem as ScrollContentPresenter).ScrollOwner;
}
}
while (directItem != null)
{
directItem = directItem.Parent as FrameworkElement;
if (directItem is ScrollViewer)
{
return directItem as ScrollViewer;
}
else if (directItem is ScrollContentPresenter)
{
return (directItem as ScrollContentPresenter).ScrollOwner;
}
}
return null;
}
private static ScrollContentPresenter FindScrollContentPresenter(FrameworkElement sv)
{
int childCount = VisualTreeHelper.GetChildrenCount(sv);
for (int i = 0; i < childCount; i++)
{
if (VisualTreeHelper.GetChild(sv, i) is FrameworkElement child && child is ScrollContentPresenter)
{
return child as ScrollContentPresenter;
}
}
for (int i = 0; i < childCount; i++)
{
if (FindScrollContentPresenter(VisualTreeHelper.GetChild(sv, i) as FrameworkElement) is FrameworkElement child && child is ScrollContentPresenter)
{
return child as ScrollContentPresenter;
}
}
return null;
}
public static readonly DependencyProperty AttachToControlProperty =
DependencyProperty.RegisterAttached("AttachToControl", typeof(FrameworkElement), typeof(StickyScrollHeader), new PropertyMetadata(null, (s, e) =>
{
try
{
if (!(s is FrameworkElement targetControl))
{ return; }
Canvas.SetZIndex(targetControl, 999);
ScrollViewer sv;
FrameworkElement parent;
if (e.OldValue is FrameworkElement oldParentControl)
{
ScrollViewer oldSv = FindScrollViewer(oldParentControl);
parent = oldParentControl;
oldSv.ScrollChanged -= Sv_ScrollChanged;
}
if (e.NewValue is FrameworkElement newParentControl)
{
sv = FindScrollViewer(newParentControl);
parent = newParentControl;
sv.ScrollChanged += Sv_ScrollChanged;
}
void Sv_ScrollChanged(object sender, ScrollChangedEventArgs sce)
{
if (!parent.IsVisible) { return; }
try
{
ScrollViewer isv = sender as ScrollViewer;
ScrollContentPresenter scp = FindScrollContentPresenter(isv);
var relativeTransform = parent.TransformToAncestor(scp);
Rect parentRenderRect = relativeTransform.TransformBounds(new Rect(new Point(0, 0), parent.RenderSize));
Rect intersectingRect = Rect.Intersect(new Rect(new Point(0, 0), scp.RenderSize), parentRenderRect);
if (intersectingRect != Rect.Empty)
{
TranslateTransform targetTransform = new TranslateTransform();
if (parentRenderRect.Top < 0)
{
double tempTop = (parentRenderRect.Top * -1);
if (tempTop + targetControl.RenderSize.Height < parent.RenderSize.Height)
{
targetTransform.Y = tempTop;
}
else if (tempTop < parent.RenderSize.Height)
{
targetTransform.Y = tempTop - (targetControl.RenderSize.Height - intersectingRect.Height);
}
}
else
{
targetTransform.Y = 0;
}
targetControl.RenderTransform = targetTransform;
}
}
catch { }
}
}
catch { }
}));
}
Hope this also helps others running into this issue ;)
This solution is not great, and it's hack-ish, but it will basically do what you want. I made the listview headers invisible, put a textblock above the listview, and set the text value to the groupitem of the first visible item in the listbox. Hacky, but it's the best I came up with.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<String> colList1 = new List<string>() { "Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7" };
List<String> colList2 = new List<string>() { "1", "2", "3", "4", "5", "6" };
ObservableCollection<Data> dataCollection = new ObservableCollection<Data>();
for (var a = 0; a < 100; a++)
{
Random rnd = new Random();
int min = rnd.Next(5000);
int rnd1 = rnd.Next(0, 6);
int rnd2 = rnd.Next(0, 5);
dataCollection.Add(
new Data()
{
Date = DateTime.Now.AddMinutes(min).ToString("hh:MM tt"),
Col1 = colList1[rnd2],
Col2 = String.Format("Col2: {0}", "X"),
Col3 = colList2[rnd2]
}
);
}
this.DataContext = dataCollection;
}
public class Data
{
public string Date { get; set; }
public string Col1 { get; set; }
public string Col2 { get; set; }
public string Col3 { get; set; }
}
private void grid_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (grid.Items.Count > 0)
{
HitTestResult hitTest = VisualTreeHelper.HitTest(grid, new Point(5, 5));
System.Windows.Controls.ListViewItem item = GetListViewItemFromEvent(null, hitTest.VisualHit) as System.Windows.Controls.ListViewItem;
if (item != null)
Head.Text = ((Data)item.Content).Date;
}
}
System.Windows.Controls.ListViewItem GetListViewItemFromEvent(object sender, object originalSource)
{
DependencyObject depObj = originalSource as DependencyObject;
if (depObj != null)
{
// go up the visual hierarchy until we find the list view item the click came from
// the click might have been on the grid or column headers so we need to cater for this
DependencyObject current = depObj;
while (current != null && current != grid)
{
System.Windows.Controls.ListViewItem ListViewItem = current as System.Windows.Controls.ListViewItem;
if (ListViewItem != null)
{
return ListViewItem;
}
current = VisualTreeHelper.GetParent(current);
}
}
return null;
}
}
XAML:
<Window x:Class="header.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="auto" SizeToContent="Width">
<Window.Resources>
<CollectionViewSource x:Key="data" Source="{Binding}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Date"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style x:Key="myHeaderStyle" TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<TextBlock Name="Head" Grid.Row="0"/>
<ListView Name="grid" Grid.Column="0" Grid.Row="1" ItemsSource="{Binding Source={StaticResource data}}" Height="300" ScrollViewer.ScrollChanged="grid_ScrollChanged">
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource myHeaderStyle}">
<GridView.Columns>
<GridViewColumn DisplayMemberBinding="{Binding Col1}" Width="100"/>
<GridViewColumn DisplayMemberBinding="{Binding Col2}" Width="100"/>
<GridViewColumn DisplayMemberBinding="{Binding Col3}" Width="100"/>
</GridView.Columns>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Background="Beige" FontWeight="Bold" Text="{Binding Path=Name, StringFormat={}{0}}"/>
</Grid>
<DockPanel Grid.Row="1">
<ItemsPresenter Grid.Row="2"></ItemsPresenter>
</DockPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</StackPanel>
</Grid>
EDIT: Fixed ScrollChanged event.
private void grid_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (grid.Items.Count > 0)
{
Point point = new Point(5, 5);
foreach(Data lvItem in grid.Items)
{
HitTestResult hitTest = VisualTreeHelper.HitTest(grid, point);
ListViewItem item = GetListViewItemFromEvent(null, hitTest.VisualHit) as System.Windows.Controls.ListViewItem;
if (item != null)
{
Data value = ((Data)item.Content);
Head.Text = ((Data)item.Content).Date;
break;
}
else
{
point.X += 5;
point.Y += 5;
}
}
}
}
I have a TabControl and I want a preview window just like the taskbar in Windows 7.
When I hoover the mouse over tabItem1, I get the result that I want. When I select the 2nd or 3rd tab (the content of tabItem1 now not visible) and then hoover over tabItem1, I expected the same preview. But now the content is disproportioned. What am I doing wrong?
I have the following MainWindow:
<Window.Resources>
<Style x:Key="TabItemContainerStyle" TargetType="{x:Type TabItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<Label Content="{Binding}">
<Label.Style>
<Style TargetType="Label">
<EventSetter Event="MouseEnter" Handler="TabItem_MouseEnter"/>
<EventSetter Event="MouseLeave" Handler="TabItem_MouseLeave"/>
</Style>
</Label.Style>
</Label>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<TabControl Name="tabControl" ItemContainerStyle="{StaticResource TabItemContainerStyle}" Grid.Row="1">
<TabItem Header="tabItem1" Name="tabItem1">
<Grid>
<Grid Name="grid1" Background="#4700D700">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0*" />
<ColumnDefinition Width="200*" />
</Grid.ColumnDefinitions>
<Label Content="Hello, world!" Height="28" HorizontalAlignment="Left" Margin="37,29,0,0" Name="label1" VerticalAlignment="Top" Grid.Column="1" />
</Grid>
</Grid>
</TabItem>
<TabItem Header="Tab 2"></TabItem>
<TabItem Header="Tab 3"></TabItem>
</TabControl>
</Grid>
And this is the code behind:
public partial class MainWindow : Window
{
private Window _previewWindow;
public MainWindow()
{
InitializeComponent();
}
private static TabItem GetTabItem(object sender)
{
var control = sender as Control;
if (control == null)
return null;
var parent = control.TemplatedParent as ContentPresenter;
if (parent == null)
return null;
return parent.TemplatedParent as TabItem;
}
void previewWindow_SizeChanged(object sender, SizeChangedEventArgs e)
{
var window = sender as Window;
if (window != null)
window.Top -= window.ActualHeight;
}
private void TabItem_MouseEnter(object sender, MouseEventArgs e)
{
var tabItem = GetTabItem(sender);
if (tabItem != null)
{
var vb = new VisualBrush(tabItem.Content as Visual)
{
Viewport = new Rect(new Size(160, 80)),
Viewbox = new Rect(new Size(160, 80)),
};
var myRectangle = new Rectangle
{
Width = 160,
Height = 80,
Stroke = Brushes.Transparent,
Margin = new Thickness(0, 0, 0, 0),
Fill = vb
};
Point renderedLocation = ((Control)sender).TranslatePoint(new Point(0, 0), this);
_previewWindow = new Window
{
WindowStyle = WindowStyle.ToolWindow,
SizeToContent = SizeToContent.WidthAndHeight,
ShowInTaskbar = false,
Content = myRectangle,
Left = renderedLocation.X + Left,
Top = renderedLocation.Y + Top,
};
// Top can only be calculated when the size is changed to the content,
// therefore the SizeChanged-event is triggered.
_previewWindow.SizeChanged += previewWindow_SizeChanged;
_previewWindow.Show();
}
e.Handled = true;
}
private void TabItem_MouseLeave(object sender, MouseEventArgs e)
{
if (_previewWindow != null)
_previewWindow.Close();
_previewWindow = null;
}
}
I'm trying to get the TreeView control in my application to properly bind to a tree of objects by setting its ItemsSource and DataContext properties. The tree visualizes as expected, but the TreeViewItems data context seems to hold incorrect values.
For example, I have a tree that looks like this:
[-] Header
[-] Contents
[+] Item1
[+] Item2
properties
[+] Dictionary
[-] MetaDictionary
[+] TypeDef1
[+] TypeDef2
properties
The items are bound to the objects' Data.Name value. However, if I click any item that is a child of Header and examine it in the event handler, its DataContext.Data.Name says Header (after appropriate castings). Same thing happens with MetaDictionary and its children.
This is a shortened version of my class:
public class CFItemTreeNode
{
private CFItem data;
public CFItem Data
{
get { return data; }
set { data = value; }
}
private ObservableCollection<CFItemTreeNode> children;
public ObservableCollection<CFItemTreeNode> Children
{
//set & get as above
}
private CFItemTreeNode parent;
public CFItemTreeNode Parent
{
//set & get as above
}
}
And this is my XAML. I've been scouring SO and the net for several days and I've incorporated bits and pieces of various tutorials and questions into this Frankenstein of mine. I believe it's a problem with the hierarchical template, but that's as far as I've gotten.
<Window x:Class="SSLowLevelBrowser.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SSLowLevelBrowser"
Title="MainWindow"
Height="600"
Width="800"
MinHeight="100"
MinWidth="200"
Closing="MainWindowClosing">
<Window.Resources>
<Style x:Key="TreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
<!-- A margin of 0 px left and 2 px top -->
<Setter Property="Margin" Value="0 2" />
<Setter Property="AllowDrop" Value="true" />
<EventSetter Event="TreeViewItem.PreviewMouseLeftButtonDown" Handler="TVI_PreviewMouseLeftButtonDown" />
<EventSetter Event="TreeViewItem.PreviewMouseMove" Handler="TVI_PreviewMouseMove" />
<EventSetter Event="TreeViewItem.PreviewDrop" Handler="TVI_PreviewDrop" />
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="575*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="390*" />
<ColumnDefinition Width="390*" />
</Grid.ColumnDefinitions>
<ToolBar Name="menuBar" Grid.ColumnSpan="2" ToolBarTray.IsLocked="True">
<Button Name="buttonOpen" Click="OpenFile">Open file</Button>
</ToolBar>
<TreeView Grid.Row="1"
Grid.Column="0"
Name="treeView"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"
ItemsSource="{Binding}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:CFItemTreeNode}" ItemsSource="{Binding Children}">
<Grid>
<TextBlock Text="{Binding Path=Data.Name}"
MouseLeftButtonDown="TBlock_PreviewMouseLeftButtonDown"/>
<TextBox Text="{Binding Path=Data.Name, Mode=TwoWay}"
Visibility="Collapsed"
LostFocus="TBox_LostFocus"/>
</Grid>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
<TextBox Grid.Row="1" Grid.Column="1" Name="textOutput" />
</Grid>
</Window>
What am I doing wrong?
Update 1. Here are my event handlers:
private void TVI_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs args)
{
dragStartPosition = args.GetPosition(null);
dragSource = args.OriginalSource;
}
private void TVI_PreviewMouseMove(object sender, MouseEventArgs args)
{
Point currentPosition = args.GetPosition(null);
// If there is actual movement and a drag is starting
if (dragInProgress == false &&
dragStartPosition.X != -1 &&
args.LeftButton == MouseButtonState.Pressed &&
Math.Pow(currentPosition.X - dragStartPosition.X, 2) +
Math.Pow(currentPosition.Y - dragStartPosition.Y, 2) > 25)
{
dragInProgress = true;
DragDropEffects de = DragDrop.DoDragDrop(
(TreeViewItem)sender,
new DataObject(typeof(FrameworkElement), dragSource),
DragDropEffects.Move);
}
}
private void TVI_PreviewDrop(object sender, DragEventArgs args)
{
if (dragInProgress && args.Data.GetDataPresent(typeof(FrameworkElement)))
{
CFItemTreeNode dragSource =
((CFItemTreeNode)((FrameworkElement)args.Data.GetData(typeof(FrameworkElement))).DataContext);
CFItemTreeNode dropTarget =
((CFItemTreeNode)((FrameworkElement)args.OriginalSource).DataContext);
CFItemTreeNode sourceParent = dragSource.Parent;
CFItemTreeNode targetParent = dropTarget.Parent;
if (sourceParent != targetParent)
{
MessageBox.Show("Can only swap siblings.");
dragInProgress = false;
return;
}
int sourceIndex = sourceParent.Children.IndexOf(dragSource);
int targetIndex = sourceParent.Children.IndexOf(dropTarget);
if (sourceIndex != targetIndex)
{
if (sourceIndex < targetIndex)
{
sourceParent.Children.RemoveAt(targetIndex);
sourceParent.Children.RemoveAt(sourceIndex);
sourceParent.Children.Insert(sourceIndex, dropTarget);
sourceParent.Children.Insert(targetIndex, dragSource);
}
else
{
sourceParent.Children.RemoveAt(sourceIndex);
sourceParent.Children.RemoveAt(targetIndex);
sourceParent.Children.Insert(targetIndex, dragSource);
sourceParent.Children.Insert(sourceIndex, dropTarget);
}
}
dragSource = null;
dragInProgress = false;
// Reset start position to invalid
dragStartPosition = new Point(-1, -1);
}
}
Add RelativeSource={RelativeSource AncestorType=local:MainWindow} to your binding.