I'm using a ListView that contains a GridView, there's a gridviewcolumn which only contains buttons. In the buttonclick event, how do I get the position (I just need the row in this case) of the button which fired the event?
<ListView.View>
<GridView>
<GridViewColumn Width="Auto">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Width="Auto" Height="Auto" Background="#00000000" BorderBrush="#00000000" BorderThickness="0" Click="Finalizado_Click">
<Button.Content>
<Image Source="Content/okay.png" Width="8" Height="8"/>
</Button.Content>
</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
I know the columns are automatically autogeneratedfields, if you change them to buttonfields you might be able to get a little more information. Sorry I can't help more than that.
public ListViewItem GetRow(DependencyObject item)
{
DependencyObject dObj = VisualTreeHelper.GetParent(item);
if (dObj == null)
return null;
if (dObj is ListViewItem)
return dObj as ListViewItem;
return GetRow(dObj);
}
Related
I have created a Grid and adding rows dynamically. And I have a remove button in every row. On click of that remove button I want to remove that particular row.
I am quite new to WPF, can some one help me out to achieve this.
Thanks.
Xaml code:
<Grid Grid.Column="2" Visibility="Collapsed" Name="operationalGrid">
<Button Height="25" Name="btnRemove" Width="24" Style="{DynamicResource ButtonStyle}" Grid.Column="4" Click="btnRemove_Click">
<materialDesign:PackIcon Background="Transparent" Height="25" Width="30" Cursor="Hand" ToolTip="Remove" Kind="Minus" HorizontalAlignment="Center" Foreground="White" VerticalAlignment="Top" Margin="0,0,0,0">
</materialDesign:PackIcon>
</Button>
</Grid>
c# code:
Adding rows:
RowDefinition row = new RowDefinition();
row.Height = GridLength.Auto;
if (ucDynamicControls != null)
ucDynamicControls.btnToggle.Visibility = ucDynamicControls.btnToggle.Visibility = Visibility.Visible;
ucDynamicControls = new ucControls();
grdContols.RowDefinitions.Add(row);
Grid.SetRow(ucDynamicControls, controlCount++);
grdContols.Children.Add(ucDynamicControls);
Instead of using row definitions, you may use ItemsControl.
As example:
First define your ItemTemplate
<ItemsControl Name="icTodoList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Title}" />
<ProgressBar Grid.Column="1" Minimum="0" Maximum="100" Value="{Binding Completion}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Define your ItemsPanel
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
Full tutorial:
https://www.wpf-tutorial.com/list-controls/itemscontrol/
You have come up with a very complex, one might say, perverse way of accomplishing the task.
A typical implementation is to create a container element with data. Then the collection with these elements is passed to ItemsControl. And he already visual each element as it should.
In this implementation: deleting a row is removing an item from the collection.
In your implementation, you need to parse the Visual Element Tree.
It is possible, but difficult.
The button, as I understand it, is inside the Grid row.
This Grid is inside ucDynamicControls.
And the outer Grid row already contains this ucDynamicControls.
If I understood correctly, then in the button event it is necessary to go up from the button to the Grid containing ucDynamicControls.
In this Grid, remove the ucDynamicControls element from the Children collection.
And the line (in which it) is to collapse: Heigh = 0.
And it's not clear why you chose Grid, and StackPanel for the ucDynamicControls collection?
If you use a StackPanel, then it will be possible not to set the line number and for it will be enough just to remove the desired ucDynamicControls from the collection.
An example implementation (if I understand your code correctly):
private void btnRemove_Click(object sender, RoutedEventArgs e)
{
if (!(sender is Button button))
return;
DependencyObject child = button;
DependencyObject parent;
while (!((parent = LogicalTreeHelper.GetParent(child)) is UcDynamicControls) && parent != null)
child = parent;
if (parent == null)
return;
UcDynamicControls ucDynamicControls = (UcDynamicControls)parent;
int row = Grid.GetRow(ucDynamicControls);
Grid grid = LogicalTreeHelper.GetParent(ucDynamicControls) as Grid;
if (grid == null || grid.RowDefinitions.Count <= row)
return;
grid.RowDefinitions[row].Height = new GridLength(0);;
grid.Children.Remove(ucDynamicControls);
}
I would like to resize columns of a grid view into ListView to contents. There is the XAML code:
<ListView x:Name="listViewStatEntries"
AlternationCount="2" Grid.Row="8" Grid.ColumnSpan="3" Background="White"
ItemsSource="{Binding StatResult.StatEntries}" >
<ListView.View>
<GridView>
<GridViewColumn Width="Auto" DisplayMemberBinding="{Binding Param.Speciality}">
<GridViewColumn.Header>
<GridViewColumnHeader Content="{Binding Labels[2851]}" Tag="Param.Speciality" Click="listViewStatEntriesColumnHeader_Click" Width="Auto" />
</GridViewColumn.Header>
</GridViewColumn>
<GridViewColumn Width="Auto" DisplayMemberBinding="{Binding Alarm}">
<GridViewColumn.Header>
<GridViewColumnHeader Content="{Binding Labels[2765]}" Tag="Alarm" Click="listViewStatEntriesColumnHeader_Click" Width="Auto" />
</GridViewColumn.Header>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
I found this thread: Force resize of GridView columns inside ListView and try Oskar solution:
public void AutoSizeColumns()
{
GridView gv = listView1.View as GridView;
if (gv != null)
{
foreach (var 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 (double.IsNaN(c.Width))
{
c.Width = c.ActualWidth;
}
c.Width = double.NaN;
}
}
}
But with this solution, columns was resized to visible contents only and not to all data into the ListView. How can I resize column to all data?
Thanks in advance for your help !
The reason they resize only to the visible rows is because you have virtualization on (which is default). Try turning it off
<ListView VirtualizingStackPanel.IsVirtualizing="False"
I have a 2 column ListView and I'm trying to fill it using and IDictionary with the following code.
System.Collections.IDictionary entryList = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User);
foreach (System.Collections.DictionaryEntry de in entryList)
{
Row row = new Row();
row.Name1 = (string)de.Key;
row.Name2 = (string)de.Value;
this.list1.Items.Add(row);
}
public class Row
{
public string Name1 { get; set; }
public string Name2 { get; set; }
}
XAML:
<ListView x:Name="varList"
Grid.ColumnSpan="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Height="400"
Width="500"
Margin="0, 30, 0, 0">
<ListView.View>
<GridView>
<GridViewColumn Width="150" Header="Name" />
<GridViewColumn Width="350" Header="Path" />
</GridView>
</ListView.View>
</ListView>
But every row and column gets filled with "Project.Views.Row".
Anyone got any idea on how to fix it? Thank you very much.
A ListView (and every other control for that matter) will display the results of calling ToString when given an object to display.
For a standard class, thats its qualified name; Project.Views.Row in your example.
There are two ways to fix this:
Don't add an object. Instead, format the string as you want it ie:
list1.Items.Add(String.Format({0}:{1}, row.Name1, row.Name2));
Do this the right way and use MVVM. In this case your XAML needs a data template:
<ListView ItemsSource="{Binding Rows}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name1}"/>
<TextBlock Text="{Binding Name2}"/>
</StackPanel>
</DataTemplate>
<ListView.ItemTemplate>
</ListView>
For a grid view:
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name1}" />
<GridViewColumn Header="Path" DisplayMemberBinding="{Binding Name2}"/>
</GridView>
</ListView.View>
Binding the ItemsSource is not actually necessary, but since we are doing everything the right way, you should do it so you are not directly manipulating the UI from code.
I have a listview bound to an Observable Collection 'People', People contains persons with the property 'Name', which gives me a table of people.
I want to have a delete button for each row of this table, appearing the last column. Below is a working implementation. However the handler and the XAML are very intertwined, which isn't good. How could I pass which member of People I've deleted?
XAML:
<ListView ItemsSource="{Binding People}" Name="ListViewPeople">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumnHeader>Names</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Names}"></TextBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Content="Delete" Click="Button_Click_Delete"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
I've managed to implement this with this handler, but as you can see it means the backing code has to know in detail about the view. This gets the index of the delete button pressed from the ListView and goes and removes the value from my observable collection.
c# code:
private void Button_Click_Delete(object sender, RoutedEventArgs e)
{
DependencyObject dep = (DependencyObject)e.OriginalSource;
while ((dep != null) && !(dep is ListViewItem))
{
dep = VisualTreeHelper.GetParent(dep);
}
if (dep == null)
return;
int index = ListViewPeople.ItemContainerGenerator.IndexFromContainer(dep);
People.RemoveAt(index);
}
Is there a way to perhaps pass which member of the observable collection I've deleted to my handler, so that the handler doesn't have to know about there being a listview etc?
Button btn = (Button)sender;
People.Remove((Person)btn.DataContext);
I am working with a WPF control that I created and I am trying to only show certain rows of my list by values of a property. An example is the following, I have a User class that holds a property of Active. How do I tell the .xaml that the list should only show the people that are Active?
Right now I am basically using linq to generate a new list and hand it to the listview based on what I want. However, I would rather just hand the ListView my entire list and let it do the work for me.
Here is my ListView code.
<ListView ItemsSource="{Binding}" DataContext="{Binding }" >
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Index}"/>
<TextBlock Text=". " />
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
You'll need some code behind to add a filter:
See: WPF filtering
ICollectionView view = CollectionViewSource.GetDefaultView(lstMovies.ItemsSource);
view.Filter = null;
view.Filter = new Predicate<object>(FilterMovieItem);
private bool FilterMovieItem(object obj)
{
MovieItem item = obj as MovieItem;
if (item == null) return false;
string textFilter = txtFilter.Text;
if (textFilter.Trim().Length == 0) return true; // the filter is empty - pass all items
// apply the filter
if (item.MovieName.ToLower().Contains(textFilter.ToLower())) return true;
return false;
}