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.)
Related
I have a ListBox, where the list element has a ComboBox, a TextBox and a slider. Depending on the selction of the ComboBox either the TextBox or the slider should be visible.
<ListBox Name="lstPWM" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<!-- more definitions -->
</Grid.ColumnDefinitions>
<ComboBox ItemsSource="{Binding Path=Gebertyp, Converter={local1:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectionChanged="PWMTyp_SelectionChanged"
SelectedValue="{Binding Path=Gebertyp}" />
<TextBox Visibility="{Binding GeberVisible}" Text="{Binding GeberNmr, Mode=TwoWay}"/>
<Slider Visibility="{Binding WertVisible}" Value="{Binding Wert, Mode=TwoWay}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The code behind is:
public partial class MainWindow : Window
{
public ObservableCollection<PWMKanal> PWM_col { get; set; } = new();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
lstPWM.ItemsSource = PWM_col;
foreach (var item in Board.PWM) PWM_col.Add(item); //Board.PWM is the data source.
}
private void PWMTyp_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox box = sender as ComboBox; // Finding the line in the ListBox.
PWMKanal PWM = box.DataContext as PWMKanal;
int z = PWM_col.IndexOf(PWM);
Board.PWM[z].Gebertyp = (QuellePWM)box.SelectedValue;
if (Board.PWM[z].Gebertyp == QuellePWM.Sender)
{
PWM_col[z].GeberVisible = Visibility.Visible; // I thought that i may change the
PWM_col[z].WertVisible = Visibility.Hidden; // ObservableColelction directly
} // but the display is not updated.
else // In Debug mode i see, that PWM_coll
{ // is changed as expected, but no effect
PWM_col[z].GeberVisible = Visibility.Hidden; // on the GUI.
PWM_col[z].WertVisible = Visibility.Visible;
}
if (PWM_col.Count != 0) // this code is intended to update the GUI, but every time
{ // a new item is added the Selection Change fires again
PWM_col.Clear(); // and i get a stack overflow in an endless loop.
foreach (var item in Board.PWM) PWM_col.Add(item);
}
}
}
The comments describe my approaches and problems:
I change the selected element of the ObservableCollection directly, but this has no effect on GUI. At least tho code doesn't crash.
I clear the list ObservableCollection PWM_col, but then i get an infinite loop: every time an element is added to the list the SelectionChange event fires, calling the routin again. Result is stack overflow.
Now my questions to my approaches:
Is it possible to change an element of an ObservableCollection directly by code, and the display is automatically refreshed?
Is it possible to somehow catch the SelectionChanged event before the handler is executed? Or is it possible to temporary dissable the event?
Any other idear?
Thank you for your help!
CollectionChanged does notify, that collection itself, not the
single items, is changed. Therefore to see the changes item's
property need to implement INotifyPropertyChanged. Also remove Mode=OneTime
You can of course set the flag, that PWMTyp_SelectionChanged is
running:
private bool selChangedIsRunning = false;
private void PWMTyp_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(selChangedIsRunning) return;
selChangedIsRunning = true;
// do stuff ....
selChangedIsRunning = false;
}
Other idea is - don't use the SelectionChange event, but do bind
Slider.Visibility and TextBox.Visibility to the
ComboBox.SelectedValue and use value converter to define the
Visibilty, also you can use the ConverterParameter.
<ComboBox x:Name="CmbPWMTyp" ItemsSource="{Binding Path=Gebertyp, Converter={local1:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectionChanged="PWMTyp_SelectionChanged"
SelectedValue="{Binding Path=Gebertyp}" />
<TextBox Visibility="{Binding ElementName=CmbPWMTyp, Path=SelectedValue, Converter={StaticResource YourConverter}, ConverterParameter=TBX}" Text="{Binding GeberNmr, Mode=TwoWay}"/>
<Slider Visibility="{Binding ElementName=CmbPWMTyp, Path=SelectedValue, Converter={StaticResource YourConverter}, ConverterParameter=SLDR}" Value="{Binding Wert, Mode=TwoWay}"/>
This link can be also very helpful for you: Difference between SelectedItem SelectedValue and SelectedValuePath
I'm having some trouble to set one property (MouseDown event) value based on it's sender. I have "MyPhotoA" and "MyPhotoB" binded to an observableCollection. Both trigger the same event "MyOnClick" Here is the xaml:
... stuff
<DataTemplate>
<Image Source="{Binding MyPhotoA, UpdateSourceTrigger=LostFocus}" MouseDown="MyOnClick" />
</DataTemplate>
... stuff
<DataTemplate>
<Image Source="{Binding MyPhotoB, UpdateSourceTrigger=LostFocus}" MouseDown="MyOnClick" />
</DataTemplate>
... stuff
These two datatemplates are used for two datagridtemplatecolumns in the datagrid. Hence there are two columns of images and the user clicks one. I want to set the source on the image clicked.
The event "MyOnClick" is something like this:
private void MyOnClick(object sender, MouseButtonEventArgs e)
{
var myImage File.ReadAllBytes("c:\\MyImage.jpeg")
var dc = (sender as System.Windows.Controls.Image).DataContext;
MyModelClass itemSelected = (MyModelClass)dc;
itemSelected.PhotoA = myImage;//Setting PhotoA
itemSelected.PhotoB = myImage;//Setting PhotoB
//How to set the photo based on "sender" property? Like:
//sender.[somestuff]=myImage;
}
I'd like to use the same method to set data in PhotoA and PhotoB based on the sender property binded to it. So if user click in the "PhotoA" DataGrid cell, the image is setted to "PhotoA". If click is done in "PhotoB" then "PhotoB" data is setted.
!!!Note!!!: I don't want tricks like
If (sender.name="PhotoA") then
itemSelected.PhotoA = myImage;
else
itemSelected.PhotoB = myImage;
Thanks in advance
[Workaround Update]
I could not find the answer so I used a workaround:
1)edit xaml code, adding a property "name" to each Photo:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Name="ImageMyPhotoA" Source="{Binding Photo}" MouseDown="MyOnClick" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
On the event, I manually added the bin to the the observable collection.
private void MyOnClick(object sender, MouseButtonEventArgs e)
{
var myImage = File.ReadAllBytes("c:\\MyImage.jpeg");
var dc = (sender as Image).DataContext;
MyModelClass itemSelected = (MyModelClass)dc;
var senderName = (sender as Image).Name;
if (senderName == "ImagePhotoA")
{
itemSelected.PhotoA = myImage;
}
if (senderName == "ImagePhotoB")
{
itemSelected.PhotoB = myImage;
}
}
Conclusion
Setting properties in "MouseDown" event based on Sender (Sender.[SomeSenderProperty] = "Something") seems not possible OR over complicated. I suggest to mark the sender's name in xaml (like the example). Thanks for the good fellows for your help, I really appreciate.
You're essentially trying to set the source property of an image the user clicked.
When you do that you want it to persist, presumably, and you probably won't want to overwrite the binding so make your binding twoway.
<Image Source="{Binding MyPhotoA, Mode=TwoWay}"
In your click handler.
Cast your sender to image.
var img = sender as Image;
(You should routinely null check when you do as anything.)
But this gives you a reference to the appropriate image control to work with.
Set the value.
As Clemens points out, I was overcomplicating this with:
img.SetCurrentValue(SourceProperty, Abitmapimage);
And you can just do:
img.Source = new BitmapImage(new Uri(#"C:\MyImage.jpeg"));
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;
}
}
I would like to be able to get and set the state of a checkbox in a listview. I would like to either be able to automatically update MyListItems[row].myCheckedValue when the box is clicked by somehow binding in xaml (I know very little about binding) or to be able to loop through each list item by row and access the checkboxes in C#. I don't know how to approach either. I'm just starting out with WPF.
I Could also use Checked and Unchecked events, but I don't know how to retrieve the row of the list item the checkbox is in.
<ListView Name="listView">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox x:Name="checkBox" Checked="itsChecked" Unchecked="itsUnchecked"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public List<myListItem> MyListItems;
...
listView.ItemsSource = MyListItems;
...
public class myListItem {
public bool myCheckedValue;
}
private void getCheckedItems() {
//Loop through listview rows and get checkbox state
//???
}
private void itsChecked(object sender, RoutedEventArgs e) {
//How can I get the row this checkbox is in??
}
something like
<GridViewColumn Header="Selected">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="chk" IsChecked="{Binding MyListItemsBoolField}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
assuming that your listView.ItemsSource = MyListItems; will stay
It should be as simple as binding IsChecked property of the CheckBox to a property on the ViewModel (you may need to add a new property if it doesn't already exist).
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);
}