Listview item text truncated after itemssource change - wpf

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;
}
}

Related

Drag Adorner on ListViewItem with a GridView

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?

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>

WPF GridView Column and dynamic cell template based upon data value

I have a GridView where I can generate the Columns at run time based upon the configuration settings. e.g
Text Column = to show the text in it
Image Column = to display the graphic in there.
but the problem is that the whole column would be shown either of them type.
What I want to do is based upon the data of particular cell, I want
(a) choose a dynamic template
(b) render the contents dynamically (ideally) as FrameworkElement and set the cell content with that.
This means that the each cell can either display text or image. so in first row, 1st column could be text and 2nd row, 1st column could be graphics based upon the data value.
Is there a way to choose the template based upon run time content value of particular cell or completely render a UIElement and then set as value of the cell?
Example:-
Cell data = <bold>Hello. I'm bold</bold> => displays the text as bold (TextBlock Element)
Cell data = <img>test.png</img> => displays the graphic (Image Element)
You can achieve this with Triggers
Here is the sample code. .
<ListView Margin="10" Name="lvUsers">
<ListView.View>
<GridView x:Name="gridview">
<GridViewColumn Header="Type">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsImage}" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Text goes here"
Foreground="Red"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding IsImage}" Value="False">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Image goes here"/>
<!--<Image Source="{Binding source}" />-->
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
And the sample backend code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<myClass> mc = new List<myClass>();
mc.Add(new myClass() { Itemsource = "textblock text", IsImage = false });
mc.Add(new myClass() { Itemsource = "Image source", IsImage = true });
lvUsers.ItemsSource = mc;
}
}
class myClass
{
public string Itemsource { get; set; }
public bool IsImage { get; set; }
}

Dynamic template for a ComboBox in ListBox

I have a ListBox with an embedded ComboBox:
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<ComboBox Width="100" IsEditable="False" Height="20">
<TextBlock Text="Opt#1"></TextBlock>
<TextBlock Text="Opt#2"></TextBlock>
<TextBlock Text="Opt#3"></TextBlock>
</ComboBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'd like to present the ComboBox as a simple text (e.g. TextBlock) when a ListBox row is not selected, and show it as a ComboBox when the ListBox row is selected.
I was thinking that replacing ComboBox template dynamically would do the trick. How to accomplish that?
Thanks,
Leszek
The best way to swap templates is to use the ItemTemplateSelector propery of the ListBox and set it to a class you create which inherits from DataTemplateSelector.
Here is a link that provides an example: http://msdn.microsoft.com/en-us/library/system.windows.controls.datatemplateselector.aspx
I would simply use a style that replace the ListBox.ItemTemplate whenever the ListBoxItem becomes selected.
Here's a quick example
<ListBox.Resources>
<DataTemplate x:Key="TextBoxTemplate">
<TextBlock Text="{Binding }" />
</DataTemplate>
<DataTemplate x:Key="ComboBoxTemplate">
<ComboBox SelectedItem="{Binding }">
<ComboBoxItem>Opt#1</ComboBoxItem>
<ComboBoxItem>Opt#2</ComboBoxItem>
<ComboBoxItem>Opt#3</ComboBoxItem>
</ComboBox>
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template" Value="{StaticResource TextBoxTemplate}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Template" Value="{StaticResource ComboBoxTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
I'd actually suggest using IsKeyboardFocusWithin instead of IsSelected as the trigger property, because templates can let you interact with them without setting the item as selected.
Thanks Josh and Rachel for pointing me in a right direction.
I came up with a solution similar to the one suggested by Rachel. My problem was I could not make ItemTemplateSelector work and I did not know how to pass the state IsSelected from my listbox. I also could not use DataTemplate because my ListBox item is much more complex than a single element (I simplified it in my previous post for the sake of example).
Anyway, I came up with the following solution. It's not very elegant but it works:
I defined a new style in Application resources:
<Style x:Key="TextBlockTemplate" TargetType="ComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBlock Text="{Binding}" Margin="3" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I attached SelectionChanged and PreviewMouseDown handlers to my ListBox:
I defined MyListBox_PreviewMouseDown:
private void MyListBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
// Grab the selected list box item.
object element = (e.OriginalSource as FrameworkElement).DataContext;
var item = MyListBox.ItemContainerGenerator.ContainerFromItem(element)
as ListBoxItem;
// Mark the row in the ListBox as selected.
if (item != null)
item.IsSelected = true;
}
I defined MyListBox_SelectionChanged:
private ComboBox prevComboBox = null;
private void MyListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Grab the list box.
ListBox list = sender as ListBox;
// Although there could be only one item selected,
// we iterate over all selected items.
foreach (MyDataItem dat in list.SelectedItems)
{
var item = list.ItemContainerGenerator.ContainerFromItem(dat) as ListBoxItem;
// FindElement is a helper method to find an element in a visual tree.
ComboBox cbo = FindElement(item, "MyComboBox") as ComboBox;
if (cbo != prevComboBox)
{
cbo.Style = null;
if (prevComboBox != null)
prevComboBox.Style =
(Style)Application.Current.Resources["TextBlockTemplate"];
prevComboBox = cbo;
}
}
}
Thanks,
Leszek

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>

Resources