Форум программистов, компьютерный форум, киберфорум
C#: WPF, UWP и Silverlight
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.52/25: Рейтинг темы: голосов - 25, средняя оценка - 4.52
31 / 17 / 5
Регистрация: 18.07.2013
Сообщений: 220
WPF

Поиск дочернего элемента по имени в DependencyObject

01.05.2019, 06:05. Показов 5617. Ответов 27

Студворк — интернет-сервис помощи студентам
Разобрался как добавить вкладку TabItem в TabControl с помощью XAML разметки, а не создавая потом в ней все элементы кодом.

C#
1
2
DataTemplate contentCatalogFundTemplate = (DataTemplate)FindResource("FundCatalog");
tab_item.Content  = contentCatalogFundTemplate.LoadContent();
LoadContent() возвращает DependencyObject.
Можно получать доступ к нужным полям, например так

C#
1
2
3
TabItem tab_item = (TabItem)openDocuments.Items[openDocuments.SelectedIndex];
StackPanel searchMenu = (StackPanel)((StackPanel)tab_item.Content).Children[0];
string keyPhrase = ((TextBox)searchMenu.Children[5]).Text;
Но так логика слишком зависима от представления в интерфейсе.

TabItem и другие элементы наследует от DependencyObject. Возможно написать функцию принимающую DependencyObject корневого элемента, имя элемента в разметки и возвращающую искомый элемент, который может находится в глубине дерева элементов, а не быть первым потомком.

Добавлено через 9 минут
Ещё почему DependencyObject?

Иногда и в загруженном шаблоне надо пометь значение некоторого поля перед тем как его назначить элементу.

Добавлено через 4 часа 1 минуту
Работает это

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
        private DependencyObject FindElement(DependencyObject root, string name)
        {
            if (FindElementhelper(root, name, out DependencyObject found)) return found;
            else throw new NotImplementedException(); 
        }
 
        private bool FindElementHelper(DependencyObject root, string name, out DependencyObject found)
        {
            found = root;
            if (root is FrameworkElement && ((FrameworkElement)root).Name == name) return true;
            if (root is Panel)
            {
                foreach (UIElement children in ((Panel)root).Children)
                {
                    if (FindElementhelper(children, name, out found)) return true;
                }
                return false;
            }
            return false;
        }
Хотя это не полный обход. Не учитываются ContentControl и ContentElement. Но мне этого достаточно для текущих шаблонов. (Тем более, что при поиске например в TabControl встаёт ещё вопрос, а искать во всех вкладках или в выбранной).
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
01.05.2019, 06:05
Ответы с готовыми решениями:

Simple_html_dom поиск дочернего элемента
find('div ') этот код находит DIV с классом rate-shop, мне надо найти элементы h3 - дочерние для этого дива. Пробывал и так и эдак -...

Поиск последнего дочернего элемента и добавление следующего в xml-документе
По наработкам: для всех элементов item вставить schema, если последний из дочерних элементов item-a = title. Xml вида: <?xml...

XML: по имени элемента (тега) и/или имени атрибута у элемента (у тега) выдать соответствующее значение
Есть некий XML файл. . <?xml version="1.0" encoding="windows-1251"?> <ТКвит xmlns="urn:cbr-ru:tk:v1.1" ...

27
31 / 17 / 5
Регистрация: 18.07.2013
Сообщений: 220
02.05.2019, 07:54  [ТС]
C#
1
2
3
4
if (root is GroupBox)
            {
                return FindElementHelper((DependencyObject)((GroupBox)root).Content, name, out found);
            }
Добавлено через 4 часа 44 минуты
Нашёл где готовая функция. Ожидал, что она должна быть.
C#
1
((FrameworkElement)d).FindName("name");
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16137 / 11261 / 2888
Регистрация: 21.04.2018
Сообщений: 33,096
Записей в блоге: 2
02.05.2019, 13:01
Archi0, а чем вызваны эти "танцы с бубном"?
Вы описываете то, что Вы делаете, как сами себе ставите задачи и их решаете.
А цель всего этого? Что Вы хотите в целом реализовать?
Какой-то навороченный UserControl?
0
31 / 17 / 5
Регистрация: 18.07.2013
Сообщений: 220
02.05.2019, 15:11  [ТС]
TabControl в котором открываются вкладки в зависимости от того что выбрали в меню.
В этих вкладках отображаются каталоги или карточка элемента каталога.
Миниатюры
Поиск дочернего элемента по имени в DependencyObject   Поиск дочернего элемента по имени в DependencyObject   Поиск дочернего элемента по имени в DependencyObject  

0
31 / 17 / 5
Регистрация: 18.07.2013
Сообщений: 220
02.05.2019, 15:17  [ТС]
Выше макет, который выполнял в отдельном проекте.
Контрольный пример из программы в которую импортировал макеты вкладок в DataTempate
Миниатюры
Поиск дочернего элемента по имени в DependencyObject   Поиск дочернего элемента по имени в DependencyObject   Поиск дочернего элемента по имени в DependencyObject  

0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16137 / 11261 / 2888
Регистрация: 21.04.2018
Сообщений: 33,096
Записей в блоге: 2
02.05.2019, 15:25
Цитата Сообщение от Archi0 Посмотреть сообщение
TabControl в котором открываются вкладки в зависимости от того что выбрали в меню.
В этих вкладках отображаются каталоги или карточка элемента каталога.
Но для этого вполне хватает типовых подходов с определением шаблонов данных в XAML. Конечно, если нормальная MVVM реализация.

А для чего Вы делаете все эти поиски в UI элементах?
Конечно, такие методы можно использовать в рамках View. Допустим, как я писал, при создании навороченного UserControl.

Но в данном случае зачем? Тем более, как я понял, у Вас обрабатывается в одном коде не только View часть, но и данные. А это, вообще, недопустимо!
0
31 / 17 / 5
Регистрация: 18.07.2013
Сообщений: 220
02.05.2019, 15:36  [ТС]
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void UpdateFundsGrid()
        {
            TabItem tab_item = (TabItem)openDocuments.Items[openDocuments.SelectedIndex];
            Grid fundsGrid = (Grid)((FrameworkElement)tab_item.Content).FindName("fundsGrid");
            ClearFundsGrid(fundsGrid);
            StackPanel navigationBar = (StackPanel)((FrameworkElement)tab_item.Content).FindName("navigationBar");
            StackPanel searchMenu = (StackPanel)((FrameworkElement)tab_item.Content).FindName("searchMenu");
            string pageString = ((TextBox)navigationBar.FindName("pageBox")).Text;
            Int32.TryParse(pageString, out int page);
            string numberFund = ((TextBox)searchMenu.FindName("numberFund")).Text;
            string yearFund = ((TextBox)searchMenu.FindName("yearFund")).Text;
            string keyPhrase = ((TextBox)searchMenu.FindName("keyPhrase")).Text;
            int? n_year;
            if (int.TryParse(yearFund, out int year)) n_year = year; else n_year = null;
            string[] keyWords = GetKeyWords(keyPhrase);
            Fund[] funds = Fund.FindFund((page - 1) * 20, numberFund, n_year, keyWords).ToArray();
            FundsToGrid(funds, fundsGrid);
        }
где Fund.FindFund возвращает из базы результат запроса
FundsToGrid заполняет Grid
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16137 / 11261 / 2888
Регистрация: 21.04.2018
Сообщений: 33,096
Записей в блоге: 2
02.05.2019, 15:42
Цитата Сообщение от Archi0 Посмотреть сообщение
где Fund.FindFund возвращает из базы результат запроса
FundsToGrid заполняет Grid
Как я понял, Вы далеки от понимания MVVM.

Дело Ваше, но имейте ввиду, что WPF очень сильно "заточен" под MVVM. И использование WPF вне этого паттерна катастрофически усложняет программирование. Далее самых простых реализаций Вы не сможете пойти.

Мой совет, притормозите чуток. Разберитесь с MVVM. И не используйте WPF без этого паттерна.
Иначе Вы сами себе создаёте огромные трудности, которые в конце концов станут непреодолимыми.
1
Эксперт .NET
 Аватар для Casper-SC
4434 / 2094 / 404
Регистрация: 27.03.2010
Сообщений: 5,657
Записей в блоге: 1
02.05.2019, 16:16
Archi0, посмотрел код. И представил какой будет дальше ад. Я бы прислушался к Элд Хасп.

Добавлено через 3 минуты
Я могу даже набросать простой пример. Сразу скажу, не пишите руками всякие классы ViewModelBase и т.д. Используйте готовое. Я использую в примерах GalaSoft.MvvmLight, но есть и другие библиотеки. Например, здесь на форуме есть мной написанный пример, как используя подход из библиотеки Prism открывать и закрывать окна в рамках MVVM.

Добавлено через 3 минуты
Вот этот пример: Архитектура MVVM в многооконном приложении

Добавлено через 21 минуту
Archi0, я хочу сказать вот что. С подходом MVVM у вас будет примерно следующее:
Будет класс, например, MainViewModel, который будет содержать в себе коллекцию других вью моделей, каждая из которых будет представлять вкладку.
Например

C#
1
2
3
4
5
6
7
8
9
10
11
12
    /// <summary>
    /// Модель представления окна.
    /// </summary>
    public class MainViewModel : ViewModelBase
    {
        public ObservableCollection<TabViewModelBase> Tabs { get; }
 
        public MainViewModel()
        {
            Tabs = new ObservableCollection<TabViewModelBase>();
        }
    }
C#
1
2
3
4
5
6
7
8
    /// <summary>
    /// Базовый класс для вкладки.
    /// </summary>
    public abstract class TabViewModelBase : ViewModelBase
    {
        /// <summary> Заголовок. </summary>
        public string Title { get; set; }
    }
C#
1
2
3
4
5
6
7
8
9
10
11
12
    /// <summary>
    /// Модель представления фондов.
    /// </summary>
    public class FundsViewModel : TabViewModelBase
    {
        public ObservableCollection<FundViewModel> Funds { get; }
 
        public FundsVeiwModel()
        {
            Funds = new ObservableCollection<FundViewModel>();
        }
    }
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
    /// <summary>
    /// Модель представления строки на вкладке "Фонды" (<see cref="FundViewModel"/>).
    /// </summary>
    public class FundViewModel : TabViewModelBase
    {
        public int Number { get; set; }
 
        public string Name { get; set; }
 
        // ...
        // Другие свойства
        // ...
    }
В итоге в каждой вкладке будет какой-нибудь ListView или DataGrid, и будет заполняться UI автоматически после добавления данных, например, в ObservableCollection<FundVeiwModel>.
То есть не придётся работать с UI элементами, а будет нужно лишь работать с вью моделями, не нужно будет получать данные поиском UI элементов и вытягивания с них текста, UI будет обновляться автоматически. В общем, если нужно, могу набросать пример.

Добавлено через 3 минуты
Далее нужно будет объявить DataTemplate для каждого типа вкладки и DataTemplateSelector, который будет подставлять нужный DataTemplate взависимости от того, какой тип вью модели у каждой конкретной вкладки. Для FundsVeiwModel свой DataTemplate, для другого типа вкладки другой. Таким подходом вы сильно упрощаете себе работу и отбрасываете огромный пласт ошибок и лишней работы.
2
31 / 17 / 5
Регистрация: 18.07.2013
Сообщений: 220
02.05.2019, 17:16  [ТС]
Если я внутри DataTemplate напишу
XML
1
2
3
<Grid ShowGridLines="True" Name="fundsGrid">
      <DataGrid ItemsSource="{Binding Funds}"></DataGrid>
</Grid>
Как мне к этому Funds обратиться.

этот код не повлиял на Grid
C#
1
2
3
4
5
6
7
8
9
10
11
private ObservableCollection<ViewFund> Funds;
 
Funds = new ObservableCollection<ViewFund>
{
                new ViewFund
                {
                    FundNumber = new TextBlock(){ Text="1"},
                    FundNames = new TextBlock(){ Text="2"},
                    FundYears = new TextBlock(){ Text="3"}
                }
};
0
Эксперт .NET
 Аватар для Casper-SC
4434 / 2094 / 404
Регистрация: 27.03.2010
Сообщений: 5,657
Записей в блоге: 1
02.05.2019, 17:25
Archi0, во ViewModel не должно быть UI элементов. UI элементы должны быть во View. Сейчас покажу.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16137 / 11261 / 2888
Регистрация: 21.04.2018
Сообщений: 33,096
Записей в блоге: 2
02.05.2019, 17:30
Цитата Сообщение от Archi0 Посмотреть сообщение
этот код не повлиял на Grid
Конечно, он не повлияет!
Хоть самые азы, основы прочитайте!

Привязка может быть только к ПУБЛИЧНЫМ СВОЙСТВАМ. И они должны поддерживать INPC!

Должно быть так (пример возможной реализации)
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class MyClass :  INotifyPropertyChanged
{
        /// <summary>Событие для извещения об изменения свойства</summary>
        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>Метод для вызова события извещения об изменении свойства</summary>
        /// <param name="prop">Изменившееся свойство</param>
        public void OnPropertyChanged([CallerMemberName]string prop = "")
            =>PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
 
        private ObservableCollection<ViewFund> funds;
        private ObservableCollection<ViewFund> Funds {get => funds; set {funds = value; OnPropertyChanged();}}
 
 
         void Metod()
        {
             Funds = new ObservableCollection<ViewFund>()
              {
                       new ViewFund
                      {
                            FundNumber = new TextBlock(){ Text="1"},
                            FundNames = new TextBlock(){ Text="2"},
                            FundYears = new TextBlock(){ Text="3"}
                      }
              };
         }
}
Пишу здесь без Студии - могут быть мелкие ошибки
1
Эксперт .NET
 Аватар для Casper-SC
4434 / 2094 / 404
Регистрация: 27.03.2010
Сообщений: 5,657
Записей в блоге: 1
02.05.2019, 19:34
Вот потратил 2 часа. Набросал проект (по ссылке можно скачать исходники из Git репозитория).

Здесь выложу лишь часть кода. По ссылке можно получить проект, я что-то исправлю, запушу, вам уже не нужно скачивать проект снова, вы можете выполнить команду git pull и получить все изменения. Это сильно ускоряет разработку. Сам проект прикрепил.

По Git на YouTube есть много видео.
Рекомендую скачать SmartGit или SourceTree.

Дальше можно брать проект за основу и обсуждать, что как делается.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
namespace TemplatedTabControl.ViewModel
{
    /// <summary>
    /// Модель представления окна.
    /// </summary>
    public class MainViewModel : TabHostViewModelBase
    {
        private string _info;
 
        public string Info
        {
            get { return _info; }
            set { Set(ref _info, value); }
        }
 
        public MainViewModel()
        {
            Tabs.Add(new FundsViewModel()
            {
                Title = "Фонды",
                Items =
                {
                    new FundViewModel() { Number = 1, Name = "Fund name 1" },
                    new FundViewModel() { Number = 2, Name = "Fund name 2" },
                }
            });
            Tabs.Add(new InventoriesViewModel()
            {
                Title = "Опись",
                Items =
                {
                    new InventoryViewModel() { Index = 1, Title = "Inventory title 1" },
                    new InventoryViewModel() { Index = 2, Title = "Inventory title 2" },
                }
            });
 
            TabRemoved += OnTabRemoved;
        }
 
        private void OnTabRemoved(TabHostViewModelBase sender, TabItemViewModelBase item)
        {
            Info = $"Вкладка \"{item.Title}\" была удалена.";
        }
    }
}
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
 
namespace TemplatedTabControl.ViewModel
{
    /// <summary>
    /// Базовый класс для вкладки.
    /// </summary>
    public abstract class TabItemViewModelBase : ViewModelBase
    {
        /// <summary> Загловок. </summary>
        public string Title { get; set; }
 
        /// <summary> Закрыть вкладку. </summary>
        public RelayCommand CloseCommand { get; }
 
        /// <summary> Запрошено закрытие вкладки. </summary>
        public event TypedEventHandler<TabItemViewModelBase> CloseRequested;
 
        protected TabItemViewModelBase()
        {
            CloseCommand = new RelayCommand(() => { CloseRequested?.Invoke(this); });
        }
    }
}
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using GalaSoft.MvvmLight;
 
namespace TemplatedTabControl.ViewModel
{
    /// <summary>
    /// Модель представления TabControl
    /// </summary>
    public class TabHostViewModelBase : ViewModelBase
    {
        /// <summary> Вкладки. </summary>
        public ObservableCollection<TabItemViewModelBase> Tabs { get; }
 
        public event TypedEventHandler<TabHostViewModelBase, TabItemViewModelBase> TabRemoved;
 
        public TabHostViewModelBase()
        {
            Tabs = new ObservableCollection<TabItemViewModelBase>();
            Tabs.CollectionChanged += TabsOnCollectionChanged;
        }
 
        private void TabsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (TabItemViewModelBase item in e.NewItems)
                {
                    item.CloseRequested += TabItemOnCloseRequested;
                }
            }
 
            if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (TabItemViewModelBase item in e.OldItems)
                {
                    item.CloseRequested -= TabItemOnCloseRequested;
                }
            }
        }
 
        private void TabItemOnCloseRequested(TabItemViewModelBase item)
        {
            Tabs.Remove(item);
            TabRemoved?.Invoke(this, item);
        }
    }
}
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System.Collections.ObjectModel;
using GalaSoft.MvvmLight;
 
namespace TemplatedTabControl.ViewModel
{
    /// <summary>
    /// Модель представления содержимого вкладки.
    /// </summary>
    /// <typeparam name="T">Тип содержимого коллекции.</typeparam>
    public class TabItemCollectionViewModelBase<T> : TabItemViewModelBase
        where T : ViewModelBase
    {
        public ObservableCollection<T> Items { get; }
 
        public TabItemCollectionViewModelBase()
        {
            Items = new ObservableCollection<T>();
        }
    }
}
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
        <DataTemplate x:Key="FundsViewModelDataTemplate" DataType="viewModel:FundsViewModel">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="22" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <TextBlock
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Text="FundsViewModel" />
                <DataGrid
                    Grid.Row="1"
                    Margin="6,3,6,0"
                    AutoGenerateColumns="False"
                    Effect="{x:Null}"
                    ItemsSource="{Binding Items, IsAsync=False}"
                    Language="RU"
                    RowDetailsVisibilityMode="Collapsed"
                    ScrollViewer.CanContentScroll="True"
                    ScrollViewer.IsDeferredScrollingEnabled="False">
 
                    <DataGrid.Columns>
                        <DataGridTextColumn
                            Binding="{Binding Number}"
                            ClipboardContentBinding="{x:Null}"
                            Header="№" />
                        <DataGridTextColumn
                            Width="*"
                            Binding="{Binding Name}"
                            ClipboardContentBinding="{x:Null}"
                            Header="Название" />
                    </DataGrid.Columns>
                </DataGrid>
            </Grid>
        </DataTemplate>
Миниатюры
Поиск дочернего элемента по имени в DependencyObject  
Вложения
Тип файла: zip TemplatedTabControl.zip (80.7 Кб, 0 просмотров)
1
Эксперт .NET
 Аватар для Casper-SC
4434 / 2094 / 404
Регистрация: 27.03.2010
Сообщений: 5,657
Записей в блоге: 1
02.05.2019, 19:39
В проекте нужно восстановить NuGet пакеты, так как я удалил из проекта всё лишнее, кроме кода (его можно тоже было удалить и восстановить командой git reset --hard).
0
31 / 17 / 5
Регистрация: 18.07.2013
Сообщений: 220
03.05.2019, 05:08  [ТС]
Как обработчик нажатия кнопки в меню может получить экземпляр TabHostViewModelBase, чтобы добавить в него вкладку?
0
Эксперт .NET
 Аватар для Casper-SC
4434 / 2094 / 404
Регистрация: 27.03.2010
Сообщений: 5,657
Записей в блоге: 1
03.05.2019, 11:32
Из UI можно привязываться используя биндинги (Binding). Биндинг видит данные для привязки, которые находятся в свойстве DataContext. Мы в окне указали, что в DataContext находится экземпляр MainViewModel, как раз то, что нам и нужно:
XML
1
2
3
4
5
<Window
    x:Class="TemplatedTabControl.Views.MainWindow"
    DataContext="{Binding Source={StaticResource Locator}, Path=Main}">
 
</Window>
Это значит, что все биндинги в окне имеют к нему доступ.

Вот так можно привязаться к команде:
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<Window
    x:Class="TemplatedTabControl.Views.MainWindow"
    DataContext="{Binding Source={StaticResource Locator}, Path=Main}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="1*" />
            <RowDefinition Height="22" />
        </Grid.RowDefinitions>
 
        <Menu Grid.Row="0">
            <MenuItem Header="Вкладки">
                <MenuItem Command="{Binding AddTabCommand}" Header="Добавить" />
            </MenuItem>
        </Menu>
 
        <!-- Для краткости -->
        <TabControl
            Grid.Row="1" />
 
        <!-- Для краткости -->
        <TextBlock
            Grid.Row="2" />
 
    </Grid>
</Window>
Удалил из классов всё лишнее, чтобы показать добавленное в него
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    public abstract class TabHostViewModelBase : ViewModelBase
    {
        public ICommand AddTabCommand { get; }
 
        protected TabHostViewModelBase()
        {
            AddTabCommand = new RelayCommand(() => { Tabs.Add(CreateNewTab()); });
        }
 
        /// <summary>
        /// В производном классе определяет логику добавления новой вкладки.
        /// </summary>
        protected abstract TabItemViewModelBase CreateNewTab();
    }
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    /// <summary>
    /// Модель представления окна.
    /// </summary>
    public class MainViewModel : TabHostViewModelBase
    {
        /// <summary>
        /// Определяет логику добавления новой вкладки.
        /// </summary>
        protected override TabItemViewModelBase CreateNewTab()
        {
            // Здесь нужно написать логику, по которой определять, какой тип вкладки добавить.
            return new FundsViewModel() { Title = "Новая" };
        }
    }
Я запушил эти изменения, их можно получить из репозитория (git pull).

Добавлено через 3 минуты
Поясню в чём моя логика. В базовый класс мы добавляем только общий функционал без конкретики, который в теории подойдёт для переиспользования в другой программе, например. Ведь по идее мы должны уметь добавлять вкладки, вот я в базовом классе создал такую команду. А вот уже в конкретной программе мы определяем конкретную логику добавления в классе наследнике MainViewModel, где и прописываем все условия и проверки перед добавлением, вот поэтому существует абстрактный метод CreateNewTab.
2
31 / 17 / 5
Регистрация: 18.07.2013
Сообщений: 220
04.05.2019, 04:26  [ТС]
Пункты меню Открыть сначала спрашивают у пользователя, что открыть в дополнительном окне.
Пункты меню Каталог открывают разные вкладки.

Чтобы написать логику добавления вкладки тогда нужен параметр команды


C#
1
2
3
4
5
protected override TabItemViewModelBase CreateNewTab(object CommandParameter)
        {
            AddTabCommandParametr parametr = (AddTabCommandParametr)CommandParameter;
            return new FundsViewModel() { Title = "новая"};
        }
Класс для параметра
Кликните здесь для просмотра всего текста

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public partial class AddTabCommandParametr : DependencyObject
    {
        public static readonly DependencyProperty CatalogProperty;
        public static readonly DependencyProperty NumberFundProperty;
        public static readonly DependencyProperty NumberListProperty;
        public static readonly DependencyProperty NumberCasesProperty;
        public static readonly DependencyProperty NumberResolutionProperty;
        public static readonly DependencyProperty YearResolutionProperty;
 
        static AddTabCommandParametr()
        {
            CatalogProperty = DependencyProperty.Register("Catalog", typeof(TypeCatalog), typeof(AddTabCommandParametr));
            NumberFundProperty = DependencyProperty.Register("NumberFund", typeof(string), typeof(AddTabCommandParametr));
            NumberListProperty = DependencyProperty.Register("NumberList", typeof(string), typeof(AddTabCommandParametr));
            NumberCasesProperty = DependencyProperty.Register("NumberCases", typeof(string), typeof(AddTabCommandParametr));
            NumberResolutionProperty = DependencyProperty.Register("NumberResolution", typeof(string), typeof(AddTabCommandParametr));
            YearResolutionProperty = DependencyProperty.Register("YearResolution", typeof(int), typeof(AddTabCommandParametr));
        }
 
        public TypeCatalog Catalog
        {
            get { return (TypeCatalog)GetValue(CatalogProperty); }
            set { SetValue(CatalogProperty, value); }
        }
        public string NumberFund
        {
            get { return (string)GetValue(NumberFundProperty); }
            set { SetValue(NumberFundProperty, value); }
        }
        public string NumberList
        {
            get { return (string)GetValue(NumberListProperty); }
            set { SetValue(NumberListProperty, value); }
        }
        public string NumberCases
        {
            get { return (string)GetValue(NumberCasesProperty); }
            set { SetValue(NumberCasesProperty, value); }
        }
        public string NumberResolution
        {
            get { return (string)GetValue(NumberResolutionProperty); }
            set { SetValue(NumberResolutionProperty, value); }
        }
        public int YearResolution
        {
            get { return (int)GetValue(YearResolutionProperty); }
            set { SetValue(YearResolutionProperty, value); }
        }
    }
 
    public enum TypeCatalog { None, Funds, Lists, Cases, Documents, Resolution, Enterprises}


Изменения в TabHostViewModelBase

C#
1
2
3
4
5
6
7
public abstract object CommandParameter { get; }
protected TabHostViewModelBase()
        {
            Tabs = new ObservableCollection<TabItemViewModelBase>();
            Tabs.CollectionChanged += TabsOnCollectionChanged;
            AddTabCommand = new RelayCommand(() => { Tabs.Add(CreateNewTab(CommandParameter)); });
        }
Изображения
 
0
31 / 17 / 5
Регистрация: 18.07.2013
Сообщений: 220
04.05.2019, 05:18  [ТС]
Как сформировать нужный экземпляр AddTabCommandParametr
при нажатии опции меню свойство Catalog
при нажатии "Открыть" свойства с номерами.
0
Эксперт .NET
 Аватар для Casper-SC
4434 / 2094 / 404
Регистрация: 27.03.2010
Сообщений: 5,657
Записей в блоге: 1
05.05.2019, 03:38
Почитайте статью, если не совсем понятно, что к чему в MVVM, но там переведены термины Model, View, View model и может тяжело читаться, но статья неплохая: Приложения WPF с шаблоном проектирования модель-представление-модель представления

Коротко ответить не получится. Чтобы работало, так как вы хотели, пришлось посидеть нехило так кода написать и много мелочей изменить.

Все изменения можно получить из репозитория. Здесь выложу лишь малую часть. Изменений очень много. Потратил несколько часов (примерно 3).

Открывается окно, вводим параметры, закрывается окно, появляется вкладка и в фоне "грузит данные из БД" (Task.Delay). Когда данные загружены, название вкладки меняется и она становится доступна для манипуляций (IsEnabled).

Неполная разметка, удалил всё лишнее, чтобы показать, что изменилось:
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<Window
    x:Class="TemplatedTabControl.Views.MainWindow"
    DataContext="{Binding Source={StaticResource Locator}, Path=Main}"
    WindowStartupLocation="CenterScreen"
    mc:Ignorable="d">
    <Window.Resources>
 
        <DataTemplate x:Key="OpenFundsDataTemplate" DataType="{x:Type entityParams:FundsParameterViewModel}">
            <StackPanel
                Width="200"
                Height="150"
                Margin="6,12,6,12">
                <TextBlock Text="Номер фонда" />
                <TextBox Text="{Binding Number, Mode=TwoWay}" />
            </StackPanel>
        </DataTemplate>
 
        <DataTemplate x:Key="OpenListsDataTemplate" DataType="{x:Type entityParams:ListsParameterViewModel}">
            <StackPanel
                Width="200"
                Height="150"
                Margin="6,12,6,12">
                <TextBlock Text="Номер описи" />
                <TextBox Text="{Binding Number, Mode=TwoWay}" />
            </StackPanel>
        </DataTemplate>
 
        <model:TypeCatalog x:Key="Funds">Funds</model:TypeCatalog>
        <model:TypeCatalog x:Key="Lists">Lists</model:TypeCatalog>
        <model:TypeCatalog x:Key="Cases">Cases</model:TypeCatalog>
        <model:TypeCatalog x:Key="Documents">Documents</model:TypeCatalog>
        <model:TypeCatalog x:Key="Resolution">Resolution</model:TypeCatalog>
        <model:TypeCatalog x:Key="Enterprises">Enterprises</model:TypeCatalog>
 
    </Window.Resources>
 
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="1*" />
            <RowDefinition Height="22" />
        </Grid.RowDefinitions>
 
        <Menu Grid.Row="0">
 
            <i:Interaction.Triggers>
 
                <i:EventTrigger EventName="Raised" SourceObject="{Binding OpenFundsDialogRequest}">
                    <triggerActions:OpenEntityDialogAction ContentDataTemplate="{StaticResource OpenFundsDataTemplate}" />
                </i:EventTrigger>
 
                <i:EventTrigger EventName="Raised" SourceObject="{Binding OpenListsDialogRequest}">
                    <triggerActions:OpenEntityDialogAction ContentDataTemplate="{StaticResource OpenListsDataTemplate}" />
                </i:EventTrigger>
 
            </i:Interaction.Triggers>
 
            <MenuItem Header="Открыть">
                <MenuItem
                    Command="{Binding AddTabCommand}"
                    CommandParameter="{StaticResource Funds}"
                    Header="Фонд" />
                <MenuItem
                    Command="{Binding AddTabCommand}"
                    CommandParameter="{StaticResource Lists}"
                    Header="Опись" />
            </MenuItem>
        </Menu>
 
        <TabControl
            Grid.Row="1" />
 
        <TextBlock
            Grid.Row="2"/>
 
    </Grid>
 
</Window>
Здесь хорошо бы уйти от switch case, но я пока забил
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
using System;
using NLog;
using TemplatedTabControl.Misc.Extensions;
using TemplatedTabControl.Model;
using TemplatedTabControl.Prism.Interactivity.InteractionRequest;
using TemplatedTabControl.ViewModel.OpenEntityParameters;
using TemplatedTabControl.ViewModel.OpenEntityParameters.Common;
using TemplatedTabControl.ViewModel.Tabs.Funds;
using TemplatedTabControl.ViewModel.Tabs.Lists;
 
namespace TemplatedTabControl.ViewModel
{
    /// <summary>
    /// Модель представления окна.
    /// </summary>
    public class MainViewModel : TabHostViewModelBase<TypeCatalog>
    {
        private readonly Logger _logger = LogManager.GetCurrentClassLogger();
 
        private string _info;
 
        public string Info
        {
            get { return _info; }
            set { Set(ref _info, value); }
        }
 
        /// <summary>
        /// Запрос взаимодействия для показа окна параметров Фонда.
        /// </summary>
        public InteractionRequest<OpenEntityParameter> OpenFundsDialogRequest { get; }
        /// <summary>
        /// Запрос взаимодействия для показа окна параметров Описи.
        /// </summary>
        public InteractionRequest<OpenEntityParameter> OpenListsDialogRequest { get; }
 
        public MainViewModel()
        {
            OpenFundsDialogRequest = new InteractionRequest<OpenEntityParameter>();
            OpenListsDialogRequest = new InteractionRequest<OpenEntityParameter>();
 
            Tabs.Add(new FundsViewModel
            {
                Title = "Фонды",
                IsInitialized = true,
                Items =
                {
                    new FundViewModel { Number = 1, Name = "Fund name 1" },
                    new FundViewModel { Number = 2, Name = "Fund name 2" },
                }
            });
            Tabs.Add(new ListsViewModel
            {
                Title = "Опись",
                IsInitialized = true,
                Items =
                {
                    new ListViewModel { Index = 1, Title = "Inventory title 1" },
                    new ListViewModel { Index = 2, Title = "Inventory title 2" },
                }
            });
            SelectedIndex = Tabs.Count - 1;
 
            TabRemoved += OnTabRemoved;
        }
 
        /// <summary>
        /// Определяет логику добавления новой вкладки.
        /// </summary>
        protected override void CreateNewTab(TypeCatalog type, Action<TabItemViewModelBase> addNewTab)
        {
            OpenEntityParameter parameter = null;
 
            switch (type)
            {
                case TypeCatalog.Funds:
                    parameter = new OpenEntityParameter
                    {
                        Title = "Выбрать фонд",
                        Type = type,
                        Content = new FundsParameterViewModel { Number = "1" }
                    };
                    break;
 
                case TypeCatalog.Lists:
                    parameter = new OpenEntityParameter
                    {
                        Title = "Выбрать опись",
                        Type = type,
                        Content = new ListsParameterViewModel { Number = "1" }
                    };
                    break;
 
                case TypeCatalog.Cases:
 
                    break;
 
                case TypeCatalog.Documents:
 
                    break;
 
                case TypeCatalog.Resolution:
 
                    break;
 
                case TypeCatalog.Enterprises:
 
                    break;
 
 
                default:
                    throw new ArgumentOutOfRangeException(nameof(type));
            }
 
            _logger.Debug($"{nameof(CreateNewTab)}. TypeCatalog = {type.ToString()}");
 
            switch (type)
            {
                case TypeCatalog.Funds:
                    OpenFundsDialogRequest.Raise(parameter, confirmation =>
                    {
                        var dialogResult = (FundsParameterViewModel)confirmation.Content;
                        var item = new FundsViewModel { Title = "Загрузка..." };
 
                        // Загружаем данные асинхронно с обработкой ошибок и логированием.
                        item.Initialize(dialogResult.Number)
                            .LogErrors(_logger, nameof(CreateNewTab));
 
                        _logger.Debug($"{nameof(CreateNewTab)}. TypeCatalog = {type.ToString()}. Новая вкладка добавлена.");
                        addNewTab(item);
                    });
                    break;
 
                case TypeCatalog.Lists:
                    OpenListsDialogRequest.Raise(parameter, confirmation =>
                    {
                        var dialogResult = (ListsParameterViewModel)confirmation.Content;
                        var item = new ListsViewModel { Title = "Загрузка..." };
 
                        // Загружаем данные асинхронно с обработкой ошибок и логированием.
                        item.Initialize(dialogResult.Number)
                            .LogErrors(_logger, nameof(CreateNewTab));
 
                        _logger.Debug($"{nameof(CreateNewTab)}. TypeCatalog = {type.ToString()}. Новая вкладка добавлена.");
                        addNewTab(item);
                    });
 
                    break;
 
                case TypeCatalog.Cases:
 
                    break;
 
                case TypeCatalog.Documents:
 
                    break;
 
                case TypeCatalog.Resolution:
 
                    break;
 
                case TypeCatalog.Enterprises:
 
                    break;
 
 
                default:
                    throw new ArgumentOutOfRangeException(nameof(type));
            }
        }
 
        private void OnTabRemoved(TabHostViewModelBase<TypeCatalog> sender, TabItemViewModelBase item)
        {
            string message = $"Вкладка \"{item.Title}\" была удалена.";
            _logger.Debug($"{nameof(CreateNewTab)}. {message}");
 
            Info = message;
        }
    }
}
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows.Input;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
 
namespace TemplatedTabControl.ViewModel
{
    /// <summary>
    /// Модель представления TabControl
    /// </summary>
    public abstract class TabHostViewModelBase<TAddTabCmdParam> : ViewModelBase
    {
        private int _selectedIndex;
 
        /// <summary> Вкладки. </summary>
        public ObservableCollection<TabItemViewModelBase> Tabs { get; }
 
        public event TypedEventHandler<TabHostViewModelBase<TAddTabCmdParam>, TabItemViewModelBase> TabRemoved;
 
        public ICommand AddTabCommand { get; }
 
        public int SelectedIndex
        {
            get { return _selectedIndex; }
            set { Set(ref _selectedIndex, value); }
        }
 
        protected TabHostViewModelBase()
        {
            Tabs = new ObservableCollection<TabItemViewModelBase>();
            Tabs.CollectionChanged += TabsOnCollectionChanged;
 
            AddTabCommand = new RelayCommand<TAddTabCmdParam>((parameter) =>
            {
                CreateNewTab(parameter,
                    newTabViewModel =>
                    {
                        Tabs.Add(newTabViewModel);
                        SelectedIndex = Tabs.Count - 1;
                    });
            });
        }
 
        /// <summary>
        /// В производном классе определяет логику добавления новой вкладки.
        /// </summary>
        protected abstract void CreateNewTab(TAddTabCmdParam parameter, Action<TabItemViewModelBase> addNewTab);
 
        private void TabsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (TabItemViewModelBase item in e.NewItems)
                {
                    item.CloseRequested += TabItemOnCloseRequested;
                }
            }
 
            if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (TabItemViewModelBase item in e.OldItems)
                {
                    item.CloseRequested -= TabItemOnCloseRequested;
                }
            }
        }
 
        private void TabItemOnCloseRequested(TabItemViewModelBase item)
        {
            Tabs.Remove(item);
            TabRemoved?.Invoke(this, item);
        }
    }
}
Добавлено через 19 минут
В текущей реализации очень легко заменить общее окно на разные конкретные для каждого типа окна или часть заменить, а часть оставить с общим окном.

Кстати, в классе Action Content добавляется по событию Loaded, это я эксперементировал, там проблема была с биндингами. То есть необязательно данные добавлять в самый последний момент.
1
31 / 17 / 5
Регистрация: 18.07.2013
Сообщений: 220
09.05.2019, 04:12  [ТС]
Зачем TabHostViewModelBase стал шаблоном не понятно, он же один в приложении.

Так работает

C#
1
2
3
4
5
6
7
8
protected TabHostViewModelBase()
{
     Tabs = new ObservableCollection<TabItemViewModelBase>();
     Tabs.CollectionChanged += TabsOnCollectionChanged;
     AddTabCommand = new RelayCommand<object>((parameter) => CreateNewTab(parameter));
}
 
protected abstract void CreateNewTab(object CommandParameter);
И в MainViewModel.cs
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected override void CreateNewTab( object CommandParameter)
{
            TypeCatalog typeCatalog = TypeCatalog.None;
            if (CommandParameter is TypeCatalog) typeCatalog = (TypeCatalog)CommandParameter;
            if (typeCatalog == TypeCatalog.Funds)
            {
                Tabs.Add(new FundsViewModel()
                {
                    Title = "Фонды",
                    Items =
                {
                    new FundViewModel() { Number = "1f", Name = "Fund name 1", Years="1994" },
                    new FundViewModel() { Number = "2f", Name = "Fund name 2", Years="1994" }
                }
                });
                SelectedIndex = Tabs.Count - 1;
            }
            //другие каталоги
           //после каталогов
           //if (CommandParametr is OpenFundParametrs) ...
}
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
09.05.2019, 04:12
Помогаю со студенческими работами здесь

Передача имени файла при создании дочернего процесса
Только начинаю программировать под Linux и не совсем понимаю, как это работает Задача такая: Родительский процесс создает n дочерних...

Отрисовка дочернего элемента
Здравствуйте. Зашел в тупик и никак не могу разобраться. Есть компонент представляющий из себя кнопку, которая отрисовывается в сообщении...

Поиск файлов по части имени \ имени
var SR: TSearchRec; Folder: String; begin Folder := 'D:\'; Folder := IncludeTrailingPathDelimiter(Folder); if...

Выбор дочернего элемента Stackpanel
привет есть stackpanel в ней элементы label Нужно через код обратится и выбрать что в нем написано, например к 3тему лейболу как это...

Добавление дочернего элемента в XML
Собственно сразу покажу часть кода _di_IXMLDocument XMLDocument= NewXMLDocument(); XMLDocument-&gt;LoadFromFile(FileName); IXMLNode...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Новые блоги и статьи
Ритм жизни
kumehtar 27.02.2026
Иногда приходится жить в ритме, где дел становится всё больше, а вовлечения в происходящее — всё меньше. Плотный график не даёт вниманию закрепиться ни на одном событии. Утро начинается с быстрых,. . .
[В процессе разработки] SDL3 для Web (WebAssembly): Сборка библиотек SDL3 и Box2D из исходников с помощью CMake и Emscripten
8Observer8 27.02.2026
Недавно вышла версия 3. 4. 2 библиотеки SDL3. На странице официальной релиза доступны исходники, готовые DLL (для x86, x64, arm64), а также библиотеки для разработки под Android, MinGW и Visual Studio. . . .
SDL3 для Web (WebAssembly): Реализация движения на Box2D v3 - трение и коллизии с повёрнутыми стенами
8Observer8 20.02.2026
Содержание блога Box2D позволяет легко создать главного героя, который не проходит сквозь стены и перемещается с заданным трением о препятствия, которые можно располагать под углом, как верхнее. . .
Конвертировать закладки radiotray-ng в m3u-плейлист
damix 19.02.2026
Это можно сделать скриптом для PowerShell. Использование . \СonvertRadiotrayToM3U. ps1 <path_to_bookmarks. json> Рядом с файлом bookmarks. json появится файл bookmarks. m3u с результатом. # Check if. . .
Семь CDC на одном интерфейсе: 5 U[S]ARTов, 1 CAN и 1 SSI
Eddy_Em 18.02.2026
Постепенно допиливаю свою "многоинтерфейсную плату". Выглядит вот так: https:/ / www. cyberforum. ru/ blog_attachment. php?attachmentid=11617&stc=1&d=1771445347 Основана на STM32F303RBT6. На борту пять. . .
Камера Toupcam IUA500KMA
Eddy_Em 12.02.2026
Т. к. у всяких "хикроботов" слишком уж мелкий пиксель, для подсмотра в ESPriF они вообще плохо годятся: уже 14 величину можно рассмотреть еле-еле лишь на экспозициях под 3 секунды (а то и больше),. . .
И ясному Солнцу
zbw 12.02.2026
И ясному Солнцу, и светлой Луне. В мире покоя нет и люди не могут жить в тишине. А жить им немного лет.
«Знание-Сила»
zbw 12.02.2026
«Знание-Сила» «Время-Деньги» «Деньги -Пуля»
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru