using System.Windows.Controls; namespace System.Windows.Forms { public partial class ListView : Control { private bool mouseMoved; private bool doubleClicked; private ListViewItem selectedItemAtMouseDown; private ListViewItem editedItem; protected new CustomControls.ListView control { get; } internal System.Windows.Controls.GridView GridView { get; } public ListViewItemCollection Items { get; } public ListViewGroupCollection Groups { get; } public ColumnHeaderCollection Columns { get; } public SelectedListViewItemCollection SelectedItems { get; } public SelectedIndexCollection SelectedIndices { get; } private CheckedListViewItemCollection checkedItems; public CheckedListViewItemCollection CheckedItems { get { if (checkedItems == null) BuildCheckedItems(); return checkedItems; } } private CheckedIndexCollection checkedIndices; public CheckedIndexCollection CheckedIndices { get { if (checkedIndices == null) BuildCheckedIndices(); return checkedIndices; } } private View view; public View View { get => view; set { view = value; if (view == View.Details) control.View = GridView; else control.View = null; } } private ImageList imageList; public ImageList LargeImageList { get => imageList; set { imageList = value; ResetImageList(); } } public ImageList SmallImageList { get => LargeImageList; set => LargeImageList = value; } public bool HideSelection { get; set; } // TODO? public bool LabelEdit { get; set; } public bool UseCompatibleStateImageBehavior { get; set; } // ignored public bool ShowGroups { get; set; } // TODO? public bool CheckBoxes { get; set; } // TODO? public bool FullRowSelect { get; set; } // TODO? public ItemActivation Activation { get; set; } // TODO? public ListViewAlignment Alignment { get; set; } // TODO? private ColumnHeaderStyle headerStyle; public ColumnHeaderStyle HeaderStyle { get => headerStyle; set { headerStyle = value; if (value == ColumnHeaderStyle.None) { this.GridView.ColumnHeaderContainerStyle = (System.Windows.Style)control.Resources["HiddenHeader"]; } } } public bool MultiSelect { get => control.SelectionMode == Windows.Controls.SelectionMode.Extended; set => control.SelectionMode = value ? Windows.Controls.SelectionMode.Extended : Windows.Controls.SelectionMode.Single; } public new event MouseEventHandler MouseClick; public new event MouseEventHandler MouseDoubleClick; public new event EventHandler DoubleClick; public event LabelEditEventHandler AfterLabelEdit; public event LabelEditEventHandler BeforeLabelEdit; public event EventHandler SelectedIndexChanged; public event ItemDragEventHandler ItemDrag; public event ItemCheckedEventHandler ItemChecked; // TODO? public event ColumnClickEventHandler ColumnClick; private ListViewItem ItemHitTest(Point point, bool textOnly = false) { var item = control.InputHitTest(point); if (!textOnly && FullRowSelect && item != null && item is FrameworkElement element && element.DataContext is ListViewItem itm) return itm; if (item is TextBlock txt && txt.Name == "PART_TextBlock") return txt.DataContext as ListViewItem; if (!textOnly && item is Image img && img.Name == "PART_Image") return img.DataContext as ListViewItem; return null; } private void ResetImageList() { foreach (var item in Items) item.ResetImageSource(); } private void BuildCheckedItems() { // TODO? checkedItems = new CheckedListViewItemCollection(); } private void BuildCheckedIndices() { // TODO? checkedIndices = new CheckedIndexCollection(); } private void ListView_OnColumnClick(object sender, RoutedEventArgs e) { var column = e.OriginalSource as GridViewColumnHeader; if (column != null) { int index = GridView.Columns.IndexOf(column.Column); if (index != -1) OnColumnClick(new ColumnClickEventArgs(index)); } } protected new virtual void OnDoubleClick(EventArgs e) => DoubleClick?.Invoke(this, e); protected new virtual void OnMouseDoubleClick(MouseEventArgs e) => MouseDoubleClick?.Invoke(this, e); protected new virtual void OnMouseClick(MouseEventArgs e) => MouseClick?.Invoke(this, e); protected virtual void OnAfterLabelEdit(LabelEditEventArgs e) => AfterLabelEdit?.Invoke(this, e); protected virtual void OnBeforeLabelEdit(LabelEditEventArgs e) => BeforeLabelEdit?.Invoke(this, e); protected virtual void OnSelectionChanged(EventArgs e) { OnSelectedIndexChanged(e); } protected virtual void OnSelectedIndexChanged(EventArgs e) => SelectedIndexChanged?.Invoke(this, e); protected virtual void OnItemDrag(ItemDragEventArgs e) => ItemDrag?.Invoke(this, e); protected virtual void OnItemChecked(ItemCheckedEventArgs e) => ItemChecked?.Invoke(this, e); protected virtual void OnColumnClick(ColumnClickEventArgs e) => ColumnClick?.Invoke(this, e); internal void DoItemSelect(ListViewItem item) { // do not rely on control.SelectedItems because of strange behavior (sometimes it can return duplicate items) // instead, use own selected items list and keep it in sync with ListView.IsSelected and also with Items list (item remove/clear) if (item.Selected) { SelectedItems.InternalAdd(item); } else { SelectedItems.InternalRemove(item); } OnSelectionChanged(EventArgs.Empty); } internal void DoItemRemove(ListViewItem item) { if (SelectedItems.InternalRemove(item)) { OnSelectionChanged(EventArgs.Empty); } } internal void DoItemsClear() { SelectedItems.InternalClear(); } internal void DoOnAfterLabelEdit(LabelEditEventArgs e) => OnAfterLabelEdit(e); internal void DoOnBeforeLabelEdit(LabelEditEventArgs e) => OnBeforeLabelEdit(e); internal void SetEditedItem(ListViewItem item) => editedItem = item; internal void AcceptEdit() => editedItem?.EndEdit(false); internal void CancelEdit() => editedItem?.EndEdit(true); public void BeginUpdate() { } public void EndUpdate() { } public ListViewItem GetItemAt(int x, int y) => ItemHitTest(new Point(x / DpiScale, y / DpiScale)); public ListViewItem GetItemAt(Drawing.Point point) => GetItemAt(point.X, point.Y); public ListView() { control = new(); SetControl(control); Items = new(this); Groups = new(this); SelectedItems = new(this); SelectedIndices = new(this); Columns = new(this); GridView = new(); control.AcceptEdit += (sender, e) => AcceptEdit(); control.CancelEdit += (sender, e) => CancelEdit(); // we use our own SelectionChanged based on ListViewItem.IsSelected property // (due to async nature of control.SelectionChanged which is inconsistent with SWF) //control.SelectionChanged += (sender, e) => OnSelectionChanged(e); control.PreviewMouseDown += (sender, e) => { mouseMoved = false; selectedItemAtMouseDown = SelectedItems.Count == 1 ? SelectedItems[0] : null; if (e.ClickCount == 2) doubleClicked = true; }; control.MouseMove += (sender, e) => { if (e.LeftButton == Input.MouseButtonState.Pressed) { var item = ItemHitTest(e.GetPosition(control)); if (item != null) OnItemDrag(new ItemDragEventArgs(MouseButtons.Left, item)); mouseMoved = true; } }; control.PreviewMouseUp += (sender, e) => { var args = Helper.GetMouseEventArgs(control, e); var pos = e.GetPosition(control); var item = ItemHitTest(pos); if (doubleClicked) { doubleClicked = false; if (item != null) { OnDoubleClick(e); OnMouseDoubleClick(args); } } else { if (!mouseMoved && item != null) { OnMouseClick(args); if (e.ChangedButton == Input.MouseButton.Left && selectedItemAtMouseDown == item && LabelEdit && ItemHitTest(pos, true) == item) item.BeginEdit(); } } }; control.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ListView_OnColumnClick)); control.FocusVisualStyle = null; control.ItemsSource = Items; } } }