Drag Adorner on ListViewItem with a GridView - wpf

I have a ListView containing objects and I have used a GridView to display the properties:
<ListView ItemsSource="{Binding Path=Fields}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="s_PreviewMouseLeftButtonDown" />
<EventSetter Event="PreviewMouseLeftButtonUp" Handler="s_PreviewMouseLeftButtonUp" />
<EventSetter Event="MouseMove" Handler="MouseMoveHandler" />
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView >
<GridViewColumn Header="1" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Textbox text={binding} />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Then I have a drag and drop behavior set up on the ListView so that when an ListViewItem is dragged, I clone the controls template which becomes the drag adorner:
private static ContentControl CreateClone(ListViewItem element)
{
ListViewItem control = new ListViewItem();
if (element != null)
{
control.Content = element.Content;
control.ContentTemplate = element.ContentTemplate;
control.Template = element.Template;
control.Style = element.Style;
control.Background = Brushes.LightGray;
control.Width = element.ActualWidth;
control.Height = (element.ActualHeight);
control.VerticalAlignment = VerticalAlignment.Top;
control.VerticalContentAlignment = VerticalAlignment.Stretch;
control.HorizontalContentAlignment = HorizontalAlignment.Stretch;
control.Opacity = .70;
}
return control;
}
My problem is that the ListViewItem does not have a ContenetTemplate, because im assuming it is defined by the GridView, So I end up with a blank adorner with no columns in it. Do I need to also define a template in the ItemContainerStyle?

Related

How to collapse all Group Expander in ListView?

I have a ListView with a GroupStyle on it. And in the style i have an Expander. I want to use a ContextMenu in the ListView to collapse and expand all groups with one click and i want to expand every single group by clicking on the expander. How can i get the Groups and then expand this programmatically?
<Style x:Key="PropertyGroupStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander Header="{Binding Name}" IsExpanded="True">
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ListView Name="PropertyChangeList"
IsSynchronizedWithCurrentItem="True" Height="Auto"
ItemsSource="{Binding}"
>
<ListView.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource PropertyGroupStyle}"/>
</ListView.GroupStyle>
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Name="menuItemPropertyExpanderCollapse"
Header="{Binding Path=labelCollapse, FallbackValue='Collapse'}"
Click="menuItemPropertyExpanderCollapse_Click"
/>
<MenuItem Name="menuItemPropertyExpanderExpand"
Header="{Binding Path=labelExpand, FallbackValue='Expand'}"
/>
</ContextMenu>
</ListView.ContextMenu>
<ListView.View>
<GridView AllowsColumnReorder="False" >
<GridViewColumn Header="Date Occured"
Width="20"
DisplayMemberBinding="{Binding DateOccured}" />
<GridViewColumn Header="PropertyName"
Width="Auto"
DisplayMemberBinding="{Binding PropertyName}"/>
</GridView>
</ListView.View>
</ListView>
ICollectionView PropertyListview = CollectionViewSource.GetDefaultView(hPropList);
PropertyListview.GroupDescriptions.Add(new PropertyGroupDescription("PropertyName"));
PropertyListview.SortDescriptions.Add(new SortDescription("PropertyName", ListSortDirection.Ascending));
PropertyListview.SortDescriptions.Add(new SortDescription("DateOccurred", ListSortDirection.Ascending));
PropertyChangeList.ItemsSource = PropertyListview;
Has anybody an sample code to do the collapse and expand all groups with an ContextMenu? i dont find anything out there.
You could bind the IsExpanded property to the Tag property of the ListView:
<Style x:Key="PropertyGroupStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander Header="{Binding Name}"
IsExpanded="{Binding Tag, RelativeSource={RelativeSource AncestorType=ListView}, TargetNullValue=true, FallbackValue=true}">
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
...and set the Tag property in the event handlers:
private void menuItemPropertyExpanderCollapse_Click(object sender, RoutedEventArgs e)
{
PropertyChangeList.Tag = false;
}
you answered the question right, but i forget to write more details. Yes now i can expand and collapse all groups but i cant expand anymore a single group. it is an all or nothing thing. My question missed some important details :-( I updated my question text.
Change the AncestorType of the binding to GroupItem and set the Tag property of each GroupItem by iterating through them in the visual tree:
private void menuItemPropertyExpanderCollapse_Click(object sender, RoutedEventArgs e)
{
foreach (GroupItem gi in FindVisualChildren<GroupItem>(PropertyChangeList))
gi.Tag = false;
}
private static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
XAML:
<Expander Header="{Binding Name}"
IsExpanded="{Binding Tag, RelativeSource={RelativeSource AncestorType=GroupItem}, TargetNullValue=true, FallbackValue=true}">
<ItemsPresenter />
</Expander>

How apply MinWidth for ListView columns in WPF in control template?

Following the answer to a similar question here, I was able to set the MinWidth on the XAML page.
What I would like to do is accomplish this in the control template for all GridViewColumn's in all ListView's.
Is this possible?
Update:
I tried a simple bit of sample code below, but it does not work:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}" >
<Setter Property="MinWidth" Value="200" />
</Style>
</Window.Resources>
<Grid Width="500">
<Border BorderBrush="Black" BorderThickness="2" Margin="20">
<ListView SelectionMode="Single">
<ListView.View>
<GridView>
<GridViewColumn Header="Header 1" Width="Auto">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="Hello There"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Header 2" Width="Auto" />
</GridView>
</ListView.View>
</ListView>
</Border>
</Grid>
</Window>
If you use a GridViewColumnHeader you can handle size changes:
<GridView>
<GridViewColumn>
<GridViewColumnHeader Content="HeaderContent" SizeChanged="HandleColumnHeaderSizeChanged"/>
...
in Code:
private void HandleColumnHeaderSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs)
{
if (sizeChangedEventArgs.NewSize.Width <= 60) {
sizeChangedEventArgs.Handled = true;
((GridViewColumnHeader) sender).Column.Width = 60;
}
}
<ListView>
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock MinWidth="100"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
...more columns...
</GridView>
</ListView.View>
</ListView>
<Window.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}" >
<Setter Property="MinWidth" Value="400" />
</Style>
</Window.Resources>
I stumbled into this one also. To solved it I had to do two things :
Modify the ControlTemplate of ListView's header.
Handle the DragDelta event of the Thumb inside the ControlTemplate.
ListView's header is GridViewColumnHeader.
Shown below is a simplified version of GridViewColumnHeader's ControlTemplate. As we can see, it uses a Thumb in a Canvas to create the drag/resize effect.
PS: To obtain the complete GridViewColumnHeader ControlTemplate please refer to How to grab WPF 4.0 control default templates?
<ControlTemplate TargetType="GridViewColumnHeader">
<Grid SnapsToDevicePixels="True">
<Border BorderThickness="0,1,0,1" Name="HeaderBorder" ...>
<!-- omitted -->
</Border>
<Border BorderThickness="1,0,1,1" Name="HeaderHoverBorder" Margin="1,1,0,0" />
<Border BorderThickness="1,1,1,0" Name="HeaderPressBorder" Margin="1,0,0,1" />
<Canvas>
<Thumb Name="PART_HeaderGripper">
<!-- omitted -->
</Thumb>
</Canvas>
</Grid>
<ControlTemplate.Triggers>
<!-- omitted -->
</ControlTemplate.Triggers>
So In order to limit the size of GridViewColumnHeader, we need to hook Thumb's drag events(DragStarted, DragDelta, DragCompleted...etc).
Turned out all we need is the DragDelta event as long we can know the MinSize within the DragDeltaEventHandler.
Shown below is modified XAML with comment.
<Grid Width="500">
<Border BorderBrush="Black" BorderThickness="2" Margin="20">
<ListView SelectionMode="Single">
<ListView.View>
<GridView>
<GridViewColumn Header="Header 1" Width="Auto">
<!-- Apply a style targeting GridViewColumnHeader with MinWidth = 80 and a ControlTemplate -->
<GridViewColumn.HeaderContainerStyle>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="MinWidth" Value="80" />
<Setter Property="Control.Template" Value="{DynamicResource myGridViewColumnHeaderControlTemplate}" />
</Style>
</GridViewColumn.HeaderContainerStyle>**
</GridViewColumn>
<GridViewColumn Header="Header 2" Width="Auto" />
</GridView>
</ListView.View>
</ListView>
</Border>
</Grid>
In the myGridViewColumnHeaderControlTemplate add some XAML to:
Bind GridViewColumnHeader's MinWidth to Canvas's MinWidth.
Hook up Thumb's DragDelta event.
<ControlTemplate x:Key="TemplateGridViewColumnHeader" TargetType="GridViewColumnHeader">
<!-- omitted -->
<Canvas MinWidth="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=MinWidth, Mode=OneTime}">
<Thumb x:Name="PART_HeaderGripper" DragDelta="myGridViewColumnHeader_DragDelta">
Finally the myGridViewColumnHeader_DragDelta function:
private void myGridViewColumnHeader_DragDelta(object sender, DragDeltaEventArgs e)
{
DependencyObject parent = sender as DependencyObject;
try
{
do
{
parent = VisualTreeHelper.GetParent(parent as DependencyObject);
} while (parent.GetType() != typeof(Canvas));
Canvas canvas = parent as Canvas;
if (canvas.ActualWidth + e.HorizontalChange < canvas.MinWidth)
{
e.Handled = true;
}
}
catch
{
}
}
This is the only way i find working. Do hope there is a simpler way.
I wanted to apply a minwidth to all columns, so I wrote this:
public static class GridViewConstraints
{
public static readonly DependencyProperty MinColumnWidthProperty =
DependencyProperty.RegisterAttached("MinColumnWidth", typeof(double), typeof(GridViewConstraints), new PropertyMetadata(75d, (s,e) =>
{
if(s is ListView listView)
{
listView.Loaded += (lvs, lve) =>
{
if(listView.View is GridView view)
{
foreach (var column in view.Columns)
{
SetMinWidth(listView, column);
((System.ComponentModel.INotifyPropertyChanged)column).PropertyChanged += (cs, ce) =>
{
if (ce.PropertyName == nameof(GridViewColumn.ActualWidth))
SetMinWidth(listView, column);
};
}
}
};
}
}));
private static void SetMinWidth(ListView listView, GridViewColumn column)
{
double minWidth = (double)listView.GetValue(MinColumnWidthProperty);
if (column.Width < minWidth)
column.Width = minWidth;
}
public static double GetMinColumnWidth(DependencyObject obj) => (double)obj.GetValue(MinColumnWidthProperty);
public static void SetMinColumnWidth(DependencyObject obj, double value) => obj.SetValue(MinColumnWidthProperty, value);
}
Just drop it on your listview:
<ListView b:GridViewConstraints.MinColumnWidth="255" />
Update to the solution of Billy Jake O'Connor who gave the most simple, easy to implement and WORKING CORRECTLY solution of them all.
For the people who don't want all columns to share the same minimum width, with the next code update you can set specific minimum width for each column separately specifying the min width directly in the column properties.
public static class GridColumn {
public static readonly DependencyProperty MinWidthProperty =
DependencyProperty.RegisterAttached("MinWidth", typeof(double), typeof(GridColumn), new PropertyMetadata(75d, (s, e) => {
if(s is GridViewColumn gridColumn ) {
SetMinWidth(gridColumn);
((System.ComponentModel.INotifyPropertyChanged)gridColumn).PropertyChanged += (cs, ce) => {
if(ce.PropertyName == nameof(GridViewColumn.ActualWidth)) {
SetMinWidth(gridColumn);
}
};
}
}));
private static void SetMinWidth(GridViewColumn column) {
double minWidth = (double)column.GetValue(MinWidthProperty);
if(column.Width < minWidth)
column.Width = minWidth;
}
public static double GetMinWidth(DependencyObject obj) => (double)obj.GetValue(MinWidthProperty);
public static void SetMinWidth(DependencyObject obj, double value) => obj.SetValue(MinWidthProperty, value);
}
And the XAML could be something like this ("local" is your using namespace name, modify accordingly)
<ListView>
<ListView.View>
<GridView>
<GridViewColumn local:GridColumn.MinWidth="25" />
<GridViewColumn local:GridColumn.MinWidth="100" />
<GridViewColumn local:GridColumn.MinWidth="200" />
</GridView>
</ListView.View>
</ListView>
You can try this, for each column, if you want to set different minimum width for all columns and maximum to auto
<ListView.View>
<GridView >
<GridViewColumn Header="FILE NAME" DisplayMemberBinding="{Binding fileName}" Width="auto" >
<GridViewColumn.HeaderContainerStyle>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="MinWidth" Value="200" />
</Style>
</GridViewColumn.HeaderContainerStyle>
</GridViewColumn>
<GridViewColumn Header="ERROR DETAILS" DisplayMemberBinding="{Binding errorMessage}" Width="auto">
<GridViewColumn.HeaderContainerStyle>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="MinWidth" Value="396" />
</Style>
</GridViewColumn.HeaderContainerStyle>
</GridViewColumn>
</GridView>
</ListView.View>

Listview item text truncated after itemssource change

I have WPF ListView and MVVM. ListView is a part of simple parent-child structure. Parent is also ListView control. When I change selected item in parent control, ItemsSource for child control is updated. If, for example, in first ItemsSource the longest text of the items contains 5 characters, after changing ItemsSource, every item's text is visible up to 5 characters (in new ItemsSource). How can I override this problem?
Code example:
<ListView Grid.Row="0" Grid.Column="3" ItemsSource="{Binding Tasks}" Width="200" Height="250" VerticalAlignment="Bottom" SelectionMode="Extended">
<ListView.View>
<GridView>
<GridView.ColumnHeaderContainerStyle>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</GridView.ColumnHeaderContainerStyle>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>
</GridView>
</ListView.View>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource {x:Type ListViewItem}}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
I use BureauBlue theme but anyway, if I don't use it, I have the same problem.
Part of the code in ViewModel:
private void MyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "CurrentParentItem")
{
if (this.CurrentParentItem != null)
{
this.Tasks = GetTasks();//ObservableCollection
}
else
{
this.Tasks = null;
}
}
}
GridView does not recalculate width with a new data. To reset width you can use the following
foreach (GridViewColumn c in gv.Columns)
{
// Code below was found in GridViewColumnHeader.OnGripperDoubleClicked() event handler (using Reflector)
// i.e. it is the same code that is executed when the gripper is double clicked
// if (adjustAllColumns || App.StaticGabeLib.FieldDefsGrid[colNum].DispGrid)
if (adjustAllColumns || fdGridSorted[colNum].AppliedDispGrid)
{
if (double.IsNaN(c.Width))
{
c.Width = c.ActualWidth;
}
c.Width = double.NaN;
}
}

WPF Sortable Listview with Checkbox in MVVM pattern

I have a WPF ListView with checkbox in MVVM pattern. I need to accomplish the following two tasks.
1) Sort by any column when I click on column header. If column is already in ascending order then it should reorder in descending and vice versa.
2) SelectedTaskItem is not communicating with ViewModel when i check or uncheck a checkbox.
TaskView.xaml
<UserControl x:Class="MyProject.TaskView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="569" Width="954"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
>
<UserControl.Resources>
<DataTemplate x:Key="FirstCellCheckBox">
<CheckBox
Command="{Binding IsSelected, Mode= TwoWay}"
IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type ListViewItem}}}"
CommandParameter="{Binding Path=SelectedTaskItem,
ElementName=dgTaskList, UpdateSourceTrigger=PropertyChanged}"
/>
</DataTemplate>
</UserControl.Resources>
<ListView Grid.Row="1"
Name="ListViewTask"
Margin="12,49,26,79"
ItemsSource="{Binding TaskList}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
>
<ListView.View >
<GridView x:Name="gvTaskList">
<GridViewColumn Header="Select"
CellTemplate="{StaticResource FirstCellCheckBox}"
Width="30"/>
<GridViewColumn Header="Internal File"
DisplayMemberBinding="{Binding TaskID}"
Width="100"/>
<GridViewColumn Header="TaskDescription"
DisplayMemberBinding="{Binding TaskDescription}"
Width="100" />
<GridViewColumn Header="Task Status"
DisplayMemberBinding="{Binding TaskStatus}"
Width="100" />
</GridView>
</ListView.View>
</ListView>
TaskViewModel.cs
namespace MyProject
{
public class TaskViewModel: ViewModelBase
{
ObservableCollection<TaskModel> _TaskList;
public TaskViewModel()
{
TaskDAO dal = new TaskDAO();
_TaskList= dal.GetUpFileList();
}
public ObservableCollection<TaskModel> TaskList
{
get { return _TaskList; }
set
{
if (_TaskList!= value)
{
this._TaskList= value;
this.OnPropertyChanged("TaskList");
}
}
}
private TaskModel _selectedTaskItem;
public TaskModel SelectedTaskItem
{
get { return _selectedTaskItem; }
set
{
if (value != null)
{
_selectedTaskItem= value;
OnPropertyChanged("SelectedTaskItem");
if (null != _selectedTaskItem)
{
ObservableCollection<TaskModel> oCol =
new ObservableCollection<TaskModel>();
foreach (TaskModel itm in TaskList)
{
if (itm.TaskID == _selectedTaskItem.TaskID)
{
itm.IsSelected = true;
}
oCol.Add(itm);
}
TaskList.Clear();
TaskList = oCol;
OnPropertyChanged("TaskList");
}
}
}
}
}
You are binding the CheckBox's IsChecked value to ListBoxItem.IsChecked, but I don't see anything that binds ListBoxItem.IsChecked to your ViewModel.
Try adding the following to your ListBox.Resources
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
For sorting, I'd recommend using a DataGrid instead of a ListView, since sorting is built into the DataGrid. If you don't want to do that, you'll probably have to make some custom ListViewHeaders which execute a SortCommand in your ViewModel

WPF: How to set column width with auto fill in ListView with custom user control

A ListView with Datatemplate in GridViewColumn:
<ListView Name ="LogDataList" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding LogDataCollection}" Background="Cyan">
<ListView.View>
<GridView AllowsColumnReorder="true"
ColumnHeaderToolTip="Event Log Information">
<GridViewColumn Header="Event Log Name" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<l:MyTextBlock Height="25" DataContext="{Binding LogName, Converter={StaticResource DataFieldConverter}}" HighlightMatchCase="{Binding Element}" Loaded="EditBox_Loaded"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
...
</GridView>
</ListView.View>
</ListView>
I have no idea about how to make column width autofill although I have tried a lot of way to walk up.
The general idea for demo is :
<ListView Name ="LogDataList" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding LogDataCollection}" Background="Cyan">
<ListView.Resources>
<Style x:Key="ColumnWidthStyle" TargetType="{x:Type GridViewColumn}">
<Style.Setters>
<Setter Property="HorizontalContentAlignment" Value="Stretch" >
</Setter>
</Style.Setters>
</Style>
</ListView.Resources>
<ListView.View>
<GridView AllowsColumnReorder="true"
ColumnHeaderToolTip="Event Log Information">
<GridViewColumn Header="Event Log Name" DisplayMemberBinding="{Binding Path=LogName}" HeaderContainerStyle="{StaticResource ColumnWidthStyle}">
It works, but not accord with my demand. I need to customize datatemplate with my custom user control(MyTextBlock) since the enhancement(HighlighMatchCase property) and binding datacontext.
How can I set up ColumnWidthMode with Fill in the word? On-line'in.
I really appreciate your help.
This is work for me. First, add Text property to MyTextBlock since it is not inherited from System.Windows.Controls.TextBlock but User Control.
public object Text
{
get { return GetValue(TextProperty); }
set
{
SetValue(TextProperty, value);
}
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
"Text",
typeof(object),
typeof(MyTextBlock),
new PropertyMetadata(null, new PropertyChangedCallback(TextChangedCallback)));
static void TextChangedCallback(DependencyObject property,
DependencyPropertyChangedEventArgs args)
{
MyTextBlock textBox = (MyTextBlock)property;
textBox.textBlock.Text = args.NewValue.ToString();
}
Then, resize column width manually like this:
private void ResizeColumnWidth()
{
foreach (GridViewColumn column in LogGridView.Columns)
{
column.Width = column.ActualWidth;
column.Width = double.NaN;
}
}

Resources