/// <summary> /// 在结点被收缩起来时,应该它的子结点的所有数据在_objectItems中对应的项都移除。 /// </summary> /// <param name="treeNode"></param> private void OnNodeCollapsed(TreeGridRow treeNode) { //处理 LazyLoading //结点被折叠后事件。 //如果懒加载开启后,则在折叠时清空所有子结点。 //不处理根结点。 if (treeNode != this.RootNode) { //更新布局 var itemKey = GetId(treeNode.DataContext); _renderExpansion.Remove(itemKey); if (this.IsLazyLoading && this.ClearCollapsedNodes && treeNode.Items.Count > 0) { //deregisters listeners for all ancestors if (this.ObserveChildItems) { _monitor.RemoveNodes(treeNode.Items); } treeNode.ItemsSource = null; TreeGridHelper.CreateDummyItem(treeNode); //TODO do we have collapsed event if all childs are being filtered? //if yes, we need to set the dummy nodes visibility depending //on the filtered childs (visible if at least 1 visible child) } } }
/// <summary> /// 为指定的结点应用 <see cref="RowStyle"/> 样式。 /// /// 子类重写此方法用于应用自定义样式。 /// </summary> /// <param name="treeNode">The node to be styled.</param> private void ApplyNodeStyle(TreeGridRow treeNode) { var style = this.RowStyle; if (style != null) { treeNode.Style = style; } }
//DummyItem 意思是展开树节点时,显示子节点正在加载中的一个临时树节点。 /// <summary> /// Checks whether a given tree node contains a dummy node to /// ensure it's rendered with an expander, and removes the node. /// </summary> /// <param name="treeNode">The node to be checked for dummy /// child nodes.</param> internal static void ClearDummyChildNode(TreeGridRow treeNode) { ////if the item has never been expanded yet, it contains a dummy ////node - replace that one and insert real data //if (ContainsDummyNode(treeNode)) //{ // treeNode.Items.Clear(); //} }
public override void OnApplyTemplate() { base.OnApplyTemplate(); var rowPresenter = ItemsControl.ItemsControlFromItemContainer(this) as TreeGridCellsPresenter; if (rowPresenter != null) { this._row = rowPresenter.Row; } }
/// <summary> /// 树型控件中的 Id 获取方法不能直接使用 Id,而是应该: /// 尽量使用 TreeId,不支持时,再使用实体自己的 Id。 /// </summary> /// <param name="item"></param> /// <returns></returns> private object GetId(TreeGridRow item) { var entity = item.DataContext; if (entity == null) { throw new InvalidOperationException("这个 TreeGridRow 还没有绑定实体,获取 Id 失败。"); } return(GetId(entity)); }
private void RefreshRowNo(TreeGridRow row) { //目前为了简单起见,只支持表格模式下有行号。(因为在树型模式下,行号应该使用递归遍历后计算生成。) if (this.OnlyGridMode) { //不能直接使用 DataItemsSource,这是因为可能界面会对集合进行排序。 //var item = GetEntity(row); //var rowNo = this.DataItemsSource.IndexOf(item) + 1; //row.RowNo = rowNo; row.RowNo = this.ItemContainerGenerator.IndexFromContainer(row) + 1; } }
/// <summary> /// 尽量展开所有目标行的父行,并让目标行处于可视范围内。 /// </summary> /// <param name="item">要显示的行对应的数据。</param> /// <returns>返回是否成功显示该数据对应的行。</returns> public bool ExpandToView(object item) { bool success = false; if (item != null) { //从最上层父开始搜索对应的生成的行,如果已经生成,则直接展开该行, //如果该行还没有生成,则在渲染布局中加入该id,该行在接下来生成时会自动展开。 var parents = this.GetParentItemList(item); bool stopFindingRow = false; TreeGridRow lastParentRow = null; foreach (var parent in parents) { _renderExpansion.Add(GetId(parent)); if (!stopFindingRow) { var parentRow = this.FindRow(parent); if (parentRow == null) { stopFindingRow = true; } else { lastParentRow = parentRow; parentRow.IsExpanded = true; } } } //没有停止搜索,说明所有行都已经生成。 if (!stopFindingRow) { var selectedRow = this.SelectedRow; if (selectedRow != null) { selectedRow.BringIntoView(); success = true; } } //如果目标行并没有成功显示,则把最后可见的父行显示出来。 if (!success && lastParentRow != null) { lastParentRow.BringIntoView(); } } return(success); }
/// <summary> /// Copy from TreeView class. /// </summary> /// <param name="data"></param> /// <param name="newRow"></param> /// <param name="selected"></param> internal void ChangeSelection(object data, TreeGridRow newRow, bool selected) { //同步本对象的 SelectedItem 属性。 if (selected) { this.SelectedItem = data; } else { if (this.SelectedItem == data) { this.SelectedItem = null; } } }
/// <summary> /// 设置行对应的选中状态属性 /// </summary> /// <param name="row"></param> /// <param name="isSelected"></param> internal void ModifyRowProperty(TreeGridRow row, bool isSelected) { if (this.IsChecking) { row.SetIsCheckedField(isSelected); } else { //然后,需要设置 TreeListItem.IsSelected 属性。 //否则,会造成内部使用的 TreeView 的 SelectedItem 属性不变,但是高亮的状态已经被清除, //这时用户再次点击该行时,TreeView 的 SelectedItemChanged 事件不会发生, //也就不会发生整个 MTTG 的 SelectedItem 处理流程,造成无法选中该行。 //具体情况见 bug:http://ipm.grandsoft.com.cn/issues/247260 row.SetIsSelectedField(isSelected); } }
private void OnNodeExpanded(TreeGridRow treeNode) { //处理 LazyLoading //结点被展开后事件 //完成懒加载时的结点创建工作,并设置各结点的 Filter 属性。 object item = treeNode.DataContext; if (item != null) { //布局 var itemKey = GetId(item); _renderExpansion.Add(itemKey); var nodeItems = treeNode.Items; //懒加载状态下,创建结点。 if (this.IsLazyLoading) { TreeGridHelper.ClearDummyChildNode(treeNode); if (nodeItems.Count == 0) { var childItems = this.GetChildItems(item); treeNode.ItemsSource = childItems; //刷新以排序 if (nodeItems.NeedsRefresh) { nodeItems.Refresh(); } } } if (nodeItems.Count == 0) { //如果结点没有数据,则不需要展开它。 treeNode.IsExpanded = false; _renderExpansion.Remove(itemKey); } } }
/// <summary> /// Copies the <see cref="SortDescription"/> elements of /// the <see cref="NodeSortDescriptions"/> collection to /// a currently processed tree node. This method is being /// invoked during the initialization of a given node, and /// if the <see cref="NodeSortDescriptions"/> property /// is changed at runtime.<br/> /// If the <see cref="ItemCollection.SortDescriptions"/> /// collection of the submitted <paramref name="node"/> is /// not empty, it will be cleared.<br/> /// This method is always being invoked, even if the /// <see cref="NodeSortDescriptions"/> dependency property /// is null. If you want to apply a custom sorting mechanism, /// simply override this method. /// </summary> /// <param name="node">The currently processed node, if any. /// This parameter is null if sort parameters should be set /// on the tree's <see cref="ItemsControl.Items"/> /// collection itself. /// </param> private void ApplySorting(TreeGridRow node) { //check whether we're sorting on node or tree level var items = node == null ? this.Items : node.Items; using (items.DeferRefresh()) { //clear existing sort directions, if there are any items.SortDescriptions.Clear(); //copy new sort directions if (this.NodeSortDescriptions != null) { foreach (var sd in this.NodeSortDescriptions) { items.SortDescriptions.Add(sd); } } } }
private void FindViewPort(TreeGridRow row) { var itemsControl = row.ParentItemsControl; if (itemsControl != null) { var scrollViewer = MSInternal.GetScrollHost(itemsControl); if (scrollViewer != null && scrollViewer.CanContentScroll) { var itemsHost = MSInternal.GetItemsHost(itemsControl); if (itemsHost is VirtualizingPanel) { this._viewPort = scrollViewer.Template.FindName(PART_ScrollContentPresenter, scrollViewer) as FrameworkElement; if (this._viewPort == null) { this._viewPort = scrollViewer; } } } } }
/*********************** 代码块解释 ********************************* * * 树结点的选择事件处理 * * 基类的此方法以一种防止 TreeView 重入的方式来同步 this.SelectedItem 到 TreeView.SelecteItem 上。 * 并在最后调用 this.OnSelectedItemChanged 方法,以抛出路由事件。 * * 注意这三个方法的顺序: * OnTreeViewSelectedItemChanged * OnSelectedItemPropertyChanged * RaiseSelectedItemChanged * **********************************************************************/ /// <summary> /// Copy from TreeView class. /// </summary> /// <param name="collapsed"></param> internal void HandleSelectionOnCollapsed(TreeGridRow collapsed) { using (this.SelectionProcess.TryEnterProcess(TGSelectionProcess.Row_Collapsed)) { if (this.SelectionProcess.Success) { //目前只支持在单选的时候,提供此功能。 if (this.SelectParentOnCollapsed && this.SelectionModel.InnerItems.Count == 1) { var selectedRow = this.SelectedRow; if (selectedRow != null && selectedRow != collapsed) { //检查 collapsed 是否为 selectedRow 的父行。 bool selectionInCollapsed = false; for (var parent = selectedRow; parent != null; parent = parent.ParentRow) { //找到 collasped 对象 if (parent == collapsed) { selectionInCollapsed = true; break; } } if (selectionInCollapsed) { this.ChangeSelection(collapsed.DataContext, collapsed, true); if (selectedRow.IsKeyboardFocusWithin) { selectedRow.Focus(); return; } } } } } } }
/// <summary> /// 设置某行的选择状态,并级联选择 /// </summary> /// <param name="row"></param> /// <param name="value"></param> internal void CheckRowWithCascade(TreeGridRow row, bool value) { if (!this.IsCheckingEnabled) { return; } var entity = row.DataContext; this.CheckingModel.MarkSelected(row, value); this.RasieCheckChanged(entity, value); //CascadeParent 模式下,需要把所有父对象选中。 if (value && this.NeedCascade(CheckingCascadeMode.CascadeParent)) { object parent = this.GetParentItem(entity); while (parent != null) { var parentRow = this.FindRow(parent); if (parentRow == null || parentRow.IsChecked) { break; } this.CheckingModel.MarkSelected(parentRow, true); this.RasieCheckChanged(parent, true); parent = this.GetParentItem(parent); } } //CascadeChildren 模式下,需要把所有子对象选中。 if (this.NeedCascade(CheckingCascadeMode.CascadeChildren)) { this.CheckChildrenRecur(entity, value); } }
/// <summary> /// Removes listener for an item that is represented /// by a given <paramref name="node"/> from the cache, and also /// processes all the node's descendants recursively. /// </summary> /// <param name="node">The node to be removed.</param> public void UnregisterListeners(TreeGridRow node) { lock (this) { //try to get the represented item object item = node.DataContext; if (item == null) { return; } //get the item's observed child collection and //deregister it var itemKey = tree.GetId(item); RemoveCollectionFromCache(itemKey); foreach (TreeGridRow childNode in node.Items) { //recursively deregister descendants by //removing all child nodes UnregisterListeners(childNode); } } }
public TreeGridRowAutomationPeer(TreeGridRow owner) : base(owner) { this._owner = owner; }
/// <summary> /// 设置某行的“选中”状态 /// /// 维护两个底层属性:一个是选择项 List,一个是 row 的选择状态属性。 /// </summary> /// <param name="row"></param> /// <param name="isSelected">是“选中”状态还是“未选中”状态。</param> internal void MarkSelected(TreeGridRow row, bool isSelected) { this.ModifyList(row.DataContext, isSelected); this.ModifyRowProperty(row, isSelected); }
internal static bool IsDummyItem(TreeGridRow item) { return false; //return TreeGrid.GetEntity(item) == null; }
/// <summary> /// Validates whether a given node contains a single dummy item, /// which was added to ensure the submitted tree node renders /// an expander. /// </summary> /// <param name="treeNode">The tree node to be validated.</param> /// <returns>True if the node contains a dummy item.</returns> internal static bool ContainsDummyNode(TreeGridRow treeNode) { return false; //return treeNode.Items.Count == 1 && TreeGrid.GetEntity(((TreeGridRow)treeNode.Items[0])) == null; }
/// <summary> /// 树型控件中的 Id 获取方法不能直接使用 Id,而是应该: /// 尽量使用 TreeId,不支持时,再使用实体自己的 Id。 /// </summary> /// <param name="item"></param> /// <returns></returns> private object GetId(TreeGridRow item) { var entity = item.DataContext; if (entity == null) throw new InvalidOperationException("这个 TreeGridRow 还没有绑定实体,获取 Id 失败。"); return GetId(entity); }
/// <summary> /// 开始编辑某行。 /// </summary> /// <param name="row"></param> /// <param name="focusCell">编辑后,应该首先进入编辑状态的单元格。</param> public bool TryEditRow(TreeGridRow row, TreeGridCell focusCell) { return(this.TryEditRow(row, null, focusCell)); }
/// <summary> /// Validates whether a given node contains a single dummy item, /// which was added to ensure the submitted tree node renders /// an expander. /// </summary> /// <param name="treeNode">The tree node to be validated.</param> /// <returns>True if the node contains a dummy item.</returns> internal static bool ContainsDummyNode(TreeGridRow treeNode) { return(false); //return treeNode.Items.Count == 1 && TreeGrid.GetEntity(((TreeGridRow)treeNode.Items[0])) == null; }
/// <summary> /// 开始编辑某行。 /// </summary> /// <param name="row"></param> /// <param name="editingEventArgs">引发编辑的事件参数。</param> /// <param name="focusCell">编辑后,应该首先进入编辑状态的单元格。</param> internal bool TryEditRow(TreeGridRow row, RoutedEventArgs editingEventArgs, TreeGridCell focusCell) { var success = false; if (!this.IsReadOnly && this.EditingMode == TreeGridEditingMode.Row) { var item = row.DataContext; var selectedItems = this.SelectionModel.InnerItems; //由于点击任何一行的该列都会发生此事件,所以需要检测如果当前只选择了一列 if (selectedItems.Count == 1 && item == selectedItems[0]) { //先退出之前行的编辑状态。 if (this._editingItem != item) { this.ExitRowEditing(); this._editingItem = item; } //focused 表示是否已经有单元格设置了焦点。 bool focused = focusCell != null; var mouseArgs = editingEventArgs as MouseButtonEventArgs; //设置所有单元格的编辑状态,及焦点。 var cells = row.TraverseCells(); foreach (var cell in cells) { //设置单元格的编辑状态 if (!cell.IsEditing && cell.Column.CanEdit(this._editingItem)) { cell.SetIsEditingField(true); } //如果成功设置单元格的编辑状态,而且还没有设置单元格焦点, //那么就通过鼠标的坐标来判断是否需要设置当前单元格的焦点。 if (!focused && mouseArgs != null && cell.IsEditing && cell.VisibleOnVirtualizing) { var position = mouseArgs.GetPosition(cell); var cellPosition = new Rect(cell.RenderSize); if (cellPosition.Contains(position)) { cell.FocusOnEditing(editingEventArgs); focused = true; } } } if (focusCell != null) { focusCell.FocusOnEditing(editingEventArgs); } //标记事件已经处理,防止事件继续冒泡导致引发 TreeGridRow.OnGotFocus 事件。 if (editingEventArgs != null) editingEventArgs.Handled = true; success = true; } } return success; }
/// <summary> /// 为指定的结点应用 <see cref="RowStyle"/> 样式。 /// /// 子类重写此方法用于应用自定义样式。 /// </summary> /// <param name="treeNode">The node to be styled.</param> private void ApplyNodeStyle(TreeGridRow treeNode) { var style = this.RowStyle; if (style != null) treeNode.Style = style; }
/// <summary> /// 开始编辑某行。 /// </summary> /// <param name="row"></param> /// <param name="editingEventArgs">引发编辑的事件参数。</param> /// <param name="focusCell">编辑后,应该首先进入编辑状态的单元格。</param> internal bool TryEditRow(TreeGridRow row, RoutedEventArgs editingEventArgs, TreeGridCell focusCell) { var success = false; if (!this.IsReadOnly && this.EditingMode == TreeGridEditingMode.Row) { var item = row.DataContext; var selectedItems = this.SelectionModel.InnerItems; //由于点击任何一行的该列都会发生此事件,所以需要检测如果当前只选择了一列 if (selectedItems.Count == 1 && item == selectedItems[0]) { //先退出之前行的编辑状态。 if (this._editingItem != item) { this.ExitRowEditing(); this._editingItem = item; } //focused 表示是否已经有单元格设置了焦点。 bool focused = focusCell != null; var mouseArgs = editingEventArgs as MouseButtonEventArgs; //设置所有单元格的编辑状态,及焦点。 var cells = row.TraverseCells(); foreach (var cell in cells) { //设置单元格的编辑状态 if (!cell.IsEditing && cell.Column.CanEdit(this._editingItem)) { cell.SetIsEditingField(true); } //如果成功设置单元格的编辑状态,而且还没有设置单元格焦点, //那么就通过鼠标的坐标来判断是否需要设置当前单元格的焦点。 if (!focused && mouseArgs != null && cell.IsEditing && cell.VisibleOnVirtualizing) { var position = mouseArgs.GetPosition(cell); var cellPosition = new Rect(cell.RenderSize); if (cellPosition.Contains(position)) { cell.FocusOnEditing(editingEventArgs); focused = true; } } } if (focusCell != null) { focusCell.FocusOnEditing(editingEventArgs); } //标记事件已经处理,防止事件继续冒泡导致引发 TreeGridRow.OnGotFocus 事件。 if (editingEventArgs != null) { editingEventArgs.Handled = true; } success = true; } } return(success); }
internal static void CreateDummyItem(TreeGridRow treeNode) { ////clear items and insert dummy //treeNode.Items.Clear(); //treeNode.Items.Add(this.CreateDummyItem()); }
/// <summary> /// Removes listener for an item that is represented /// by a given <paramref name="node"/> from the cache, and also /// processes all the node's descendants recursively. /// </summary> /// <param name="node">The node to be removed.</param> public void UnregisterListeners(TreeGridRow node) { lock (this) { //try to get the represented item object item = node.DataContext; if (item == null) return; //get the item's observed child collection and //deregister it var itemKey = tree.GetId(item); RemoveCollectionFromCache(itemKey); foreach (TreeGridRow childNode in node.Items) { //recursively deregister descendants by //removing all child nodes UnregisterListeners(childNode); } } }
internal static bool IsDummyItem(TreeGridRow item) { return(false); //return TreeGrid.GetEntity(item) == null; }
/// <summary> /// 开始编辑某行。 /// </summary> /// <param name="row"></param> /// <param name="focusCell">编辑后,应该首先进入编辑状态的单元格。</param> public bool TryEditRow(TreeGridRow row, TreeGridCell focusCell) { return this.TryEditRow(row, null, focusCell); }