Когда к TreeView потребовалось сделать поиск, я сделал реализацию поиска вне контрола, а потом установил его свойство SelectedItem.
К сожалению у стандартного WPF контрола TreeView свойство SelectedItem только для чтения.
Поэтому мне пришлось ввести в каждый объект отображенный в TreeView свойства IsSelected и IsExpanded и связать эти свойства с соответствующим свойством TreeViewItem. (обычно именно это советуют в инете )
Таким образом нужно только установить у нужного объекта IsSelected, а у всех его предков IsExpanded.
Такая реализация работает прекрасно, но…
В общем поразмыслив немного пришел к такой концепции.
Вот так я осуществил поиск во всех шаблонах. Тут все относительно просто.
Вот логика поиска шаблонов, здесь все как в задуманно за исключением того, что предусмотрен альтернанивный способ задания путей поиска.
Это может быть необходимо с одной стороны потому, что не всегда срабатывает поиск биндингов.
(в случае когда для показа узла используется отдельный контрол со своей достаточно сложной логикой точно не работает).
Ну а с другой стороны иногда надо более точно указывать путь для поиска.
Путь задается через свойство FindPatches в виде строки «TypeName1:Property1,Property2 TypeName2:»
В данном случае для типа TypeName1 будет осуществлен поиск по свойствам Property1,Property2, а по TypeName2 поиск вообще не будет осуществлен.
Вот так выглядит поиск всех элементов содержащих искомый текст.
Вот так выглядит поиск всех элементов содержащих искомый текст.
Функцию GetTreeViewItem(control, L [0]); позволяющую надежно найти узел сделал благодаря
Deranged спасибо ему большое. На основе кода с указанного им сайта.
blogs.msdn.com/b/wpfsdk/archive/2010/02/23/finding-an-object-treeviewitem.aspx
Вот здесь новый код.
myTreeViewWith search.zip
Поиск работает в отдельном потоке.
Возможно сделать расширенный поиск что бы пользователь мог выбирать свойства по которым искать
и… Но это уже украшательства идея вот и работает.
P.S Автор поста, мой брат SergejSh — все вопросы и замечания к нему.
К сожалению у стандартного WPF контрола TreeView свойство SelectedItem только для чтения.
Поэтому мне пришлось ввести в каждый объект отображенный в TreeView свойства IsSelected и IsExpanded и связать эти свойства с соответствующим свойством TreeViewItem. (обычно именно это советуют в инете )
Таким образом нужно только установить у нужного объекта IsSelected, а у всех его предков IsExpanded.
Такая реализация работает прекрасно, но…
- Не очень красиво иметь бизнес объекту IsSelected и IsExpanded. А создавать вью модель для каждого пункта дерева муторно.
- Поиск по дереву должен быть реализован каждый раз в коде, что как минмум требует времени и… морока одним словом.
В общем поразмыслив немного пришел к такой концепции.
- Поиск в объекта осуществлять по части строкового представления объекта в узле дерева.
- На загрузке нужно пройти по всем вышестоящим HierarchicalDataTemplate и DataTemplate получить все биндинги на элементы объекта, а у HierarchicalDataTemplate путь к дочерним элементам.
- При поиске по иерархической коллекции Item Source. Использовать эти данные для поиска элемента.
- Все найденные элементы сохранять вместе путем к узлу.
- Когда надо выделить один из найденных элементов нужно просто выделить нужный узел по запомненному пути.
- Если нужно просто выделить объект (реализация двунаправленного свойства SelectedItem), то нужно найти его в иерархической ItemSource, запомнить путь к нему и потом выделить нужный узел по пути.
Вот так я осуществил поиск во всех шаблонах. Тут все относительно просто.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
templateDescrColl = new TemplateDescriptionCollection();//создал коллекцию описаний биндингов
List<FrameworkElement> parents= Helper.GetAllParent(this);// поулучил спискок предков
FindDataTemplatesResources(this.Resources);// поулучил описания шаблонов описанные ресурсах
foreach (FrameworkElement parent in parents) // потом во всех предках
{
FindDataTemplatesResources(parent.Resources);
}
FindDataTemplatesResources(System.Windows.Application.Current.Resources);// и под конец в приложении
}
Вот логика поиска шаблонов, здесь все как в задуманно за исключением того, что предусмотрен альтернанивный способ задания путей поиска.
Это может быть необходимо с одной стороны потому, что не всегда срабатывает поиск биндингов.
(в случае когда для показа узла используется отдельный контрол со своей достаточно сложной логикой точно не работает).
Ну а с другой стороны иногда надо более точно указывать путь для поиска.
Путь задается через свойство FindPatches в виде строки «TypeName1:Property1,Property2 TypeName2:»
В данном случае для типа TypeName1 будет осуществлен поиск по свойствам Property1,Property2, а по TypeName2 поиск вообще не будет осуществлен.
private void FindDataTemplatesResources(ResourceDictionary Resource)
{
foreach (object xxx in Resource.Values)
{
DataTemplate dataTemplate = xxx as DataTemplate;
if (dataTemplate != null)// если a DataTemplate
{
bool hierarhical=false;
if (!templateDescrColl.ExistType(dataTemplate.DataType as Type, out hierarhical))// если колекция нужного типа
{
TemplateBindingDescr templateBindingDescr = new TemplateBindingDescr();//
HierarchicalDataTemplate hdt = xxx as HierarchicalDataTemplate;// если HierarchicalDataTemplate here
if (hdt != null)
{
Binding ItemsSourceBinding = hdt.ItemsSource as Binding;
string ItemsSourcePath = ItemsSourceBinding.Path.Path;
templateBindingDescr.itemSourcePath = ItemsSourcePath;// путь для поиска во внутренней коллекции
templateBindingDescr.IsHierathical = true;
}
Type tType = dataTemplate.DataType as Type;
templateBindingDescr.TargetType = tType; // type шаблона
if (! String.IsNullOrWhiteSpace (FindPatches) && FindPatches.Contains (tType.Name + ":")) //если задан альтернативный поиск для этого типа
{
Match match = Regex.Match (FindPatches, tType.Name + ":([^:]*)");.
if (match.Success)
{
string re = match.Groups [1]. Value;
string [] pathes = re.Split (',');
templateBindingDescr.BindingPathes.AddRange (pathes);
}
}
else //Визуализирую шаблон и ищу все его биндинги
{
FrameworkElement frameworkElement = dataTemplate.LoadContent () as FrameworkElement; // загрузил шаблон
List <FrameworkElement> DependencyObjects = Helper.GetAllVisualChildren (frameworkElement);//нашел в нем все элементы
if (DependencyObjects!= null)
{
foreach (FrameworkElement dependencyObject in DependencyObjects)
{
//получил биндинги на текст
BindingExpression BE = System.Windows.Data.BindingOperations.GetBindingExpression (dependencyObject, TextBlock.TextProperty);
if (BE!= null)
{
string path = BE.ParentBinding.Path.Path;
templateBindingDescr.BindingPathes.Add (path);
}
}
}
}
templateDescrColl.Add (templateBindingDescr); // добавил в коллекцию
}
}
}
}
Вот так выглядит поиск всех элементов содержащих искомый текст.
/// <summary>
/// Иерархический поиск всех узлов в методом поиска по указанным путям если в получившимся объекте после приведения его к строке есть FindText
/// Работает гораздо быстрее
/// </summary>
/// <param name="control"></param>
/// <param name="FindText"></param>
/// <returns></returns>
public List<NamedObject> FindObjectByPropertyNames(IEnumerable itemsSource, List<int> path)
{
List<NamedObject> ret = new List<NamedObject>();//Создали список куда будем епомещать результаты поиска
int i = 0;
path.Add(i);//добавили в путь этот уровень иерархии
foreach (object Item in itemsSource) //Перебираем все элементы коллекции
{
path[path.Count - 1] = i;//по ходу дела устанавливаем текущий путь
TemplateBindingDescr desctiption= templateDescrColl.Get(Item.GetType());//ищем в коллекции описаний подходящий по типу шаблон
string subItemsPath=desctiption.itemSourcePath;//получили путь по которому искать детей
string ObjectText=String.Empty;//здесь мы будем хранить текст для представления объекта в комбобоксе поиска
bool ok=false;
foreach (string findPath in desctiption.BindingPathes)// прошлись по путям указанным в биндинге данного узла
{
object ItemVal = Item.GetObjectSubItem(findPath); //Получили по пути объект
if (ItemVal != null )//Если он не нулл
{
string ItemText = ItemVal.ToString().ToUpper();//получаем текстовое представление объекта
ObjectText += ItemText + " ";
if (ItemText.Contains(FindText.ToUpper()))
{
ok = true; //объект содержит искомую строку пометим его
}
}
}
if (ok)//если объект содержал найденные значения
{
List<int> path2 = new List<int>();
path2.AddRange(path);
ret.Add(new NamedObject() { Name = ObjectText, Item = Item, Path = path2 });// поместим его в коллекцию
}
if (subItemsPath != null)//А теперь пройдем по его детям
{
IEnumerable subItems=Item.GetObjectSubItem(subItemsPath) as IEnumerable;
if (subItems != null)
{
List<int> path1 = new List<int>();
path1.AddRange(path);
ret.AddRange(FindObjectByPropertyNames(subItems, path1));
}
}
i++;
}
return ret;
}
Вот так выглядит поиск всех элементов содержащих искомый текст.
int failCount; //количество падений поиска
//выделение узла по пути
void SelectNodeByPath(ItemsControl control, IEnumerable<int> path)
{
List<int>L= path.ToList<int>();
if (L.Count == 0) { return; }
if (control.Items.Count > L[0])
{
do //тут вот чуть подождем пока сонтрол сгенерит своих детей
{
control.UpdateLayout();
}
while (control.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated);
TreeViewItem dObject = control.ItemContainerGenerator.ContainerFromIndex(L[0]) as TreeViewItem;
if (dObject == null)//не нашли сразу применим специально обученный метод
{
dObject = GetTreeViewItem(control, L [0]);
}
if (dObject == null)
{
MessageBox.Show("Не могу найти );
}
else //нашли родимого
{
L.RemoveAt(0);
if (L.Count == 0) //последнее звено в дереве его нуждно выделить
{
dObject.IsSelected = true;
dObject.BringIntoView();
}
else //Это только промежуточный узел и его надо раскрыть
{
dObject.IsExpanded = true;
SelectNodeByPath(dObject, L); //и искать дальше
}
}
}
else
{
MessageBox.Show("В контроле"+control.Items.Count+" Задан поиск > "+L[0]);
}
}
Функцию GetTreeViewItem(control, L [0]); позволяющую надежно найти узел сделал благодаря
Deranged спасибо ему большое. На основе кода с указанного им сайта.
blogs.msdn.com/b/wpfsdk/archive/2010/02/23/finding-an-object-treeviewitem.aspx
Вот здесь новый код.
myTreeViewWith search.zip
Поиск работает в отдельном потоке.
Возможно сделать расширенный поиск что бы пользователь мог выбирать свойства по которым искать
и… Но это уже украшательства идея вот и работает.
P.S Автор поста, мой брат SergejSh — все вопросы и замечания к нему.