/* * Events - All the events that can be triggered within an ObjectListView. * * Author: Phillip Piper * Date: 17/10/2008 9:15 PM * * Change log: * v2.8.0 * 2014-05-20 JPP - Added IsHyperlinkEventArgs.IsHyperlink * v2.6 * 2012-04-17 JPP - Added group state change and group expansion events * v2.5 * 2010-08-08 JPP - CellEdit validation and finish events now have NewValue property. * v2.4 * 2010-03-04 JPP - Added filtering events * v2.3 * 2009-08-16 JPP - Added group events * 2009-08-08 JPP - Added HotItem event * 2009-07-24 JPP - Added Hyperlink events * - Added Formatting events * v2.2.1 * 2009-06-13 JPP - Added Cell events * - Moved all event parameter blocks to this file. * - Added Handled property to AfterSearchEventArgs * v2.2 * 2009-06-01 JPP - Added ColumnToGroupBy and GroupByOrder to sorting events - Gave all event descriptions * 2009-04-23 JPP - Added drag drop events * v2.1 * 2009-01-18 JPP - Moved SelectionChanged event to this file * v2.0 * 2008-12-06 JPP - Added searching events * 2008-12-01 JPP - Added secondary sort information to Before/AfterSorting events * 2008-10-17 JPP - Separated from ObjectListView.cs * * Copyright (C) 2006-2014 Phillip Piper * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com. */ using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; namespace BrightIdeasSoftware { /// /// The callbacks for CellEditing events /// /// this /// We could replace this with EventHandler<CellEditEventArgs> but that would break all /// cell editing event code from v1.x. /// public delegate void CellEditEventHandler(object sender, CellEditEventArgs e); public partial class ObjectListView { //----------------------------------------------------------------------------------- #region Events /// /// Triggered after a ObjectListView has been searched by the user typing into the list /// [Category("ObjectListView"), Description("This event is triggered after the control has done a search-by-typing action.")] public event EventHandler AfterSearching; /// /// Triggered after a ObjectListView has been sorted /// [Category("ObjectListView"), Description("This event is triggered after the items in the list have been sorted.")] public event EventHandler AfterSorting; /// /// Triggered before a ObjectListView is searched by the user typing into the list /// /// /// Set Cancelled to true to prevent the searching from taking place. /// Changing StringToFind or StartSearchFrom will change the subsequent search. /// [Category("ObjectListView"), Description("This event is triggered before the control does a search-by-typing action.")] public event EventHandler BeforeSearching; /// /// Triggered before a ObjectListView is sorted /// /// /// Set Cancelled to true to prevent the sort from taking place. /// Changing ColumnToSort or SortOrder will change the subsequent sort. /// [Category("ObjectListView"), Description("This event is triggered before the items in the list are sorted.")] public event EventHandler BeforeSorting; /// /// Triggered after a ObjectListView has created groups /// [Category("ObjectListView"), Description("This event is triggered after the groups are created.")] public event EventHandler AfterCreatingGroups; /// /// Triggered before a ObjectListView begins to create groups /// /// /// Set Groups to prevent the default group creation process /// [Category("ObjectListView"), Description("This event is triggered before the groups are created.")] public event EventHandler BeforeCreatingGroups; /// /// Triggered just before a ObjectListView creates groups /// /// /// You can make changes to the groups, which have been created, before those /// groups are created within the listview. /// [Category("ObjectListView"), Description("This event is triggered when the groups are just about to be created.")] public event EventHandler AboutToCreateGroups; /// /// This event is triggered when the user moves a drag over an ObjectListView that /// has a SimpleDropSink installed as the drop handler. /// /// /// Handlers for this event should set the Effect argument and optionally the /// InfoMsg property. They can also change any of the DropTarget* setttings to change /// the target of the drop. /// [Category("ObjectListView"), Description("Can the user drop the currently dragged items at the current mouse location?")] public event EventHandler CanDrop; /// /// Triggered when a cell is about to finish being edited. /// /// If Cancel is already true, the user is cancelling the edit operation. /// Set Cancel to true to prevent the value from the cell being written into the model. /// You cannot prevent the editing from finishing within this event -- you need /// the CellEditValidating event for that. [Category("ObjectListView"), Description("This event is triggered cell edit operation is finishing.")] public event CellEditEventHandler CellEditFinishing; /// /// Triggered when a cell is about to be edited. /// /// Set Cancel to true to prevent the cell being edited. /// You can change the the Control to be something completely different. [Category("ObjectListView"), Description("This event is triggered when cell edit is about to begin.")] public event CellEditEventHandler CellEditStarting; /// /// Triggered when a cell editor needs to be validated /// /// /// If this event is cancelled, focus will remain on the cell editor. /// [Category("ObjectListView"), Description("This event is triggered when a cell editor is about to lose focus and its new contents need to be validated.")] public event CellEditEventHandler CellEditValidating; /// /// Triggered when a cell is left clicked. /// [Category("ObjectListView"), Description("This event is triggered when the user left clicks a cell.")] public event EventHandler CellClick; /// /// Triggered when the mouse is above a cell. /// [Category("ObjectListView"), Description("This event is triggered when the mouse is over a cell.")] public event EventHandler CellOver; /// /// Triggered when a cell is right clicked. /// [Category("ObjectListView"), Description("This event is triggered when the user right clicks a cell.")] public event EventHandler CellRightClick; /// /// This event is triggered when a cell needs a tool tip. /// [Category("ObjectListView"), Description("This event is triggered when a cell needs a tool tip.")] public event EventHandler CellToolTipShowing; /// /// This event is triggered when a checkbox is checked/unchecked on a subitem /// [Category("ObjectListView"), Description("This event is triggered when a checkbox is checked/unchecked on a subitem.")] public event EventHandler SubItemChecking; /// /// Triggered when a column header is right clicked. /// [Category("ObjectListView"), Description("This event is triggered when the user right clicks a column header.")] public event ColumnRightClickEventHandler ColumnRightClick; /// /// This event is triggered when the user releases a drag over an ObjectListView that /// has a SimpleDropSink installed as the drop handler. /// [Category("ObjectListView"), Description("This event is triggered when the user dropped items onto the control.")] public event EventHandler Dropped; /// /// This event is triggered when the control needs to filter its collection of objects. /// [Category("ObjectListView"), Description("This event is triggered when the control needs to filter its collection of objects.")] public event EventHandler Filter; /// /// This event is triggered when a cell needs to be formatted. /// [Category("ObjectListView"), Description("This event is triggered when a cell needs to be formatted.")] public event EventHandler FormatCell; /// /// This event is triggered when the frozeness of the control changes. /// [Category("ObjectListView"), Description("This event is triggered when frozeness of the control changes.")] public event EventHandler Freezing; /// /// This event is triggered when a row needs to be formatted. /// [Category("ObjectListView"), Description("This event is triggered when a row needs to be formatted.")] public event EventHandler FormatRow; /// /// This event is triggered when a group is about to collapse or expand. /// This can be cancelled to prevent the expansion. /// [Category("ObjectListView"), Description("This event is triggered when a group is about to collapse or expand.")] public event EventHandler GroupExpandingCollapsing; /// /// This event is triggered when a group changes state. /// [Category("ObjectListView"), Description("This event is triggered when a group changes state.")] public event EventHandler GroupStateChanged; /// /// This event is triggered when a header checkbox is changing value /// [Category("ObjectListView"), Description("This event is triggered when a header checkbox changes value.")] public event EventHandler HeaderCheckBoxChanging; /// /// This event is triggered when a header needs a tool tip. /// [Category("ObjectListView"), Description("This event is triggered when a header needs a tool tip.")] public event EventHandler HeaderToolTipShowing; /// /// Triggered when the "hot" item changes /// [Category("ObjectListView"), Description("This event is triggered when the hot item changed.")] public event EventHandler HotItemChanged; /// /// Triggered when a hyperlink cell is clicked. /// [Category("ObjectListView"), Description("This event is triggered when a hyperlink cell is clicked.")] public event EventHandler HyperlinkClicked; /// /// Triggered when the task text of a group is clicked. /// [Category("ObjectListView"), Description("This event is triggered when the task text of a group is clicked.")] public event EventHandler GroupTaskClicked; /// /// Is the value in the given cell a hyperlink. /// [Category("ObjectListView"), Description("This event is triggered when the control needs to know if a given cell contains a hyperlink.")] public event EventHandler IsHyperlink; /// /// Some new objects are about to be added to an ObjectListView. /// [Category("ObjectListView"), Description("This event is triggered when objects are about to be added to the control")] public event EventHandler ItemsAdding; /// /// The contents of the ObjectListView has changed. /// [Category("ObjectListView"), Description("This event is triggered when the contents of the control have changed.")] public event EventHandler ItemsChanged; /// /// The contents of the ObjectListView is about to change via a SetObjects call /// /// /// Set Cancelled to true to prevent the contents of the list changing. This does not work with virtual lists. /// [Category("ObjectListView"), Description("This event is triggered when the contents of the control changes.")] public event EventHandler ItemsChanging; /// /// Some objects are about to be removed from an ObjectListView. /// [Category("ObjectListView"), Description("This event is triggered when objects are removed from the control.")] public event EventHandler ItemsRemoving; /// /// This event is triggered when the user moves a drag over an ObjectListView that /// has a SimpleDropSink installed as the drop handler, and when the source control /// for the drag was an ObjectListView. /// /// /// Handlers for this event should set the Effect argument and optionally the /// InfoMsg property. They can also change any of the DropTarget* setttings to change /// the target of the drop. /// [Category("ObjectListView"), Description("Can the dragged collection of model objects be dropped at the current mouse location")] public event EventHandler ModelCanDrop; /// /// This event is triggered when the user releases a drag over an ObjectListView that /// has a SimpleDropSink installed as the drop handler and when the source control /// for the drag was an ObjectListView. /// [Category("ObjectListView"), Description("A collection of model objects from a ObjectListView has been dropped on this control")] public event EventHandler ModelDropped; /// /// This event is triggered once per user action that changes the selection state /// of one or more rows. /// [Category("ObjectListView"), Description("This event is triggered once per user action that changes the selection state of one or more rows.")] public event EventHandler SelectionChanged; /// /// This event is triggered when the contents of the ObjectListView has scrolled. /// [Category("ObjectListView"), Description("This event is triggered when the contents of the ObjectListView has scrolled.")] public event EventHandler Scroll; #endregion //----------------------------------------------------------------------------------- #region OnEvents /// /// /// /// protected virtual void OnAboutToCreateGroups(CreateGroupsEventArgs e) { if (this.AboutToCreateGroups != null) this.AboutToCreateGroups(this, e); } /// /// /// /// protected virtual void OnBeforeCreatingGroups(CreateGroupsEventArgs e) { if (this.BeforeCreatingGroups != null) this.BeforeCreatingGroups(this, e); } /// /// /// /// protected virtual void OnAfterCreatingGroups(CreateGroupsEventArgs e) { if (this.AfterCreatingGroups != null) this.AfterCreatingGroups(this, e); } /// /// /// /// protected virtual void OnAfterSearching(AfterSearchingEventArgs e) { if (this.AfterSearching != null) this.AfterSearching(this, e); } /// /// /// /// protected virtual void OnAfterSorting(AfterSortingEventArgs e) { if (this.AfterSorting != null) this.AfterSorting(this, e); } /// /// /// /// protected virtual void OnBeforeSearching(BeforeSearchingEventArgs e) { if (this.BeforeSearching != null) this.BeforeSearching(this, e); } /// /// /// /// protected virtual void OnBeforeSorting(BeforeSortingEventArgs e) { if (this.BeforeSorting != null) this.BeforeSorting(this, e); } /// /// /// /// protected virtual void OnCanDrop(OlvDropEventArgs args) { if (this.CanDrop != null) this.CanDrop(this, args); } /// /// /// /// protected virtual void OnCellClick(CellClickEventArgs args) { if (this.CellClick != null) this.CellClick(this, args); } /// /// /// /// protected virtual void OnCellOver(CellOverEventArgs args) { if (this.CellOver != null) this.CellOver(this, args); } /// /// /// /// protected virtual void OnCellRightClick(CellRightClickEventArgs args) { if (this.CellRightClick != null) this.CellRightClick(this, args); } /// /// /// /// protected virtual void OnCellToolTip(ToolTipShowingEventArgs args) { if (this.CellToolTipShowing != null) this.CellToolTipShowing(this, args); } /// /// /// /// protected virtual void OnSubItemChecking(SubItemCheckingEventArgs args) { if (this.SubItemChecking != null) this.SubItemChecking(this, args); } /// /// /// /// protected virtual void OnColumnRightClick(ColumnClickEventArgs e) { if (this.ColumnRightClick != null) this.ColumnRightClick(this, e); } /// /// /// /// protected virtual void OnDropped(OlvDropEventArgs args) { if (this.Dropped != null) this.Dropped(this, args); } /// /// /// /// protected virtual void OnFilter(FilterEventArgs e) { if (this.Filter != null) this.Filter(this, e); } /// /// /// /// protected virtual void OnFormatCell(FormatCellEventArgs args) { if (this.FormatCell != null) this.FormatCell(this, args); } /// /// /// /// protected virtual void OnFormatRow(FormatRowEventArgs args) { if (this.FormatRow != null) this.FormatRow(this, args); } /// /// /// /// protected virtual void OnFreezing(FreezeEventArgs args) { if (this.Freezing != null) this.Freezing(this, args); } /// /// /// /// protected virtual void OnGroupExpandingCollapsing(GroupExpandingCollapsingEventArgs args) { if (this.GroupExpandingCollapsing != null) this.GroupExpandingCollapsing(this, args); } /// /// /// /// protected virtual void OnGroupStateChanged(GroupStateChangedEventArgs args) { if (this.GroupStateChanged != null) this.GroupStateChanged(this, args); } /// /// /// /// protected virtual void OnHeaderCheckBoxChanging(HeaderCheckBoxChangingEventArgs args) { if (this.HeaderCheckBoxChanging != null) this.HeaderCheckBoxChanging(this, args); } /// /// /// /// protected virtual void OnHeaderToolTip(ToolTipShowingEventArgs args) { if (this.HeaderToolTipShowing != null) this.HeaderToolTipShowing(this, args); } /// /// /// /// protected virtual void OnHotItemChanged(HotItemChangedEventArgs e) { if (this.HotItemChanged != null) this.HotItemChanged(this, e); } /// /// /// /// protected virtual void OnHyperlinkClicked(HyperlinkClickedEventArgs e) { if (this.HyperlinkClicked != null) this.HyperlinkClicked(this, e); } /// /// /// /// protected virtual void OnGroupTaskClicked(GroupTaskClickedEventArgs e) { if (this.GroupTaskClicked != null) this.GroupTaskClicked(this, e); } /// /// /// /// protected virtual void OnIsHyperlink(IsHyperlinkEventArgs e) { if (this.IsHyperlink != null) this.IsHyperlink(this, e); } /// /// /// /// protected virtual void OnItemsAdding(ItemsAddingEventArgs e) { if (this.ItemsAdding != null) this.ItemsAdding(this, e); } /// /// /// /// protected virtual void OnItemsChanged(ItemsChangedEventArgs e) { if (this.ItemsChanged != null) this.ItemsChanged(this, e); } /// /// /// /// protected virtual void OnItemsChanging(ItemsChangingEventArgs e) { if (this.ItemsChanging != null) this.ItemsChanging(this, e); } /// /// /// /// protected virtual void OnItemsRemoving(ItemsRemovingEventArgs e) { if (this.ItemsRemoving != null) this.ItemsRemoving(this, e); } /// /// /// /// protected virtual void OnModelCanDrop(ModelDropEventArgs args) { if (this.ModelCanDrop != null) this.ModelCanDrop(this, args); } /// /// /// /// protected virtual void OnModelDropped(ModelDropEventArgs args) { if (this.ModelDropped != null) this.ModelDropped(this, args); } /// /// /// /// protected virtual void OnSelectionChanged(EventArgs e) { if (this.SelectionChanged != null) this.SelectionChanged(this, e); } /// /// /// /// protected virtual void OnScroll(ScrollEventArgs e) { if (this.Scroll != null) this.Scroll(this, e); } /// /// Tell the world when a cell is about to be edited. /// protected virtual void OnCellEditStarting(CellEditEventArgs e) { if (this.CellEditStarting != null) this.CellEditStarting(this, e); } /// /// Tell the world when a cell is about to finish being edited. /// protected virtual void OnCellEditorValidating(CellEditEventArgs e) { // Hack. ListView is an imperfect control container. It does not manage validation // perfectly. If the ListView is part of a TabControl, and the cell editor loses // focus by the user clicking on another tab, the TabControl processes the click // and switches tabs, even if this Validating event cancels. This results in the // strange situation where the cell editor is active, but isn't visible. When the // user switches back to the tab with the ListView, composite controls like spin // controls, DateTimePicker and ComboBoxes do not work properly. Specifically, // keyboard input still works fine, but the controls do not respond to mouse // input. SO, if the validation fails, we have to specifically give focus back to // the cell editor. (this is the Select() call in the code below). // But (there is always a 'but'), doing that changes the focus so the cell editor // triggers another Validating event -- which fails again. From the user's point // of view, they click away from the cell editor, and the validating code // complains twice. So we only trigger a Validating event if more than 0.1 seconds // has elapsed since the last validate event. // I know it's a hack. I'm very open to hear a neater solution. // Also, this timed response stops us from sending a series of validation events // if the user clicks and holds on the OLV scroll bar. //System.Diagnostics.Debug.WriteLine(Environment.TickCount - lastValidatingEvent); if ((Environment.TickCount - lastValidatingEvent) < 100) { e.Cancel = true; } else { lastValidatingEvent = Environment.TickCount; if (this.CellEditValidating != null) this.CellEditValidating(this, e); } lastValidatingEvent = Environment.TickCount; } private int lastValidatingEvent = 0; /// /// Tell the world when a cell is about to finish being edited. /// protected virtual void OnCellEditFinishing(CellEditEventArgs e) { if (this.CellEditFinishing != null) this.CellEditFinishing(this, e); } #endregion } public partial class TreeListView { #region Events /// /// This event is triggered when user input requests the expansion of a list item. /// [Category("ObjectListView"), Description("This event is triggered when a branch is about to expand.")] public event EventHandler Expanding; /// /// This event is triggered when user input requests the collapse of a list item. /// [Category("ObjectListView"), Description("This event is triggered when a branch is about to collapsed.")] public event EventHandler Collapsing; /// /// This event is triggered after the expansion of a list item due to user input. /// [Category("ObjectListView"), Description("This event is triggered when a branch has been expanded.")] public event EventHandler Expanded; /// /// This event is triggered after the collapse of a list item due to user input. /// [Category("ObjectListView"), Description("This event is triggered when a branch has been collapsed.")] public event EventHandler Collapsed; #endregion #region OnEvents /// /// Trigger the expanding event /// /// protected virtual void OnExpanding(TreeBranchExpandingEventArgs e) { if (this.Expanding != null) this.Expanding(this, e); } /// /// Trigger the collapsing event /// /// protected virtual void OnCollapsing(TreeBranchCollapsingEventArgs e) { if (this.Collapsing != null) this.Collapsing(this, e); } /// /// Trigger the expanded event /// /// protected virtual void OnExpanded(TreeBranchExpandedEventArgs e) { if (this.Expanded != null) this.Expanded(this, e); } /// /// Trigger the collapsed event /// /// protected virtual void OnCollapsed(TreeBranchCollapsedEventArgs e) { if (this.Collapsed != null) this.Collapsed(this, e); } #endregion } //----------------------------------------------------------------------------------- #region Event Parameter Blocks /// /// Let the world know that a cell edit operation is beginning or ending /// public class CellEditEventArgs : EventArgs { /// /// Create an event args /// /// /// /// /// /// public CellEditEventArgs(OLVColumn column, Control control, Rectangle r, OLVListItem item, int subItemIndex) { this.Control = control; this.column = column; this.cellBounds = r; this.listViewItem = item; this.rowObject = item.RowObject; this.subItemIndex = subItemIndex; this.value = column.GetValue(item.RowObject); } /// /// Change this to true to cancel the cell editing operation. /// /// /// During the CellEditStarting event, setting this to true will prevent the cell from being edited. /// During the CellEditFinishing event, if this value is already true, this indicates that the user has /// cancelled the edit operation and that the handler should perform cleanup only. Setting this to true, /// will prevent the ObjectListView from trying to write the new value into the model object. /// public bool Cancel; /// /// During the CellEditStarting event, this can be modified to be the control that you want /// to edit the value. You must fully configure the control before returning from the event, /// including its bounds and the value it is showing. /// During the CellEditFinishing event, you can use this to get the value that the user /// entered and commit that value to the model. Changing the control during the finishing /// event has no effect. /// public Control Control; /// /// The column of the cell that is going to be or has been edited. /// public OLVColumn Column { get { return this.column; } } private OLVColumn column; /// /// The model object of the row of the cell that is going to be or has been edited. /// public Object RowObject { get { return this.rowObject; } } private Object rowObject; /// /// The listview item of the cell that is going to be or has been edited. /// public OLVListItem ListViewItem { get { return this.listViewItem; } } private OLVListItem listViewItem; /// /// The data value of the cell as it stands in the control. /// /// Only validate during Validating and Finishing events. public Object NewValue { get { return this.newValue; } set { this.newValue = value; } } private Object newValue; /// /// The index of the cell that is going to be or has been edited. /// public int SubItemIndex { get { return this.subItemIndex; } } private int subItemIndex; /// /// The data value of the cell before the edit operation began. /// public Object Value { get { return this.value; } } private Object value; /// /// The bounds of the cell that is going to be or has been edited. /// public Rectangle CellBounds { get { return this.cellBounds; } } private Rectangle cellBounds; /// /// Gets or sets whether the control used for editing should be auto matically disposed /// when the cell edit operation finishes. Defaults to true /// /// If the control is expensive to create, you might want to cache it and reuse for /// for various cells. If so, you don't want ObjectListView to dispose of the control automatically public bool AutoDispose { get { return autoDispose; } set { autoDispose = value; } } private bool autoDispose = true; } /// /// Event blocks for events that can be cancelled /// public class CancellableEventArgs : EventArgs { /// /// Has this event been cancelled by the event handler? /// public bool Canceled; } /// /// BeforeSorting /// public class BeforeSortingEventArgs : CancellableEventArgs { /// /// Create BeforeSortingEventArgs /// /// /// /// /// public BeforeSortingEventArgs(OLVColumn column, SortOrder order, OLVColumn column2, SortOrder order2) { this.ColumnToGroupBy = column; this.GroupByOrder = order; this.ColumnToSort = column; this.SortOrder = order; this.SecondaryColumnToSort = column2; this.SecondarySortOrder = order2; } /// /// Create BeforeSortingEventArgs /// /// /// /// /// /// /// public BeforeSortingEventArgs(OLVColumn groupColumn, SortOrder groupOrder, OLVColumn column, SortOrder order, OLVColumn column2, SortOrder order2) { this.ColumnToGroupBy = groupColumn; this.GroupByOrder = groupOrder; this.ColumnToSort = column; this.SortOrder = order; this.SecondaryColumnToSort = column2; this.SecondarySortOrder = order2; } /// /// Did the event handler already do the sorting for us? /// public bool Handled; /// /// What column will be used for grouping /// public OLVColumn ColumnToGroupBy; /// /// How will groups be ordered /// public SortOrder GroupByOrder; /// /// What column will be used for sorting /// public OLVColumn ColumnToSort; /// /// What order will be used for sorting. None means no sorting. /// public SortOrder SortOrder; /// /// What column will be used for secondary sorting? /// public OLVColumn SecondaryColumnToSort; /// /// What order will be used for secondary sorting? /// public SortOrder SecondarySortOrder; } /// /// Sorting has just occurred. /// public class AfterSortingEventArgs : EventArgs { /// /// Create a AfterSortingEventArgs /// /// /// /// /// /// /// public AfterSortingEventArgs(OLVColumn groupColumn, SortOrder groupOrder, OLVColumn column, SortOrder order, OLVColumn column2, SortOrder order2) { this.columnToGroupBy = groupColumn; this.groupByOrder = groupOrder; this.columnToSort = column; this.sortOrder = order; this.secondaryColumnToSort = column2; this.secondarySortOrder = order2; } /// /// Create a AfterSortingEventArgs /// /// public AfterSortingEventArgs(BeforeSortingEventArgs args) { this.columnToGroupBy = args.ColumnToGroupBy; this.groupByOrder = args.GroupByOrder; this.columnToSort = args.ColumnToSort; this.sortOrder = args.SortOrder; this.secondaryColumnToSort = args.SecondaryColumnToSort; this.secondarySortOrder = args.SecondarySortOrder; } /// /// What column was used for grouping? /// public OLVColumn ColumnToGroupBy { get { return columnToGroupBy; } } private OLVColumn columnToGroupBy; /// /// What ordering was used for grouping? /// public SortOrder GroupByOrder { get { return groupByOrder; } } private SortOrder groupByOrder; /// /// What column was used for sorting? /// public OLVColumn ColumnToSort { get { return columnToSort; } } private OLVColumn columnToSort; /// /// What ordering was used for sorting? /// public SortOrder SortOrder { get { return sortOrder; } } private SortOrder sortOrder; /// /// What column was used for secondary sorting? /// public OLVColumn SecondaryColumnToSort { get { return secondaryColumnToSort; } } private OLVColumn secondaryColumnToSort; /// /// What order was used for secondary sorting? /// public SortOrder SecondarySortOrder { get { return secondarySortOrder; } } private SortOrder secondarySortOrder; } /// /// This event is triggered when the contents of a list have changed /// and we want the world to have a chance to filter the list. /// public class FilterEventArgs : EventArgs { /// /// Create a FilterEventArgs /// /// public FilterEventArgs(IEnumerable objects) { this.Objects = objects; } /// /// Gets or sets what objects are being filtered /// public IEnumerable Objects; /// /// Gets or sets what objects survived the filtering /// public IEnumerable FilteredObjects; } /// /// This event is triggered after the items in the list have been changed, /// either through SetObjects, AddObjects or RemoveObjects. /// public class ItemsChangedEventArgs : EventArgs { /// /// Create a ItemsChangedEventArgs /// public ItemsChangedEventArgs() { } /// /// Constructor for this event when used by a virtual list /// /// /// public ItemsChangedEventArgs(int oldObjectCount, int newObjectCount) { this.oldObjectCount = oldObjectCount; this.newObjectCount = newObjectCount; } /// /// Gets how many items were in the list before it changed /// public int OldObjectCount { get { return oldObjectCount; } } private int oldObjectCount; /// /// Gets how many objects are in the list after the change. /// public int NewObjectCount { get { return newObjectCount; } } private int newObjectCount; } /// /// This event is triggered by AddObjects before any change has been made to the list. /// public class ItemsAddingEventArgs : CancellableEventArgs { /// /// Create an ItemsAddingEventArgs /// /// public ItemsAddingEventArgs(ICollection objectsToAdd) { this.ObjectsToAdd = objectsToAdd; } /// /// Gets or sets the objects to be added to the list /// public ICollection ObjectsToAdd; } /// /// This event is triggered by SetObjects before any change has been made to the list. /// /// /// When used with a virtual list, OldObjects will always be null. /// public class ItemsChangingEventArgs : CancellableEventArgs { /// /// Create ItemsChangingEventArgs /// /// /// public ItemsChangingEventArgs(IEnumerable oldObjects, IEnumerable newObjects) { this.oldObjects = oldObjects; this.NewObjects = newObjects; } /// /// Gets the objects that were in the list before it change. /// For virtual lists, this will always be null. /// public IEnumerable OldObjects { get { return oldObjects; } } private IEnumerable oldObjects; /// /// Gets or sets the objects that will be in the list after it changes. /// public IEnumerable NewObjects; } /// /// This event is triggered by RemoveObjects before any change has been made to the list. /// public class ItemsRemovingEventArgs : CancellableEventArgs { /// /// Create an ItemsRemovingEventArgs /// /// public ItemsRemovingEventArgs(ICollection objectsToRemove) { this.ObjectsToRemove = objectsToRemove; } /// /// Gets or sets the objects that will be removed /// public ICollection ObjectsToRemove; } /// /// Triggered after the user types into a list /// public class AfterSearchingEventArgs : EventArgs { /// /// Create an AfterSearchingEventArgs /// /// /// public AfterSearchingEventArgs(string stringToFind, int indexSelected) { this.stringToFind = stringToFind; this.indexSelected = indexSelected; } /// /// Gets the string that was actually searched for /// public string StringToFind { get { return this.stringToFind; } } private string stringToFind; /// /// Gets or sets whether an the event handler already handled this event /// public bool Handled; /// /// Gets the index of the row that was selected by the search. /// -1 means that no row was matched /// public int IndexSelected { get { return this.indexSelected; } } private int indexSelected; } /// /// Triggered when the user types into a list /// public class BeforeSearchingEventArgs : CancellableEventArgs { /// /// Create BeforeSearchingEventArgs /// /// /// public BeforeSearchingEventArgs(string stringToFind, int startSearchFrom) { this.StringToFind = stringToFind; this.StartSearchFrom = startSearchFrom; } /// /// Gets or sets the string that will be found by the search routine /// /// Modifying this value does not modify the memory of what the user has typed. /// When the user next presses a character, the search string will revert to what /// the user has actually typed. public string StringToFind; /// /// Gets or sets the index of the first row that will be considered to matching. /// public int StartSearchFrom; } /// /// The parameter block when telling the world about a cell based event /// public class CellEventArgs : EventArgs { /// /// Gets the ObjectListView that is the source of the event /// public ObjectListView ListView { get { return this.listView; } internal set { this.listView = value; } } private ObjectListView listView; /// /// Gets the model object under the cell /// /// This is null for events triggered by the header. public object Model { get { return this.model; } internal set { this.model = value; } } private object model; /// /// Gets the row index of the cell /// /// This is -1 for events triggered by the header. public int RowIndex { get { return this.rowIndex; } internal set { this.rowIndex = value; } } private int rowIndex = -1; /// /// Gets the column index of the cell /// /// This is -1 when the view is not in details view. public int ColumnIndex { get { return this.columnIndex; } internal set { this.columnIndex = value; } } private int columnIndex = -1; /// /// Gets the column of the cell /// /// This is null when the view is not in details view. public OLVColumn Column { get { return this.column; } internal set { this.column = value; } } private OLVColumn column; /// /// Gets the location of the mouse at the time of the event /// public Point Location { get { return this.location; } internal set { this.location = value; } } private Point location; /// /// Gets the state of the modifier keys at the time of the event /// public Keys ModifierKeys { get { return this.modifierKeys; } internal set { this.modifierKeys = value; } } private Keys modifierKeys; /// /// Gets the item of the cell /// public OLVListItem Item { get { return item; } internal set { this.item = value; } } private OLVListItem item; /// /// Gets the subitem of the cell /// /// This is null when the view is not in details view and /// for event triggered by the header public OLVListSubItem SubItem { get { return subItem; } internal set { this.subItem = value; } } private OLVListSubItem subItem; /// /// Gets the HitTest object that determined which cell was hit /// public OlvListViewHitTestInfo HitTest { get { return hitTest; } internal set { hitTest = value; } } private OlvListViewHitTestInfo hitTest; /// /// Gets or set if this event completelely handled. If it was, no further processing /// will be done for it. /// public bool Handled; } /// /// Tells the world that a cell was clicked /// public class CellClickEventArgs : CellEventArgs { /// /// Gets or sets the number of clicks associated with this event /// public int ClickCount { get { return this.clickCount; } set { this.clickCount = value; } } private int clickCount; } /// /// Tells the world that a cell was right clicked /// public class CellRightClickEventArgs : CellEventArgs { /// /// Gets or sets the menu that should be displayed as a result of this event. /// /// The menu will be positioned at Location, so changing that property changes /// where the menu will be displayed. public ContextMenuStrip MenuStrip; } /// /// Tell the world that the mouse is over a given cell /// public class CellOverEventArgs : CellEventArgs { } /// /// Tells the world that the frozen-ness of the ObjectListView has changed. /// public class FreezeEventArgs : EventArgs { /// /// Make a FreezeEventArgs /// /// public FreezeEventArgs(int freeze) { this.FreezeLevel = freeze; } /// /// How frozen is the control? 0 means that the control is unfrozen, /// more than 0 indicates froze. /// public int FreezeLevel { get { return this.freezeLevel; } set { this.freezeLevel = value; } } private int freezeLevel; } /// /// The parameter block when telling the world that a tool tip is about to be shown. /// public class ToolTipShowingEventArgs : CellEventArgs { /// /// Gets the tooltip control that is triggering the tooltip event /// public ToolTipControl ToolTipControl { get { return this.toolTipControl; } internal set { this.toolTipControl = value; } } private ToolTipControl toolTipControl; /// /// Gets or sets the text should be shown on the tooltip for this event /// /// Setting this to empty or null prevents any tooltip from showing public string Text; /// /// In what direction should the text for this tooltip be drawn? /// public RightToLeft RightToLeft; /// /// Should the tooltip for this event been shown in bubble style? /// /// This doesn't work reliable under Vista public bool? IsBalloon; /// /// What color should be used for the background of the tooltip /// /// Setting this does nothing under Vista public Color? BackColor; /// /// What color should be used for the foreground of the tooltip /// /// Setting this does nothing under Vista public Color? ForeColor; /// /// What string should be used as the title for the tooltip for this event? /// public string Title; /// /// Which standard icon should be used for the tooltip for this event /// public ToolTipControl.StandardIcons? StandardIcon; /// /// How many milliseconds should the tooltip remain before it automatically /// disappears. /// public int? AutoPopDelay; /// /// What font should be used to draw the text of the tooltip? /// public Font Font; } /// /// Common information to all hyperlink events /// public class HyperlinkEventArgs : EventArgs { //TODO: Unified with CellEventArgs /// /// Gets the ObjectListView that is the source of the event /// public ObjectListView ListView { get { return this.listView; } internal set { this.listView = value; } } private ObjectListView listView; /// /// Gets the model object under the cell /// public object Model { get { return this.model; } internal set { this.model = value; } } private object model; /// /// Gets the row index of the cell /// public int RowIndex { get { return this.rowIndex; } internal set { this.rowIndex = value; } } private int rowIndex = -1; /// /// Gets the column index of the cell /// /// This is -1 when the view is not in details view. public int ColumnIndex { get { return this.columnIndex; } internal set { this.columnIndex = value; } } private int columnIndex = -1; /// /// Gets the column of the cell /// /// This is null when the view is not in details view. public OLVColumn Column { get { return this.column; } internal set { this.column = value; } } private OLVColumn column; /// /// Gets the item of the cell /// public OLVListItem Item { get { return item; } internal set { this.item = value; } } private OLVListItem item; /// /// Gets the subitem of the cell /// /// This is null when the view is not in details view public OLVListSubItem SubItem { get { return subItem; } internal set { this.subItem = value; } } private OLVListSubItem subItem; /// /// Gets the ObjectListView that is the source of the event /// public string Url { get { return this.url; } internal set { this.url = value; } } private string url; /// /// Gets or set if this event completelely handled. If it was, no further processing /// will be done for it. /// public bool Handled { get { return handled; } set { handled = value; } } private bool handled; } /// /// /// public class IsHyperlinkEventArgs : EventArgs { /// /// Gets the ObjectListView that is the source of the event /// public ObjectListView ListView { get { return this.listView; } internal set { this.listView = value; } } private ObjectListView listView; /// /// Gets the model object under the cell /// public object Model { get { return this.model; } internal set { this.model = value; } } private object model; /// /// Gets the column of the cell /// /// This is null when the view is not in details view. public OLVColumn Column { get { return this.column; } internal set { this.column = value; } } private OLVColumn column; /// /// Gets the text of the cell /// public string Text { get { return this.text; } internal set { this.text = value; } } private string text; /// /// Gets or sets whether or not this cell is a hyperlink. /// Defaults to true for enabled rows and false for disabled rows. /// public bool IsHyperlink { get { return this.isHyperlink; } set { this.isHyperlink = value; } } private bool isHyperlink; /// /// Gets or sets the url that should be invoked when this cell is clicked. /// /// Setting this to None or String.Empty means that this cell is not a hyperlink public string Url; } /// /// public class FormatRowEventArgs : EventArgs { //TODO: Unified with CellEventArgs /// /// Gets the ObjectListView that is the source of the event /// public ObjectListView ListView { get { return this.listView; } internal set { this.listView = value; } } private ObjectListView listView; /// /// Gets the item of the cell /// public OLVListItem Item { get { return item; } internal set { this.item = value; } } private OLVListItem item; /// /// Gets the model object under the cell /// public object Model { get { return this.Item.RowObject; } } /// /// Gets the row index of the cell /// public int RowIndex { get { return this.rowIndex; } internal set { this.rowIndex = value; } } private int rowIndex = -1; /// /// Gets the display index of the row /// public int DisplayIndex { get { return this.displayIndex; } internal set { this.displayIndex = value; } } private int displayIndex = -1; /// /// Should events be triggered for each cell in this row? /// public bool UseCellFormatEvents { get { return useCellFormatEvents; } set { useCellFormatEvents = value; } } private bool useCellFormatEvents; } /// /// Parameter block for FormatCellEvent /// public class FormatCellEventArgs : FormatRowEventArgs { /// /// Gets the column index of the cell /// /// This is -1 when the view is not in details view. public int ColumnIndex { get { return this.columnIndex; } internal set { this.columnIndex = value; } } private int columnIndex = -1; /// /// Gets the column of the cell /// /// This is null when the view is not in details view. public OLVColumn Column { get { return this.column; } internal set { this.column = value; } } private OLVColumn column; /// /// Gets the subitem of the cell /// /// This is null when the view is not in details view public OLVListSubItem SubItem { get { return subItem; } internal set { this.subItem = value; } } private OLVListSubItem subItem; /// /// Gets the model value that is being displayed by the cell. /// /// This is null when the view is not in details view public object CellValue { get { return this.SubItem == null ? null : this.SubItem.ModelValue; } } } /// /// The event args when a hyperlink is clicked /// public class HyperlinkClickedEventArgs : CellEventArgs { /// /// Gets the url that was associated with this cell. /// public string Url { get { return url; } set { url = value; } } private string url; } /// /// The event args when the check box in a column header is changing /// public class HeaderCheckBoxChangingEventArgs : CancelEventArgs { /// /// Get the column whose checkbox is changing /// public OLVColumn Column { get { return column; } internal set { column = value; } } private OLVColumn column; /// /// Get or set the new state that should be used by the column /// public CheckState NewCheckState { get { return newCheckState; } set { newCheckState = value; } } private CheckState newCheckState; } /// /// The event args when the hot item changed /// public class HotItemChangedEventArgs : EventArgs { /// /// Gets or set if this event completelely handled. If it was, no further processing /// will be done for it. /// public bool Handled { get { return handled; } set { handled = value; } } private bool handled; /// /// Gets the part of the cell that the mouse is over /// public HitTestLocation HotCellHitLocation { get { return newHotCellHitLocation; } internal set { newHotCellHitLocation = value; } } private HitTestLocation newHotCellHitLocation; /// /// Gets an extended indication of the part of item/subitem/group that the mouse is currently over /// public virtual HitTestLocationEx HotCellHitLocationEx { get { return this.hotCellHitLocationEx; } internal set { this.hotCellHitLocationEx = value; } } private HitTestLocationEx hotCellHitLocationEx; /// /// Gets the index of the column that the mouse is over /// /// In non-details view, this will always be 0. public int HotColumnIndex { get { return newHotColumnIndex; } internal set { newHotColumnIndex = value; } } private int newHotColumnIndex; /// /// Gets the index of the row that the mouse is over /// public int HotRowIndex { get { return newHotRowIndex; } internal set { newHotRowIndex = value; } } private int newHotRowIndex; /// /// Gets the group that the mouse is over /// public OLVGroup HotGroup { get { return hotGroup; } internal set { hotGroup = value; } } private OLVGroup hotGroup; /// /// Gets the part of the cell that the mouse used to be over /// public HitTestLocation OldHotCellHitLocation { get { return oldHotCellHitLocation; } internal set { oldHotCellHitLocation = value; } } private HitTestLocation oldHotCellHitLocation; /// /// Gets an extended indication of the part of item/subitem/group that the mouse used to be over /// public virtual HitTestLocationEx OldHotCellHitLocationEx { get { return this.oldHotCellHitLocationEx; } internal set { this.oldHotCellHitLocationEx = value; } } private HitTestLocationEx oldHotCellHitLocationEx; /// /// Gets the index of the column that the mouse used to be over /// public int OldHotColumnIndex { get { return oldHotColumnIndex; } internal set { oldHotColumnIndex = value; } } private int oldHotColumnIndex; /// /// Gets the index of the row that the mouse used to be over /// public int OldHotRowIndex { get { return oldHotRowIndex; } internal set { oldHotRowIndex = value; } } private int oldHotRowIndex; /// /// Gets the group that the mouse used to be over /// public OLVGroup OldHotGroup { get { return oldHotGroup; } internal set { oldHotGroup = value; } } private OLVGroup oldHotGroup; /// /// Returns a string that represents the current object. /// /// /// A string that represents the current object. /// /// 2 public override string ToString() { return string.Format("NewHotCellHitLocation: {0}, HotCellHitLocationEx: {1}, NewHotColumnIndex: {2}, NewHotRowIndex: {3}, HotGroup: {4}", this.newHotCellHitLocation, this.hotCellHitLocationEx, this.newHotColumnIndex, this.newHotRowIndex, this.hotGroup); } } /// /// Let the world know that a checkbox on a subitem is changing /// public class SubItemCheckingEventArgs : CancellableEventArgs { /// /// Create a new event block /// /// /// /// /// /// public SubItemCheckingEventArgs(OLVColumn column, OLVListItem item, int subItemIndex, CheckState currentValue, CheckState newValue) { this.column = column; this.listViewItem = item; this.subItemIndex = subItemIndex; this.currentValue = currentValue; this.newValue = newValue; } /// /// The column of the cell that is having its checkbox changed. /// public OLVColumn Column { get { return this.column; } } private OLVColumn column; /// /// The model object of the row of the cell that is having its checkbox changed. /// public Object RowObject { get { return this.listViewItem.RowObject; } } /// /// The listview item of the cell that is having its checkbox changed. /// public OLVListItem ListViewItem { get { return this.listViewItem; } } private OLVListItem listViewItem; /// /// The current check state of the cell. /// public CheckState CurrentValue { get { return this.currentValue; } } private CheckState currentValue; /// /// The proposed new check state of the cell. /// public CheckState NewValue { get { return this.newValue; } set { this.newValue = value; } } private CheckState newValue; /// /// The index of the cell that is going to be or has been edited. /// public int SubItemIndex { get { return this.subItemIndex; } } private int subItemIndex; } /// /// This event argument block is used when groups are created for a list. /// public class CreateGroupsEventArgs : EventArgs { /// /// Create a CreateGroupsEventArgs /// /// public CreateGroupsEventArgs(GroupingParameters parms) { this.parameters = parms; } /// /// Gets the settings that control the creation of groups /// public GroupingParameters Parameters { get { return this.parameters; } } private GroupingParameters parameters; /// /// Gets or sets the groups that should be used /// public IList Groups { get { return this.groups; } set { this.groups = value; } } private IList groups; /// /// Has this event been cancelled by the event handler? /// public bool Canceled { get { return canceled; } set { canceled = value; } } private bool canceled; } /// /// This event argument block is used when the text of a group task is clicked /// public class GroupTaskClickedEventArgs : EventArgs { /// /// Create a GroupTaskClickedEventArgs /// /// public GroupTaskClickedEventArgs(OLVGroup group) { this.group = group; } /// /// Gets which group was clicked /// public OLVGroup Group { get { return this.group; } } private readonly OLVGroup group; } /// /// This event argument block is used when a group is about to expand or collapse /// public class GroupExpandingCollapsingEventArgs : CancellableEventArgs { /// /// Create a GroupExpandingCollapsingEventArgs /// /// public GroupExpandingCollapsingEventArgs(OLVGroup group) { if (group == null) throw new ArgumentNullException("group"); this.olvGroup = group; } /// /// Gets which group is expanding/collapsing /// public OLVGroup Group { get { return this.olvGroup; } } private readonly OLVGroup olvGroup; /// /// Gets whether this event is going to expand the group. /// If this is false, the group must be collapsing. /// public bool IsExpanding { get { return this.Group.Collapsed; } } } /// /// This event argument block is used when the state of group has changed (collapsed, selected) /// public class GroupStateChangedEventArgs : EventArgs { /// /// Create a GroupStateChangedEventArgs /// /// /// /// public GroupStateChangedEventArgs(OLVGroup group, GroupState oldState, GroupState newState) { this.group = group; this.oldState = oldState; this.newState = newState; } /// /// Gets whether the group was collapsed by this event /// public bool Collapsed { get { return ((oldState & GroupState.LVGS_COLLAPSED) != GroupState.LVGS_COLLAPSED) && ((newState & GroupState.LVGS_COLLAPSED) == GroupState.LVGS_COLLAPSED); } } /// /// Gets whether the group was focused by this event /// public bool Focused { get { return ((oldState & GroupState.LVGS_FOCUSED) != GroupState.LVGS_FOCUSED) && ((newState & GroupState.LVGS_FOCUSED) == GroupState.LVGS_FOCUSED); } } /// /// Gets whether the group was selected by this event /// public bool Selected { get { return ((oldState & GroupState.LVGS_SELECTED) != GroupState.LVGS_SELECTED) && ((newState & GroupState.LVGS_SELECTED) == GroupState.LVGS_SELECTED); } } /// /// Gets whether the group was uncollapsed by this event /// public bool Uncollapsed { get { return ((oldState & GroupState.LVGS_COLLAPSED) == GroupState.LVGS_COLLAPSED) && ((newState & GroupState.LVGS_COLLAPSED) != GroupState.LVGS_COLLAPSED); } } /// /// Gets whether the group was unfocused by this event /// public bool Unfocused { get { return ((oldState & GroupState.LVGS_FOCUSED) == GroupState.LVGS_FOCUSED) && ((newState & GroupState.LVGS_FOCUSED) != GroupState.LVGS_FOCUSED); } } /// /// Gets whether the group was unselected by this event /// public bool Unselected { get { return ((oldState & GroupState.LVGS_SELECTED) == GroupState.LVGS_SELECTED) && ((newState & GroupState.LVGS_SELECTED) != GroupState.LVGS_SELECTED); } } /// /// Gets which group had its state changed /// public OLVGroup Group { get { return this.group; } } private readonly OLVGroup group; /// /// Gets the previous state of the group /// public GroupState OldState { get { return this.oldState; } } private readonly GroupState oldState; /// /// Gets the new state of the group /// public GroupState NewState { get { return this.newState; } } private readonly GroupState newState; } /// /// This event argument block is used when a branch of a tree is about to be expanded /// public class TreeBranchExpandingEventArgs : CancellableEventArgs { /// /// Create a new event args /// /// /// public TreeBranchExpandingEventArgs(object model, OLVListItem item) { this.Model = model; this.Item = item; } /// /// Gets the model that is about to expand. If null, all branches are going to be expanded. /// public object Model { get { return model; } private set { model = value; } } private object model; /// /// Gets the OLVListItem that is about to be expanded /// public OLVListItem Item { get { return item; } private set { item = value; } } private OLVListItem item; } /// /// This event argument block is used when a branch of a tree has just been expanded /// public class TreeBranchExpandedEventArgs : EventArgs { /// /// Create a new event args /// /// /// public TreeBranchExpandedEventArgs(object model, OLVListItem item) { this.Model = model; this.Item = item; } /// /// Gets the model that is was expanded. If null, all branches were expanded. /// public object Model { get { return model; } private set { model = value; } } private object model; /// /// Gets the OLVListItem that was expanded /// public OLVListItem Item { get { return item; } private set { item = value; } } private OLVListItem item; } /// /// This event argument block is used when a branch of a tree is about to be collapsed /// public class TreeBranchCollapsingEventArgs : CancellableEventArgs { /// /// Create a new event args /// /// /// public TreeBranchCollapsingEventArgs(object model, OLVListItem item) { this.Model = model; this.Item = item; } /// /// Gets the model that is about to collapse. If this is null, all models are going to collapse. /// public object Model { get { return model; } private set { model = value; } } private object model; /// /// Gets the OLVListItem that is about to be collapsed. Can be null /// public OLVListItem Item { get { return item; } private set { item = value; } } private OLVListItem item; } /// /// This event argument block is used when a branch of a tree has just been collapsed /// public class TreeBranchCollapsedEventArgs : EventArgs { /// /// Create a new event args /// /// /// public TreeBranchCollapsedEventArgs(object model, OLVListItem item) { this.Model = model; this.Item = item; } /// /// Gets the model that is was collapsed. If null, all branches were collapsed /// public object Model { get { return model; } private set { model = value; } } private object model; /// /// Gets the OLVListItem that was collapsed /// public OLVListItem Item { get { return item; } private set { item = value; } } private OLVListItem item; } #endregion }