WPF custom control GetTemplateChild vs FindName,NameScope separation between Logical Tree and Visual Tree,
Protected DependencyObject GetTemplateChild(string childName) is a framework-designed method exclusively for custom controls.
- Access the current applied
ControlTemplateof your control - Traverse the Visual Tree generated by the template
- Look up the name inside the template’s private isolated
NameScope
All elements defined inside <ControlTemplate> are constructed into the Visual Tree.
- These template-generated elements do NOT join the Logical Tree of your outer control.
- Every
ControlTemplateowns its own independentNameScope. Names defined inside the template cannot be discovered by the outer control’sNameScope
public override void OnApplyTemplate() { base.OnApplyTemplate(); //invalid _leftDG = FindName("PART_LeftDG") as DataGrid; //valid _leftDG = GetTemplateChild("PART_LeftDG") as DataGrid; _rightDG = GetTemplateChild("PART_RightDG") as DataGrid; }
//D:\C\WpfApp9\WpfApp9\Themes\Generic.xaml <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:behavior="http://schemas.microsoft.com/xaml/behaviors" xmlns:local="clr-namespace:WpfApp9"> <Style TargetType="{x:Type local:DualDatagrid}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:DualDatagrid}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <DataGrid x:Name="PART_LeftDG" Grid.Column="0" ItemsSource="{Binding LeftDGItemsSource,RelativeSource={RelativeSource AncestorType=local:DualDatagrid}}" VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.CacheLength="5,5" VirtualizingPanel.CacheLengthUnit="Item" AutoGenerateColumns="True" CanUserAddRows="False" IsReadOnly="True"> <behavior:Interaction.Behaviors> <local:SyncScrollBehavior LeftTag="LeftDG"/> </behavior:Interaction.Behaviors> </DataGrid> <DataGrid x:Name="PART_RightDG" Grid.Column="1" ItemsSource="{Binding RightDGItemsSource,RelativeSource={RelativeSource AncestorType=local:DualDatagrid}}" VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.CacheLength="5,5" VirtualizingPanel.CacheLengthUnit="Item" AutoGenerateColumns="True" CanUserAddRows="False" IsReadOnly="True"> <behavior:Interaction.Behaviors> <local:SyncScrollBehavior RightTag="RightDG"/> </behavior:Interaction.Behaviors> </DataGrid> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary> using Microsoft.Xaml.Behaviors; using System; using System.Collections; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace WpfApp9 { /// <summary> /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file. /// /// Step 1a) Using this custom control in a XAML file that exists in the current project. /// Add this XmlNamespace attribute to the root element of the markup file where it is /// to be used: /// /// xmlns:MyNamespace="clr-namespace:WpfApp9" /// /// /// Step 1b) Using this custom control in a XAML file that exists in a different project. /// Add this XmlNamespace attribute to the root element of the markup file where it is /// to be used: /// /// xmlns:MyNamespace="clr-namespace:WpfApp9;assembly=WpfApp9" /// /// You will also need to add a project reference from the project where the XAML file lives /// to this project and Rebuild to avoid compilation errors: /// /// Right click on the target project in the Solution Explorer and /// "Add Reference"->"Projects"->[Browse to and select this project] /// /// /// Step 2) /// Go ahead and use your control in the XAML file. /// /// <MyNamespace:DualDatagrid/> /// /// </summary> public class DualDatagrid : Control { private DataGrid _leftDG; private DataGrid _rightDG; static DualDatagrid() { DefaultStyleKeyProperty.OverrideMetadata(typeof(DualDatagrid), new FrameworkPropertyMetadata(typeof(DualDatagrid))); } public override void OnApplyTemplate() { base.OnApplyTemplate(); //invalid _leftDG = FindName("PART_LeftDG") as DataGrid; //valid _leftDG = GetTemplateChild("PART_LeftDG") as DataGrid; _rightDG = GetTemplateChild("PART_RightDG") as DataGrid; } public IEnumerable LeftDGItemsSource { get { return (IEnumerable)GetValue(LeftDGItemsSourceProperty); } set { SetValue(LeftDGItemsSourceProperty, value); } } // Using a DependencyProperty as the backing store for LeftDGItemsSource. This enables animation, styling, binding, etc... public static readonly DependencyProperty LeftDGItemsSourceProperty = DependencyProperty.Register( nameof(LeftDGItemsSource), typeof(IEnumerable), typeof(DualDatagrid), new PropertyMetadata(null)); public IEnumerable RightDGItemsSource { get { return (IEnumerable)GetValue(RightDGItemsSourceProperty); } set { SetValue(RightDGItemsSourceProperty, value); } } // Using a DependencyProperty as the backing store for RightDGItemsSource. This enables animation, styling, binding, etc... public static readonly DependencyProperty RightDGItemsSourceProperty = DependencyProperty.Register( nameof(RightDGItemsSource), typeof(IEnumerable), typeof(DualDatagrid), new PropertyMetadata(null)); } public class SyncScrollBehavior : Behavior<DataGrid> { private ScrollViewer _sourceScroller; private ScrollViewer _targetScroller; private static string leftKey = ""; private static string rightKey = ""; private static Dictionary<string, ScrollViewer> scrollViewerDic = new Dictionary<string, ScrollViewer>(); public SyncScrollBehavior() { } protected override void OnAttached() { base.OnAttached(); AssociatedObject.Loaded += AssociatedObject_Loaded; } private void AssociatedObject_Loaded(object sender, RoutedEventArgs e) { try { if (!string.IsNullOrWhiteSpace(LeftTag)) { leftKey = LeftTag; _sourceScroller = GetScrollViewer(AssociatedObject); if (_sourceScroller != null && !string.IsNullOrWhiteSpace(LeftTag) && !scrollViewerDic.ContainsKey(LeftTag)) { scrollViewerDic[LeftTag] = _sourceScroller; _sourceScroller.ScrollChanged += _sourceScroller_ScrollChanged; } } if (!string.IsNullOrWhiteSpace(RightTag)) { rightKey = RightTag; _targetScroller = GetScrollViewer(AssociatedObject); if (_targetScroller != null && !string.IsNullOrWhiteSpace(RightTag) && !scrollViewerDic.ContainsKey(RightTag)) { scrollViewerDic[RightTag] = _targetScroller; } } } catch (Exception ex) { MessageBox.Show(ex?.Message); } } private void _sourceScroller_ScrollChanged(object sender, ScrollChangedEventArgs e) { if (_sourceScroller != null) { _targetScroller = scrollViewerDic[rightKey]; if (_targetScroller != null) { Application.Current?.Dispatcher.Invoke(() => { _targetScroller.ScrollToHorizontalOffset(_sourceScroller.HorizontalOffset); _targetScroller.ScrollToVerticalOffset(_sourceScroller.VerticalOffset); }, System.Windows.Threading.DispatcherPriority.Background); } } } protected override void OnDetaching() { base.OnDetaching(); if (_sourceScroller != null) { _sourceScroller.ScrollChanged -= _sourceScroller_ScrollChanged; _sourceScroller = null; } if (_targetScroller == null) { _targetScroller = null; } } public string LeftTag { get { return (string)GetValue(LeftTagProperty); } set { SetValue(LeftTagProperty, value); } } // Using a DependencyProperty as the backing store for LeftTag. This enables animation, styling, binding, etc... public static readonly DependencyProperty LeftTagProperty = DependencyProperty.Register( nameof(LeftTag), typeof(string), typeof(SyncScrollBehavior), new PropertyMetadata(null)); public string RightTag { get { return (string)GetValue(RightTagProperty); } set { SetValue(RightTagProperty, value); } } // Using a DependencyProperty as the backing store for RightTag. This enables animation, styling, binding, etc... public static readonly DependencyProperty RightTagProperty = DependencyProperty.Register( nameof(RightTag), typeof(string), typeof(SyncScrollBehavior), new PropertyMetadata(null)); private ScrollViewer GetScrollViewer(DependencyObject depObj) { if (depObj is ScrollViewer sv) { return sv; } int childrenCnt = VisualTreeHelper.GetChildrenCount(depObj); for (int i = 0; i < childrenCnt; i++) { var child = VisualTreeHelper.GetChild(depObj, i); if (child is ScrollViewer scrollViewer) { return scrollViewer; } var result = GetScrollViewer(child); if (result != null) { return result; } } return null; } } } //D:\C\WpfApp9\WpfApp9\MainWindow.xaml <Window x:Class="WpfApp9.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp9" mc:Ignorable="d" Title="MainWindow" WindowState="Maximized"> <Window.DataContext> <local:MainVM/> </Window.DataContext> <Grid> <local:DualDatagrid LeftDGItemsSource="{Binding LeftBksCollection}" RightDGItemsSource="{Binding RightBksCollection}"/> </Grid> </Window> //D:\C\WpfApp9\WpfApp9\MainWindow.xaml.cs using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Net.Http; using System.Net.NetworkInformation; using System.Runtime.CompilerServices; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Xml.Serialization; namespace WpfApp9 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } public class MainVM : INotifyPropertyChanged { private static HttpClient client = new HttpClient(); private static string originUrl = "http://localhost:64660/BookService.svc/getbooks?cnt="; public MainVM() { _ = InitAsync(); } private async Task InitAsync(int leftCnt=1000,int rightCnt=1000) { var leftList = await GetBksList(leftCnt); LeftBksCollection = new ObservableCollection<Book>(leftList); var rightList = await GetBksList(rightCnt); RightBksCollection = new ObservableCollection<Book>(rightList); } private async Task<List<Book>> GetBksList(int cnt=10000) { string url = $"{originUrl}{cnt}"; var xmlStr = await client.GetStringAsync(url); var bks = DeserializeXmlToList(xmlStr); return bks; } private List<Book> DeserializeXmlToList(string xmlStr) { var xmlSerializer = new XmlSerializer(typeof(XmlBook)); using(var reader=new StringReader(xmlStr)) { var xmlBook = (XmlBook)xmlSerializer.Deserialize(reader); return xmlBook.BksList; } } private ObservableCollection<Book> leftBksCollection; public ObservableCollection<Book> LeftBksCollection { get { return leftBksCollection; } set { if(value!=leftBksCollection) { leftBksCollection = value; OnPropertyChanged(); } } } private ObservableCollection<Book> rightBksCollection; public ObservableCollection<Book> RightBksCollection { get { return rightBksCollection; } set { if(value!=rightBksCollection) { rightBksCollection = value; OnPropertyChanged(); } } } public event PropertyChangedEventHandler? PropertyChanged; private void OnPropertyChanged([CallerMemberName] string propName="") { var handler = Volatile.Read(ref PropertyChanged); if(handler==null) { return; } handler.Invoke(this, new PropertyChangedEventArgs(propName)); } } [XmlRoot("ArrayOfBook",Namespace = "http://schemas.datacontract.org/2004/07/WcfService4")] public class XmlBook { [XmlElement("Book")] public List<Book> BksList { get; set; } } public class Book { public long Id { get; set; } public string Name { get; set; } public string ISBN { get; set; } public string Author { get; set; } public string Comment { get; set; } public string CategoryName { get; set; } } }



浙公网安备 33010602011771号