开发者

Coordinates of specific ListViewItem

开发者 https://www.devze.com 2023-03-30 14:44 出处:网络
This is going to drive me mad. I have a WPF ListView and I try to overlay a TextBox over a double-clicked cell to emulate an Editor. This is working fine so far. I call ListView.InputHitTest(e.Positio

This is going to drive me mad. I have a WPF ListView and I try to overlay a TextBox over a double-clicked cell to emulate an Editor. This is working fine so far. I call ListView.InputHitTest(e.Position) to get the clicked item. From there I use VisualTreeHelper.GetParent() to navigate through the VisualTree and to resolve the coordinates. Fine.

Now I want, that if the user presses the cursor down, that this TextBox moves to the next Item. I tried the following attempts:

ListView.ItemContainerGenerator.ContainerFromIndex(index + 1)
ListView.ItemContainerGenerator.ContainerFromItem(…)  

But both methods return null (Nothing in VB), but ListView.ItemContainerGenerator.Status returns ContainersGenerated. How can I get the coordinates of a specific ListViewItem?

By the way: I’m limited to Framework 3.5, therefore the Grid in 4.0 is not available. Additionally I do not want to use 3rd party tools.

Edit

Here is some (experimental) code I use:

I use a surrounding Canvas to easily position the TextBox over the ListView. I included the DataTemplate of the first column. The rest is quite similar, so I removed this code. In addition I remove some style definitions and converters - I don't think that they are of any interest here.

<Canvas x:Name="Canvas">
    <ListView ItemsSource="{Binding MyListViewList}"
              Name="ListView"
              MouseDoubleClick="ListView_MouseDoubleClick">
        <ListView.Resources>
            <DataTemplate x:Key="NameTemplate">
                <Border Name="NameBorder">
                    <!-- Some content for this cell here -->
                </Border>
            </DataTemplate>
        </ListView.Resources>
        <ListView.ItemContainerStyle>
            <Style TargetType="ListViewItem">
                <Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
                <Setter Property="VerticalContentAlignment" Value="Stretch"></Setter>
                <Setter Property="IsSelected" Value="{Binding IsSelected}"></Setter>
            </Style>
        </ListView.ItemContainerStyle>
        <ListView.View>
            <GridView>
                <GridView.Columns>
                    <GridViewColumn Header="Name" x:Name="colName" 
                                CellTemplate="{StaticResource NameTemplate}"></GridViewColumn>
                </GridView.Columns>
            </GridView>
        </ListView.View>
    </ListView>
    <TextBox Name="EditorTextBox"
             Visibility="Hidden"
             BorderThickness="0">
    </TextBox>
</Canvas>

I register PreviewKeyDown to catch the keypress on Cursor-Down:

  Private Sub TextBox_PreviewKeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
    Dim model As MyListViewModel

    model = CType(DataContext, MyListViewModel)

    Select Case e.Key
      Case Key.Down
        Dim lvp As MyListViewParameter

        If Me.editorSelectedItemIndex + 1 < model.MyListViewList.Count Then

          lvp = model.MyListViewList(Me.editorSelectedItemIndex + 1)

          ' How to position EditorTextBox to the next ListViewItem here?
          ' The following methods return null/Nothing:
          ' ListView.ItemContainerGenerator.ContainerFromIndex(editorSelectedItemIndex + 1)
          ' ListView.ItemContainerGenerator.ContainerFromItem(lvp)
        End If
        e.Handled = True
      Case Key.Up

    End Select
  End Sub

So I have the position in code (PreviewKeyDown-Event) when I need to re-arrange the TextBox, but inside this routine, I can't get a result of the methods

ListView.ItemContainerGenerator.ContainerFromIndex(index + 1) and

ListView.ItemContainerGenerator.ContainerFromItem(开发者_运维问答…)

Further tests:

I wrote a class that inherits ListView and caches the ListViewItems when they are created in simple list of ListViewItem

Public Class ExtListView
  Inherits ListView

  Public Sub New()
    Dim ic = TryCast(Me.Items, System.Collections.Specialized.INotifyCollectionChanged)
    If Not ic Is Nothing Then
      AddHandler ic.CollectionChanged, AddressOf NotifyCollectionChangedEventHandler
    End If
    AddHandler Me.ItemContainerGenerator.StatusChanged, AddressOf ItemContainerGenerator_ItemsChanged

    _ListViewItems = New List(Of ListViewItem)
  End Sub

  Private _listViewItems As List(Of ListViewItem)
  Public ReadOnly Property ListViewItems As List(Of ListViewItem)
    Get
      Return _listViewItems
    End Get
  End Property

  Private Sub ItemContainerGenerator_ItemsChanged(ByVal sender As Object, ByVal e As EventArgs)
    If Me.ItemContainerGenerator.Status = Primitives.GeneratorStatus.ContainersGenerated Then
      _listViewItems.Clear()
      For index = 0 To Me.Items.Count - 1
        _listViewItems.Add(CType(ItemContainerGenerator.ContainerFromIndex(index), ListViewItem))
      Next
    End If
  End Sub
End Class

With this ListViewItems available, I tried to get the bounds of them:

Dim bounds As Rect = VisualTreeHelper.GetDescendantBounds(ListView.ListViewItems(editorSelectedItemIndex + 1))

But, however, all rectangles start at x,y-position (0, 0). I have no clue why. I expected them to be in the visual tree at a valid position. Any suggestions to solve this?


Don't do that.

Okay, if you really want to, do that. But there's a much, much easier way.

Add a boolean IsEditing and a string EditableContent property to your view model. Create a data template that looks something like this:

<DataTemplate>
   <Grid>
      <Border>
         <!-- normal cell content goes here -->
      </Border>
      <TextBox Text="{Binding EditableContent, Mode=TwoWay}">
         <TextBox.Style>
            <Style TargetType="TextBox">
               <Setter Property="Visibility" Value="Hidden"/>
               <Style.Triggers>
                  <DataTrigger Binding="{Binding IsEditing}" Value="True">
                     <Setter Property="Visibility" Value="Visible"/>
                  </DataTrigger>
               </Style.Triggers>
            </Style>
      </TextBox>
   <Grid>
</DataTemplate>

When the IsEditing property is true, the TextBox will be displayed and the user can edit whatever's in the EditableContent property. When it's false, the ordinary cell content will be displayed.

I don't know what you're sticking inside that border, but when I do this to alternate between a TextBlock and TextBox it looks identical to (say) what renaming files looks like in Windows Explorer, which I assume is what you're after.

You need to handle keypress and lost-focus events to implement the other pieces of edit-in-place functionality, but those are relatively straightforward problems once you've got the layout working.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号