如何在DevExpress TreeListView列中访问内嵌的TableView?
问题:无法访问TreeListView列内嵌套的TableView对象
在dxg:TreeListView的列中嵌入了dxg:TableView,界面运行正常,但后台代码无法直接访问名为LocationView的TableView,却能正常访问外层的StatesView:
// 可正常运行 var focusedState = (StateType)StatesView.FocusedNode.Content; // LocationView无法被调试器或解释器识别 var focusedLocationInStateRow = (LocationType)LocationView.FocusedNode.Content;
目标是获取State行内选中的Location行(Location是State数据模型中的集合),并通过DeleteLocationRow方法删除该记录。
对应的WPF标记如下:
<Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <dxg:TreeListControl x:Name="StateGrid" AllowLiveDataShaping="True" SelectionMode="Cell" Margin="16.5,0,0,0" SelectedItem="{Binding SelectedState}" ItemsSource="{Binding MyStates}"> <dxg:TreeListControl.Resources> <Style x:Key="SummaryStyle" TargetType="Run"> <Setter Property="FontWeight" Value="Bold"/> <Setter Property="dxp:TextExportSettings.FontFamily" Value="Arial Narrow"/> </Style> </dxg:TreeListControl.Resources> <dxg:TreeListControl.Columns> <dxg:TreeListColumn Header="Name" Width="190" FieldName="Name" /> <dxg:TreeListColumn Header="State Id" Width="80" FieldName="StateId" /> <dxg:TreeListColumn AllowEditing="true" Header="Locations" Width="Auto"> <dxg:TreeListColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding RowData.Row.Locations, Converter={valueConverters:LocaleIdsTextValueConverter}}"/> </DataTemplate> </dxg:TreeListColumn.CellTemplate> <dxg:TreeListColumn.CellEditTemplate> <DataTemplate> <dxg:GridControl x:Name="LocationGrid" ItemsSource="{Binding RowData.Row.Locations}" HorizontalAlignment="Stretch" Height="100" Width="525" AutoGenerateColumns="AddNew" EnableSmartColumnsGeneration="True" SelectionMode="Cell"> <dxg:GridControl.Columns> <dxg:GridColumn Header="LocationId" FieldName="LocationId" SortIndex="0" Width="100" AllowEditing="true"> <dxg:GridColumn.EditSettings> <dxe:SpinEditSettings MaskType="Numeric" Mask="d" MinValue="1" MaxValue="100" ShowText="{Binding Path=RowData.Row.IsNode}" IsTextEditable="{Binding Path=RowData.Row.IsNode}" IsEnabled="{Binding Path=RowData.Row.IsNode}" /> </dxg:GridColumn.EditSettings> </dxg:GridColumn> <dxg:GridColumn Header="Incorporated Date" SortIndex="1" SortOrder="Ascending" Width="110" FieldName="IncorporatedDate" AllowEditing="true"> <dxg:GridColumn.EditSettings> <dxe:DateEditSettings MaskType="DateTime" Mask="MM/dd/yyyy HH:mm" MaskUseAsDisplayFormat="True" dxe:DateTimeMaskOptions.DateTimeKind="Local" NullText="" AllowNullInput="True" ValidateOnEnterKeyPressed="True" ValidateOnTextInput="True"> <dxe:DateEditSettings.StyleSettings> <dxe:DateEditNavigatorWithTimePickerStyleSettings/> </dxe:DateEditSettings.StyleSettings> </dxe:DateEditSettings> </dxg:GridColumn.EditSettings> </dxg:GridColumn> <dxg:GridColumn Header="Delete Rows" Width="100"> <dxg:GridColumn.CellTemplate> <DataTemplate> <Button Click="DeleteLocationRow"> Delete Row </Button> </DataTemplate> </dxg:GridColumn.CellTemplate> </dxg:GridColumn> </dxg:GridControl.Columns> <dxg:GridControl.View> <dxg:TableView Name="LocationView" NavigationStyle="Cell" AutoScrollOnSorting="False" NewItemRowPosition="Bottom" HorizontalScrollbarVisibility="Hidden" ShowColumnHeaders="True" ImmediateUpdateRowPosition="False" AllowSorting="True" AllowEditing="True" ShowIndicator="True" ShowGroupPanel="False" ShowTotalSummary="False" ShowFilterPanelMode="Never" CellStyle="{StaticResource CustomLocaleCellStyle}" ShowAutoFilterRow="False"> </dxg:TableView> </dxg:GridControl.View> </dxg:GridControl> </DataTemplate> </dxg:TreeListColumn.CellEditTemplate> </dxg:TreeListColumn> <dxg:TreeListColumn Header="City" Width="60" FieldName="City" /> </dxg:TreeListControl.Columns> <dxg:TreeListControl.View> <dxg:TreeListView Name="StatesView" TreeDerivationMode="HierarchicalDataTemplate" AutoExpandAllNodes="True" AllowConditionalFormattingMenu="True" AutoScrollOnSorting="False" ShowEmptyText ="False" AutoWidth="False" ShowExpandButtons="True" ShowRootIndent="True" ShowAutoFilterRow="True" NavigationStyle="Cell" ImmediateUpdateRowPosition="False" EnableImmediatePosting ="True" AllowSorting="False" AllowEditing="True" ShowIndicator="False" ClipboardCopyOptions="All" ClipboardMode="Formatted" ShowTotalSummary="True" ShowFilterPanelMode="Never" EditorShowMode="MouseDown" RowStyle="{StaticResource FocusedRowStyle}" CellStyle="{StaticResource CustomCellStyle}" AllowDataUpdateFormatConditionMenu="True"> <dxg:TreeListView.ColumnHeaderTemplate> <DataTemplate> <TextBlock Text="{Binding}" TextWrapping="Wrap"/> </DataTemplate> </dxg:TreeListView.ColumnHeaderTemplate> </dxg:TreeListView> </dxg:TreeListControl.View> </dxg:TreeListControl> </Grid>
原因分析
LocationView和它所在的LocationGrid定义在CellEditTemplate的DataTemplate内部,DataTemplate是复用模板,只有当TreeList单元格进入编辑状态时才会实例化模板内容。XAML编译器不会为模板内的元素生成后台代码可直接访问的字段,因此无法通过名称直接调用。
解决方法
方法1:通过VisualTreeHelper查找激活的TableView
当TreeList单元格处于编辑状态时,遍历可视化树找到对应的LocationView:
private TableView GetActiveLocationView() { var activeEditor = StatesView.ActiveEditor; if (activeEditor == null) return null; return FindVisualChild<TableView>(activeEditor); } private T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) { DependencyObject child = VisualTreeHelper.GetChild(obj, i); if (child is T target) return target; T childOfChild = FindVisualChild<T>(child); if (childOfChild != null) return childOfChild; } return null; } // 使用示例 var locationView = GetActiveLocationView(); if (locationView != null) { var focusedLocation = (LocationType)locationView.FocusedRow; DeleteLocationRow(focusedLocation); }
方法2:通过按钮DataContext直接获取当前Location对象
删除按钮位于LocationGrid的行内,可直接绑定按钮的DataContext到当前Location行,点击时直接获取对象:
修改按钮XAML:
<Button Click="DeleteLocationRow" DataContext="{Binding RowData.Row}"> Delete Row </Button>
后台代码:
private void DeleteLocationRow(object sender, RoutedEventArgs e) { var button = sender as Button; var locationToDelete = button.DataContext as LocationType; if (locationToDelete == null) return; var focusedState = (StateType)StatesView.FocusedNode.Content; focusedState.Locations.Remove(locationToDelete); // 如果Locations是ObservableCollection,UI会自动更新;否则需触发PropertyChanged }
方法3:MVVM方式绑定SelectedItem
将LocationGrid的SelectedItem绑定到ViewModel属性,无需直接操作View:
修改LocationGrid的XAML:
<dxg:GridControl x:Name="LocationGrid" ItemsSource="{Binding RowData.Row.Locations}" SelectedItem="{Binding DataContext.SelectedLocation, RelativeSource={RelativeSource AncestorType=dxg:TreeListControl}}" ...>
ViewModel添加属性和方法:
private LocationType _selectedLocation; public LocationType SelectedLocation { get => _selectedLocation; set { _selectedLocation = value; OnPropertyChanged(); } } private StateType _selectedState; public StateType SelectedState { get => _selectedState; set { _selectedState = value; OnPropertyChanged(); } } public ICommand DeleteLocationCommand => new RelayCommand(() => { if (SelectedState != null && SelectedLocation != null) { SelectedState.Locations.Remove(SelectedLocation); } });
按钮绑定命令:
<Button Command="{Binding DataContext.DeleteLocationCommand, RelativeSource={RelativeSource AncestorType=dxg:TreeListControl}}"> Delete Row </Button>
内容的提问来源于stack exchange,提问作者user2284452
相关产品推荐
相关产品推荐

