I am storing column widths on application exit and restore them on startup. Everything works fine unless user double click header. This would cause column width become double.NaN which I understood is a flag for autosizing. Then I have problems.
While investigating the issue I noticed what setting column width to NaN will enable auto-resizing but only for one time.
Here is a repro:
<ListView x:Name="listView">
<ListView.View>
<GridView>
<GridViewColumn Header="A" Width="NaN" />
</GridView>
</ListView.View>
</ListView>
Then add two buttons with following click handlers:
void button1_Click(object sender, RoutedEventArgs e) => listView.Items.Add("abcd");
void button2_Click(object sender, RoutedEventArgs e) => listView.Items.Add("ABCDEFGHIJKL");
Clicking button1 first will autosize column to fit "abcd". Clicking then button2 won't.
Why? Is there a workaround to have it either always autosizing or to at least disable user double-click resizing (tried this solution without success)?
You need to reset the Width of the column on each update:
void button2_Click(object sender, RoutedEventArgs e)
{
listView.Items.Add("ABCDEFGHIJKL");
GridView gv = listView.View as GridView;
gv.Columns[0].Width = gv.Columns[0].ActualWidth;
gv.Columns[0].Width = double.NaN;
}
And to disable double-click resizing you could handle the PreviewMouseLeftButtonDown event for the GridViewColumnHeader like this:
<ListView x:Name="listView">
<ListView.View>
<GridView>
<GridView.ColumnHeaderContainerStyle>
<Style TargetType="{x:Type GridViewColumnHeader}">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="listView_PreviewMouseLeftButtonDown"/>
</Style>
</GridView.ColumnHeaderContainerStyle>
<GridViewColumn Header="A" Width="Auto" />
</GridView>
</ListView.View>
</ListView>
private void listView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) =>
e.Handled = e.ClickCount == 2;
This is a well know behaviour of the WPF GridView.
A generic solution for multiple columns is to register an event handler (my personal suggestion is for SizeChanged)
<ListView x:Name="listView" SizeChanged="listView_SizeChanged">
to do the update
private void listView_SizeChanged(object sender, SizeChangedEventArgs e)
{
foreach (GridViewColumn c in ((GridView)listView.View).Columns)
{
if (double.IsNaN(c.Width))
{
c.Width = c.ActualWidth;
}
c.Width = double.NaN;
}
}
Related
I'm new to C# and XAML, so please excuse any obvious mistakes.
Let's say I wanted to create 100 checkboxes, all with the same layout, and when you click the checkbox, it makes the text in a label that's a child to that checkbox turn bold. I don't want to write out 100 functions for 100 checkboxes, I want to make a single function each checkbox can call that'll do the same thing.
<CheckBox VerticalContentAlignment="Center" Checked="CheckBox_Checked">
<WrapPanel>
<Image> Width="50" Source="Images/example.jpg"/>
<Label VerticalContentAlignment="Center">Extra cheese</Label>
</WrapPanel>
</CheckBox>
I'm able to get the WrapPanel nested under the CheckBox, but I can't seem to do the same to get the Label which is nested in the WrapPanel.
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
CheckBox _cb = (CheckBox)sender;
WrapPanel _wp = (WrapPanel)(_cb).Content;
Label _lb = (Label)(_wp).Content;
_lb.FontWeight = FontWeights.Bold;
}
The wrap panel class doesn't have a content attribute. What you can use however is the Children attribute in a combination with FirstOrDefault OfType. the method could look somethind like this.
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
CheckBox _cb = (CheckBox)sender;
WrapPanel _wp = (WrapPanel)(_cb).Content;
Label lbl = _wp.Children.OfType<Label>().FirstOrDefault();
lbl.FontWeight = FontWeights.Bold;
}
You can easily access the element you want without the need for hierarchical transformations that exist in HTML and there is no need for different transformations, so we will have the following code:
File.xml
<CheckBox VerticalContentAlignment="Center" Checked="CheckBox_Checked" Unchecked="CheckBox_Checked">
<WrapPanel>
<Image Width="50" Source="Images/config.gif"/>
<Label VerticalContentAlignment="Center">Extra cheese</Label>
</WrapPanel>
</CheckBox>
File.cs
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
if (sender is CheckBox _cb)
{
if (_cb.FontWeight == FontWeights.Normal)
_cb.FontWeight = FontWeights.Bold;
else
_cb.FontWeight = FontWeights.Normal;
}
}
I've gone through a few posts with similar requirements but haven't found a fully working solution yet.
I have a GridView in a ListView with a few columns
<ListView x:Name="TheList" DockPanel.Dock="Top"
ItemsSource="{Binding Itemlist}"
SelectionMode="Extended">
<ListView.View>
<GridView>
....
<GridViewColumn x:Name="valueColumn" Header="Value" DisplayMemberBinding="{Binding ItemValue}" Width="150"/>
</GridView>
</ListView.View>
</ListView>
I set the model and sub to the model's property changing
_viewModel = new MyModel();
DataContext = _viewModel;
_viewModel.ItemValueChanged += RunResizeLogic; //ItemValueChanged gets emitted when the property changes
And then based on some solutions I found I attempt resizing the column
private void RunResizeLogic(object sender, EventArgs e)
{
ResizeGridViewColumn(valueColumn);
}
private void ResizeGridViewColumn(GridViewColumn col)
{
Dispatcher.Invoke(DispatcherPriority.Normal,
new Action(() =>
{
if (double.IsNaN(col.Width))
{
col.Width = col.ActualWidth;
}
col.Width = double.NaN;
}));
}
My resize logic gets called when the my ItemValue changes, however, the column is only resizing when ItemValue changes again.
So, if it's current size is 5. the ItemValue size changes to 10, my logic runs with ActualWidth==5, column remains the same size (5). The ItemValue size changes again to 2, logic runs ActualWidth==2 and the column resizes to 10.
What am I doing wrong? Thanks.
EDIT:
Some of the posts I've looked through
GridViewColumn Width Adjustment
WPF: GridViewColumn resize event
I'm new in WPF/C# and I'm in trouble with bindings :(
I've got a ListView in my main Window which is binded from a list.
I've got a second window with a datagrid which is binded from another list (a copy from the first one).
My probleme is when I update datas from the datagrid (2nd window), it upgrades datas from the first window too but I don't want it. (I want the first list to be independant from the other)
Here is the code from the 1st window:
public partial class MainWindow : Window
{
List<Voiture> mesBagnoles = new List<Voiture>();
public MainWindow()
{
InitializeComponent();
...
lstViewBagnoles.ItemsSource = mesBagnoles;
...
}
}
xaml :
<ListView Grid.Row="0" x:Name="lstViewBagnoles" selectionChanged="lstViewBagnoles_SelectionChanged" Width="220">
<ListView.Resources>
<Style TargetType="GridViewColumnHeader">
<Setter Property="Visibility" Value="Collapsed"></Setter>
</Style>
</ListView.Resources>
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn DisplayMemberBinding="{Binding Marque}"/>
<GridViewColumn DisplayMemberBinding="{Binding Modele}"/>
<GridViewColumn DisplayMemberBinding="{Binding Moteur}"/>
</GridView>
</ListView.View>
</ListView>
Openning 2nd window:
private void btnVoitures_Click(object sender, RoutedEventArgs e)
{
VoituresWindow voitureWindow = new VoituresWindow(mesBagnoles);
voitureWindow.ShowDialog();
}
2nd Window :
public partial class VoituresWindow : Window
{
public List<Voiture> listBagnoles = new List<Voiture>();
public VoituresWindow(List<Voiture> e)
{
listBagnoles = e;
InitializeComponent();
dataVoitures.ItemsSource = listBagnoles;
}
}
And xaml :
<DataGrid x:Name="dataVoitures" Grid.Row="0" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="Marque" Binding="{Binding Marque}" />
<DataGridTextColumn Header="Modèle" Binding="{Binding Modele}" />
<DataGridTextColumn Header="Moteur" Binding="{Binding Moteur}" />
...
</DataGrid.Columns>
</DataGrid>
As you can see, the binding source of the listview and the datagrid are from 2 differents lists.
I think I miss something with DataContext or else but I cannot find a way
Hope you can help me :)
EDIT :
Ok so I replaced the 2nd window opening :
private void btnVoitures_Click(object sender, RoutedEventArgs e)
{
VoituresWindow voitureWindow = new VoituresWindow(mesBagnoles);
voitureWindow.ShowDialog();
}
By this:
private void btnVoitures_Click(object sender, RoutedEventArgs e)
{
List<Voiture> mesBagnoles2 = new List<Voiture>(mesBagnoles);
VoituresWindow voitureWindow = new VoituresWindow(mesBagnoles2);
voitureWindow.ShowDialog();
}
But the fist window listview is always updated by the 2nd window datagrid :/
I think there is something wrong with my binding
As sa_ddam213 & Mike Schartz say, you pass the same List to the second window, or as i can read you want a copy of this one.
Just initialize another list and affect it with the value of mesBagnoles.
Suppose I have a code in XAML like this:
<GridView>
<GridView.ItemTemplate>
<DataTemplate>
<Button Content="{Binding test}" Click="ButtonClick" />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
Then how can I get which GridViewItem was selected? Because, normally what is done is to add the ItemClick functionality to the GridView itself, but in this case I am doing something customized and need to get the SelectedItem starting from the Button.
I tried code something like this:
void ButtonClick (object sender, RoutedEventArgs e)
{
var g = (GridViewItem)((Button)sender).Parent;
}
But it does not work (returns null). Please help!
Thanks!
Sure! Here's the code that I use when the ad control fails to load an ad (like when the machine is offline). In that case I remove it form the gridview. To do that I have to locate the ad's parent gridviewitem and remove the whole thing. I do it like this:
private void AdControl_ErrorOccurred_1(object sender, Microsoft.Advertising.WinRT.UI.AdErrorEventArgs e)
{
var _Item = sender as DependencyObject;
while (!(_Item is GridViewItem))
_Item = VisualTreeHelper.GetParent(_Item);
HubGrid.Items.Remove(_Item);
}
I'm using ListView with GridView. Is there GridViewColumn resize event?
I will handle the PropertyChanged event instead. The PropertyChanged event is not seen in the Visual Studio intellisense, but you can trick it :)
GridViewColumn column = ...
((System.ComponentModel.INotifyPropertyChanged)column).PropertyChanged += (sender, e) =>
{
if (e.PropertyName == "ActualWidth")
{
//do something here...
}
};
Although GridViewColumn does not appear to have a Resize event, you can bind to the ColumnWidth property.
You can verify this with sample XAML below - no code behind needed for this example. It binds only in one direction, from the column width to the text box, and when you resize you will see the textbox immediately update with the column width.
(This is just a simple example; if you want to pick up the resize in code I would create a class with a Width property so binding will work in both directions).
<StackPanel>
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Width="{Binding ElementName=tbWidth1, Path=Text, Mode=OneWayToSource}" />
<GridViewColumn Width="{Binding ElementName=tbWidth2, Path=Text, Mode=OneWayToSource}" />
</GridView>
</ListView.View>
<ListViewItem>Item 1</ListViewItem>
<ListViewItem>Item 2</ListViewItem>
</ListView>
<TextBox Name="tbWidth1" />
<TextBox Name="tbWidth2" />
</StackPanel>
Have a look at MSDN DridViewColumn details. It does not appaer to have such an event, probably some workaround required, I am not sure though. have look here
Hope it helps.
private void ListView_Loaded( object sender, RoutedEventArgs e )
{
// Add the handler to know when resizing a column is done
((ListView)sender).AddHandler( Thumb.DragCompletedEvent, new DragCompletedEventHandler( ListViewHeader_DragCompleted ), true );
}
private void ListViewHeader_DragCompleted( object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e )
{
ListView lv = sender as ListView;
... code handing the resize goes here ...
}
XAML:
<ListView Loaded="ListView_Loaded">
Another approach: you can attach a change event handler to the GridViewColumn Width property:
PropertyDescriptor pd = DependencyPropertyDescriptor.FromProperty(
GridViewColumn.WidthProperty, typeof(GridViewColumn));
GridView gv = (GridView)myListView.View;
foreach (GridViewColumn col in gv.Columns) {
pd.AddValueChanged(col, ColumnWidthChanged);
}
...
private void ColumnWidthChanged(object sender, EventArgs e) { ... }
(Inspired by an answer here, for a similar question about DataGrid.)