Лучший отвечающий
WPF TreeView и ObservableCollection

Вопрос
-
Есть поле
public ObservableCollection<ServerClientVM> ServerClientList { get; private set; }
Которое биндится к TreeView и выглядит это примерно так:
Нужно добавить 1 пункт который будет использоваться для добавления сервера, что бы стало примерно так:
Проблема в том что по логике в ServerClientList я его добавить не могу, т.к. это список серверов.- Изменено skysniper777 20 октября 2011 г. 11:00
20 октября 2011 г. 10:16
Ответы
-
можно изменить шаблон TreeView.
примерно так ...
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="400" Height="400" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid> <Grid.Resources> <Style TargetType="TreeView"> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="TreeView"> <ScrollViewer Focusable="False"> <StackPanel> <ItemsPresenter/> <Label Content="Добавить сервер" /> </StackPanel> </ScrollViewer> </ControlTemplate> </Setter.Value> </Setter> </Style> </Grid.Resources> <TreeView x:Name="tw" ItemsSource="{Binding}" /> </Grid> </Window>
- Предложено в качестве ответа Malobukv 22 октября 2011 г. 18:22
- Помечено в качестве ответа skysniper777 26 октября 2011 г. 10:13
20 октября 2011 г. 12:14 -
> Спасибо, это почти то что надо, но я кое что забыл указать :( В итоге должно быть вот так: ...
в таком случае можно использовать Converter (который добавит экземпляр типа None) и два шаблона для Item и None:<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="800" Height="400" xmlns:local="clr-namespace:WpfApplication1" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid> <Grid.Resources> <local:ChildsConverter x:Key="cc" /> </Grid.Resources> <TreeView ItemsSource="{Binding}" x:Name="tw"> <TreeView.Resources> <DataTemplate DataType="{x:Type local:None}"> <Button Content="Добавить" /> </DataTemplate> <HierarchicalDataTemplate DataType="{x:Type local:Item}" ItemsSource="{Binding Childs, Converter={StaticResource cc}}"> <TextBlock Text="{Binding Name}" /> </HierarchicalDataTemplate> </TreeView.Resources> </TreeView> </Grid> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Data; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var rnd = new Random(); tw.DataContext = Enumerable .Range(0, 10) .Select(i => new Item { Name = "item" + i, Childs = Enumerable .Range(0, rnd.Next(2, 5)) .Select(c => new Item { Name = "child" + c }) }); } } public class Item { public string Name { get; set; } public IEnumerable<Item> Childs { get; set; } } public class None {} public class ChildsConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var en = value as IEnumerable<Item>; if (en != null) { var lst = new List<object>(en); lst.Add(new None()); return lst; } return value; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } }
- Предложено в качестве ответа Malobukv 22 октября 2011 г. 18:21
- Помечено в качестве ответа skysniper777 25 октября 2011 г. 7:38
- Снята пометка об ответе skysniper777 25 октября 2011 г. 13:22
- Помечено в качестве ответа skysniper777 26 октября 2011 г. 10:13
22 октября 2011 г. 18:04 -
другой вариант без None, на основе ItemTemplateSelector:
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="800" Height="400" xmlns:local="clr-namespace:WpfApplication1" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid> <Grid.Resources> <local:ChildsConverter x:Key="cc" /> <DataTemplate x:Key="itm" > <TextBlock Text="{Binding Name}" /> </DataTemplate> <DataTemplate x:Key="ctrl"> <TextBlock Text="Добавить" /> </DataTemplate> <local:TemplateSelector x:Key="ts" Item="{StaticResource itm}" Control="{StaticResource ctrl}" /> <HierarchicalDataTemplate x:Key="ht" ItemsSource="{Binding Childs, Converter={StaticResource cc}}" ItemTemplateSelector="{StaticResource ts}"> <TextBlock Text="{Binding Name}" /> </HierarchicalDataTemplate> </Grid.Resources> <TreeView ItemsSource="{Binding}" x:Name="tw" ItemTemplate="{StaticResource ht}" /> </Grid> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Data; using System.Windows.Controls; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var rnd = new Random(); tw.DataContext = Enumerable .Range(0, 10) .Select(i => new Item { Name = "item" + i, Childs = Enumerable .Range(0, rnd.Next(2, 5)) .Select(c => new Item { Name = "child" + c }) }); } } public class Item { public string Name { get; set; } public IEnumerable<Item> Childs { get; set; } } public class TemplateSelector : DataTemplateSelector { public DataTemplate Item { get; set; } public DataTemplate Control { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { return (item is Item) ? Item : Control; } } public class ChildsConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var en = value as IEnumerable<Item>; if (en != null) { var lst = new List<object>(en); lst.Add(new object()); return lst; } return value; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } }
- Предложено в качестве ответа Malobukv 22 октября 2011 г. 18:21
- Помечено в качестве ответа skysniper777 25 октября 2011 г. 7:38
- Снята пометка об ответе skysniper777 25 октября 2011 г. 13:22
- Помечено в качестве ответа skysniper777 26 октября 2011 г. 10:13
22 октября 2011 г. 18:09 -
и еще один вариант - перехват TreeViewItem.ExpandedEvent и изменение TreeViewItem.ItemsSource
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="800" Height="400" xmlns:local="clr-namespace:WpfApplication1" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid> <Grid.Resources> <DataTemplate x:Key="itm" > <TextBlock Text="{Binding Name}" /> </DataTemplate> <DataTemplate x:Key="ctrl"> <TextBlock Text="Добавить" /> </DataTemplate> <local:TemplateSelector x:Key="ts" Item="{StaticResource itm}" Control="{StaticResource ctrl}" /> <HierarchicalDataTemplate x:Key="ht" ItemsSource="{Binding Childs}" ItemTemplateSelector="{StaticResource ts}"> <TextBlock Text="{Binding Name}" /> </HierarchicalDataTemplate> </Grid.Resources> <TreeView ItemsSource="{Binding}" x:Name="tw" ItemTemplate="{StaticResource ht}" /> </Grid> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Data; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Controls.Primitives; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var rnd = new Random(); tw.DataContext = Enumerable .Range(0, 10) .Select(i => new Item { Name = "item" + i, Childs = Enumerable .Range(0, rnd.Next(2, 5)) .Select(c => new Item { Name = "child" + c }) }); tw.AddHandler(TreeViewItem.ExpandedEvent, new RoutedEventHandler(TreeViewItemExpanded)); } private void TreeViewItemExpanded(object sender, RoutedEventArgs e) { var ti = e.OriginalSource as TreeViewItem; if ((ti != null) && (ti.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)) { var lst = new List<object>(ti.ItemsSource.OfType<object>()); lst.Add(new object()); ti.ItemsSource = lst; } } } public class Item { public string Name { get; set; } public IEnumerable<Item> Childs { get; set; } } public class TemplateSelector : DataTemplateSelector { public DataTemplate Item { get; set; } public DataTemplate Control { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { return (item is Item) ? Item : Control; } } }
- Предложено в качестве ответа Malobukv 22 октября 2011 г. 18:21
- Помечено в качестве ответа skysniper777 25 октября 2011 г. 7:38
- Снята пометка об ответе skysniper777 25 октября 2011 г. 13:22
- Помечено в качестве ответа skysniper777 26 октября 2011 г. 10:13
22 октября 2011 г. 18:15 -
> var en = value as IEnumerable<ServerClientVM>;
if (en != null) {
var lst = new List<object>(en);
...
return lst;
этот код подменяет ObservableCollection на List; из-за этого перестает работать привязка TreeView;
можно сделать так:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var lst = value as ObservableCollection<ServerClientVM>; if (lst != null) { ServerClientVM sc = new ServerClientVM { DisplayName = "Новый сервер" }; lst.Add(sc); return lst; } return value; }
- Помечено в качестве ответа skysniper777 26 октября 2011 г. 13:23
26 октября 2011 г. 13:16 -
> жаль что в итоге пришли к тому что изменяется сам ServerClientList [...] по логике в ServerClientList я его добавить не могу, т.к. это список серверов
пример который не меняет коллекции серверов и устройств.
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="300" Height="600" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:local="clr-namespace:WpfApplication1" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Window.Resources> <local:WrapperConverter x:Key="w"/> </Window.Resources> <TreeView x:Name="tv" ItemsSource="{Binding ServerClientList, Converter={StaticResource w}, ConverterParameter='Добавить сервер'}"> <TreeView.Resources> <DataTemplate DataType="{x:Type sys:String}"> <TextBlock Text="{Binding}" MouseUp="Add_MouseUp" /> </DataTemplate> <DataTemplate DataType="{x:Type local:Device}"> <TextBlock Text="{Binding DisplayName}" /> </DataTemplate> <HierarchicalDataTemplate DataType="{x:Type local:Server}" ItemsSource="{Binding Childs, Converter={StaticResource w}, ConverterParameter='Добавить камеру'}"> <TextBlock Text="{Binding DisplayName}" /> </HierarchicalDataTemplate> </TreeView.Resources> <TreeView.ItemContainerStyle> <Style TargetType="{x:Type TreeViewItem}"> <Setter Property="IsExpanded" Value="True"/> </Style> </TreeView.ItemContainerStyle> </TreeView> </Window>
using System; using System.Collections; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Globalization; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Media; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var model = new Model(); model.ServerClientList.Add(new Server { DisplayName = "dn", Childs = new ObservableCollection<Device> { new Device { DisplayName="d1" } } }); this.DataContext = model; } private void Add_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e) { switch ((e.OriginalSource as TextBlock).Text) { case "Добавить сервер": var model = this.DataContext as Model; model.ServerClientList.Add(new Server { DisplayName = "dn", Childs = new ObservableCollection<Device>() }); break; case "Добавить камеру": var ti = FindAncestor<TreeViewItem>(e.OriginalSource as DependencyObject); var ri = FindAncestor<TreeViewItem>(ti); (ri.DataContext as Server).Childs.Add(new Device { DisplayName = "nd" }); break; } } private T FindAncestor<T>(DependencyObject o) where T : class { DependencyObject cur = o; while (true) { cur = VisualTreeHelper.GetParent(cur); if (cur == null) break; if (cur.GetType() == typeof(T)) return cur as T; } return default(T); } } public class Server { public string DisplayName { get; set; } public ObservableCollection<Device> Childs { get; set; } } public class Device { public string DisplayName { get; set; } } class Model { public ObservableCollection<Server> ServerClientList { get; private set; } public Model() { this.ServerClientList = new ObservableCollection<Server>(); } } public class WrapperConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is ObservableCollection<Server> || value is ObservableCollection<Device>) { var res = new Wrapper(value as INotifyCollectionChanged); res.Add(parameter); return res; } return value; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } public class Wrapper : Collection<object>, INotifyCollectionChanged { public event NotifyCollectionChangedEventHandler CollectionChanged; public object Original {get; private set;} public Wrapper(INotifyCollectionChanged col) { foreach (var o in (IEnumerable)col) this.Add(o); col.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(col_CollectionChanged); this.Original = col; } void col_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Add) { var last = this.Last(); this.Remove(last); foreach (var o in e.NewItems) this.Add(o); this.Add(last); } var eh = this.CollectionChanged; if (eh != null) eh(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } }
- Помечено в качестве ответа skysniper777 27 октября 2011 г. 10:13
27 октября 2011 г. 8:09 -
в предыдущем примере можно использовать другой вариант WrapperConverter:
public class WrapperConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return (value is INotifyCollectionChanged) ? new CollectionExtention<object>(value as INotifyCollectionChanged, parameter) : value; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } class CollectionExtention<T> : IEnumerable, INotifyCollectionChanged { public event NotifyCollectionChangedEventHandler CollectionChanged; private IEnumerable Head; private IEnumerable Tail; public CollectionExtention(INotifyCollectionChanged col, params T[] parr) { col.CollectionChanged += (s, e) => { var eh = this.CollectionChanged; if (eh != null) eh(this, e); }; this.Head = (IEnumerable)col; this.Tail = parr; } IEnumerator IEnumerable.GetEnumerator() { return Enumerable.Concat(this.Head.OfType<object>(), this.Tail.OfType<object>()).GetEnumerator(); } }
- Изменено Malobukv 27 октября 2011 г. 9:43
- Помечено в качестве ответа skysniper777 27 октября 2011 г. 10:47
27 октября 2011 г. 9:08
Все ответы
-
можно изменить шаблон TreeView.
примерно так ...
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="400" Height="400" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid> <Grid.Resources> <Style TargetType="TreeView"> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="TreeView"> <ScrollViewer Focusable="False"> <StackPanel> <ItemsPresenter/> <Label Content="Добавить сервер" /> </StackPanel> </ScrollViewer> </ControlTemplate> </Setter.Value> </Setter> </Style> </Grid.Resources> <TreeView x:Name="tw" ItemsSource="{Binding}" /> </Grid> </Window>
- Предложено в качестве ответа Malobukv 22 октября 2011 г. 18:22
- Помечено в качестве ответа skysniper777 26 октября 2011 г. 10:13
20 октября 2011 г. 12:14 -
Спасибо, это почти то что надо, но я кое что забыл указать :(
В итоге должно быть вот так:
Вот текущий XAML:
<ResourceDictionary> <!-- DeviceList TEMPLATE --> <DataTemplate x:Key="DeviceListTemplate"> <TextBlock> <TextBlock Text="{Binding Path=DisplayName}" /> </TextBlock> </DataTemplate> <!-- ServerClientList TEMPLATE --> <HierarchicalDataTemplate x:Key="ServerClientListTemplate" ItemsSource="{Binding Path=DeviceList}" ItemTemplate="{StaticResource DeviceListTemplate}" > <Border Background="LightGray" CornerRadius="10" BorderThickness="2"> <StackPanel ToolTip="{Binding Path=DisplayName}" Orientation="Horizontal"> <!--<Image Margin="4,4,4,4" Height="32" Source="/IPRecord;component/Pictures/back_blue_32.png" ></Image>--> <TextBlock Margin="4" VerticalAlignment="Center" Text="{Binding Path=DisplayName}"/> <TextBlock Margin="4" VerticalAlignment="Center" Text="{Binding Path=IP, StringFormat=({0})}"/> </StackPanel> </Border> </HierarchicalDataTemplate> </ResourceDictionary>
- Изменено skysniper777 20 октября 2011 г. 13:24
20 октября 2011 г. 13:19 -
Тогда меняйте Template в ServerClientListTemplate так же как было вам показано выше.
- Изменено Anton.MaksimovEditor 21 октября 2011 г. 7:50
21 октября 2011 г. 7:49Отвечающий -
Тогда меняйте Template в ServerClientListTemplate так же как было вам показано выше.
В том то и проблема, я незнаю как.
Можно пример?
21 октября 2011 г. 8:07 -
Пардон, нужно использовать в шаблоне для TreeViewItem.21 октября 2011 г. 8:28Отвечающий
-
21 октября 2011 г. 8:31Отвечающий
-
Нет видимо сам я с этим не справлюсь, не дорос еще :(
Может кто то конкретно к моей задаче XAML TreeViewItem сделать?
21 октября 2011 г. 11:47 -
> Спасибо, это почти то что надо, но я кое что забыл указать :( В итоге должно быть вот так: ...
в таком случае можно использовать Converter (который добавит экземпляр типа None) и два шаблона для Item и None:<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="800" Height="400" xmlns:local="clr-namespace:WpfApplication1" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid> <Grid.Resources> <local:ChildsConverter x:Key="cc" /> </Grid.Resources> <TreeView ItemsSource="{Binding}" x:Name="tw"> <TreeView.Resources> <DataTemplate DataType="{x:Type local:None}"> <Button Content="Добавить" /> </DataTemplate> <HierarchicalDataTemplate DataType="{x:Type local:Item}" ItemsSource="{Binding Childs, Converter={StaticResource cc}}"> <TextBlock Text="{Binding Name}" /> </HierarchicalDataTemplate> </TreeView.Resources> </TreeView> </Grid> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Data; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var rnd = new Random(); tw.DataContext = Enumerable .Range(0, 10) .Select(i => new Item { Name = "item" + i, Childs = Enumerable .Range(0, rnd.Next(2, 5)) .Select(c => new Item { Name = "child" + c }) }); } } public class Item { public string Name { get; set; } public IEnumerable<Item> Childs { get; set; } } public class None {} public class ChildsConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var en = value as IEnumerable<Item>; if (en != null) { var lst = new List<object>(en); lst.Add(new None()); return lst; } return value; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } }
- Предложено в качестве ответа Malobukv 22 октября 2011 г. 18:21
- Помечено в качестве ответа skysniper777 25 октября 2011 г. 7:38
- Снята пометка об ответе skysniper777 25 октября 2011 г. 13:22
- Помечено в качестве ответа skysniper777 26 октября 2011 г. 10:13
22 октября 2011 г. 18:04 -
другой вариант без None, на основе ItemTemplateSelector:
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="800" Height="400" xmlns:local="clr-namespace:WpfApplication1" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid> <Grid.Resources> <local:ChildsConverter x:Key="cc" /> <DataTemplate x:Key="itm" > <TextBlock Text="{Binding Name}" /> </DataTemplate> <DataTemplate x:Key="ctrl"> <TextBlock Text="Добавить" /> </DataTemplate> <local:TemplateSelector x:Key="ts" Item="{StaticResource itm}" Control="{StaticResource ctrl}" /> <HierarchicalDataTemplate x:Key="ht" ItemsSource="{Binding Childs, Converter={StaticResource cc}}" ItemTemplateSelector="{StaticResource ts}"> <TextBlock Text="{Binding Name}" /> </HierarchicalDataTemplate> </Grid.Resources> <TreeView ItemsSource="{Binding}" x:Name="tw" ItemTemplate="{StaticResource ht}" /> </Grid> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Data; using System.Windows.Controls; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var rnd = new Random(); tw.DataContext = Enumerable .Range(0, 10) .Select(i => new Item { Name = "item" + i, Childs = Enumerable .Range(0, rnd.Next(2, 5)) .Select(c => new Item { Name = "child" + c }) }); } } public class Item { public string Name { get; set; } public IEnumerable<Item> Childs { get; set; } } public class TemplateSelector : DataTemplateSelector { public DataTemplate Item { get; set; } public DataTemplate Control { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { return (item is Item) ? Item : Control; } } public class ChildsConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var en = value as IEnumerable<Item>; if (en != null) { var lst = new List<object>(en); lst.Add(new object()); return lst; } return value; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } }
- Предложено в качестве ответа Malobukv 22 октября 2011 г. 18:21
- Помечено в качестве ответа skysniper777 25 октября 2011 г. 7:38
- Снята пометка об ответе skysniper777 25 октября 2011 г. 13:22
- Помечено в качестве ответа skysniper777 26 октября 2011 г. 10:13
22 октября 2011 г. 18:09 -
и еще один вариант - перехват TreeViewItem.ExpandedEvent и изменение TreeViewItem.ItemsSource
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="800" Height="400" xmlns:local="clr-namespace:WpfApplication1" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid> <Grid.Resources> <DataTemplate x:Key="itm" > <TextBlock Text="{Binding Name}" /> </DataTemplate> <DataTemplate x:Key="ctrl"> <TextBlock Text="Добавить" /> </DataTemplate> <local:TemplateSelector x:Key="ts" Item="{StaticResource itm}" Control="{StaticResource ctrl}" /> <HierarchicalDataTemplate x:Key="ht" ItemsSource="{Binding Childs}" ItemTemplateSelector="{StaticResource ts}"> <TextBlock Text="{Binding Name}" /> </HierarchicalDataTemplate> </Grid.Resources> <TreeView ItemsSource="{Binding}" x:Name="tw" ItemTemplate="{StaticResource ht}" /> </Grid> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Data; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Controls.Primitives; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var rnd = new Random(); tw.DataContext = Enumerable .Range(0, 10) .Select(i => new Item { Name = "item" + i, Childs = Enumerable .Range(0, rnd.Next(2, 5)) .Select(c => new Item { Name = "child" + c }) }); tw.AddHandler(TreeViewItem.ExpandedEvent, new RoutedEventHandler(TreeViewItemExpanded)); } private void TreeViewItemExpanded(object sender, RoutedEventArgs e) { var ti = e.OriginalSource as TreeViewItem; if ((ti != null) && (ti.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)) { var lst = new List<object>(ti.ItemsSource.OfType<object>()); lst.Add(new object()); ti.ItemsSource = lst; } } } public class Item { public string Name { get; set; } public IEnumerable<Item> Childs { get; set; } } public class TemplateSelector : DataTemplateSelector { public DataTemplate Item { get; set; } public DataTemplate Control { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { return (item is Item) ? Item : Control; } } }
- Предложено в качестве ответа Malobukv 22 октября 2011 г. 18:21
- Помечено в качестве ответа skysniper777 25 октября 2011 г. 7:38
- Снята пометка об ответе skysniper777 25 октября 2011 г. 13:22
- Помечено в качестве ответа skysniper777 26 октября 2011 г. 10:13
22 октября 2011 г. 18:15 -
Уважаемый пользователь, пожалуйста, не забудьте отметить сообщение или сообщения, которые являются решением вашей проблемы, даже если это ваше собстевенное сообщение. Это можно сделать с помощью кнопки 'Пометить как ответ', которая есть под каждым сообщением.
Спасибо.
Для связи [mail]24 октября 2011 г. 7:24 -
К сожалению у меня так и не получилось заставить обновляться TreeView при добавлении в ObservableCollection<ServerClientVM> ServerClientList элемента.
Попробовал все три метода. Как быть?
25 октября 2011 г. 13:24 -
> К сожалению у меня так и не получилось заставить обновляться TreeView при добавлении в ObservableCollection<ServerClientVM> ServerClientList элемента. Попробовал все три метода. Как быть?
мой код работает. вы внесли какие-то изменения? покажите код.
давайте уточним ...
вначале был вопрос: как сделать кнопку "Добавить сервер"? -- есть ответ.
затем появился вопрос: как в подузлах сделать кнопки "Добавить камеру"? -- есть ответ.
теперь вопрос: как обновлять TreeView? -- так?
- Изменено Malobukv 25 октября 2011 г. 13:52
25 октября 2011 г. 13:28 -
> К сожалению у меня так и не получилось заставить обновляться TreeView при добавлении в ObservableCollection<ServerClientVM> ServerClientList элемента. Попробовал все три метода. Как быть?
мой код работает. вы внесли какие-то изменения? покажите код.
давайте уточним ...
вначале был вопрос: как сделать кнопку "Добавить сервер"? -- есть ответ.
затем появился вопрос: как в подузлах сделать кнопки "Добавить камеру"? -- есть ответ.
теперь вопрос: как обновлять TreeView? -- так?
Все верно, это моя ошибка что изначально указал недостаточно данных.Если в кратце то у вас, насколько я понимаю, заменяется ItemsSource у TreeView, тогда как у меня ItemsSource="{Binding Path=ServerClientList}"
- Изменено skysniper777 26 октября 2011 г. 11:20
25 октября 2011 г. 14:16 -
> ...заставить обновляться TreeView при добавлении в ObservableCollection<ServerClientVM> ServerClientList элемента.
надо указать DataContext и привязки шаблонов.<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="300" Height="300" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <StackPanel> <Button Content="Text" Click="Button_Click" HorizontalAlignment="Left" VerticalAlignment="Top" /> <TreeView x:Name="tv" ItemsSource="{Binding}" ItemTemplate="{DynamicResource ht}"> <TreeView.Resources> <DataTemplate x:Key="dt"> <TextBlock Text="{Binding}" /> </DataTemplate> <HierarchicalDataTemplate ItemsSource="{Binding Childs}" ItemTemplate="{StaticResource dt}" x:Key="ht"> <TextBlock Text="{Binding DisplayName}" /> </HierarchicalDataTemplate> </TreeView.Resources> </TreeView> </StackPanel> </Window>
using System.Collections.ObjectModel; using System.Windows; using System.Collections.Generic; namespace WpfApplication1 { public partial class MainWindow : Window { ObservableCollection<ServerClientVM> ServerClientList; public MainWindow() { InitializeComponent(); this.ServerClientList = new ObservableCollection<ServerClientVM>(); tv.DataContext = this.ServerClientList; } private void Button_Click(object sender, RoutedEventArgs e) { this.ServerClientList.Add(new ServerClientVM { DisplayName = "dn", Childs = new List<int> { 1, 2, 3 } }); } } public class ServerClientVM { public string DisplayName { get; set; } public object Childs { get; set; } } }
> Использование TempServer один из вариантов добавления сервера в список, нужно только как то забиндить (или еще что то сделать, что тоже является для меня вопросом) его к последнему элементу в списке.
т.е. в ServerClientList надо добавить элемент (ServerClientBaseVM); изменения должно отобразиться в TreeView?
26 октября 2011 г. 11:43 -
> Если в кратце то у вас, насколько я понимаю, заменяется ItemsSource у TreeView, тогда как у меня ItemsSource="{Binding Path=ServerClientList}"
в такой ситуации так
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="300" Height="300" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <StackPanel> <Button Content="Text" Click="Button_Click" HorizontalAlignment="Left" VerticalAlignment="Top" /> <TreeView x:Name="tv" ItemsSource="{Binding ServerClientList}" ItemTemplate="{DynamicResource ht}"> <TreeView.Resources> <DataTemplate x:Key="dt"> <TextBlock Text="{Binding}" /> </DataTemplate> <HierarchicalDataTemplate ItemsSource="{Binding Childs}" ItemTemplate="{StaticResource dt}" x:Key="ht"> <TextBlock Text="{Binding DisplayName}" /> </HierarchicalDataTemplate> </TreeView.Resources> </TreeView> </StackPanel> </Window>
using System.Collections.ObjectModel; using System.Windows; using System.Collections.Generic; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.DataContext = new Model(); } private void Button_Click(object sender, RoutedEventArgs e) { var model = this.DataContext as Model; model.ServerClientList.Add(new ServerClientVM { DisplayName = "dn", Childs = new List<int> { 1, 2, 3 } }); } } class ServerClientVM { public string DisplayName { get; set; } public object Childs { get; set; } } class Model { public ObservableCollection<ServerClientVM> ServerClientList { get; private set; } public Model() { this.ServerClientList = new ObservableCollection<ServerClientVM>(); } } }
26 октября 2011 г. 12:13 -
Я немного другое имел ввиду, то что вы выше указали у меня прекрасно работает, не работает вот что:
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication1" Title="MainWindow" Height="350" Width="525"> <StackPanel> <StackPanel.Resources> <local:ChildsConverter x:Key="servercc"/> </StackPanel.Resources> <Button Content="Text" Click="Button_Click" HorizontalAlignment="Left" VerticalAlignment="Top" /> <TreeView x:Name="tv" ItemsSource="{Binding ServerClientList, Converter={StaticResource servercc}}"> <TreeView.Resources> <DataTemplate x:Key="dt"> <TextBlock Text="{Binding}" /> </DataTemplate> <HierarchicalDataTemplate ItemsSource="{Binding Childs}" ItemTemplate="{StaticResource dt}" DataType="{x:Type local:ServerClientVM}"> <TextBlock Text="{Binding DisplayName}" /> </HierarchicalDataTemplate> </TreeView.Resources> </TreeView> </StackPanel> </Window>
using System; using System.Collections.Generic; using System.Linq; 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.Collections.ObjectModel; using System.Windows.Controls.Primitives; namespace WpfApplication1 { public partial class MainWindow : Window { ObservableCollection<ServerClientVM> ServerClientList; public MainWindow() { InitializeComponent(); this.DataContext = new Model(); var model = this.DataContext as Model; model.ServerClientList.Add(new ServerClientVM { DisplayName = "dn", Childs = new List<int> { 1, 2, 3 } }); // tv.AddHandler(TreeViewItem.ExpandedEvent, new RoutedEventHandler(TreeViewItemExpanded)); } //private void TreeViewItemExpanded(object sender, RoutedEventArgs e) //{ // var ti = e.OriginalSource as TreeViewItem; // if ((ti != null) && (ti.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated) && (ti.ItemsSource != null)) // { // var lst = new List<object>(ti.ItemsSource.OfType<object>()); // lst.Add(new Object()); // ti.ItemsSource = lst; // } //} private void Button_Click(object sender, RoutedEventArgs e) { var model = this.DataContext as Model; model.ServerClientList.Add(new ServerClientVM { DisplayName = "dn", Childs = new List<int> { 1, 2, 3 } }); } } public class ServerClientVM { public string DisplayName { get; set; } public object Childs { get; set; } } class Model { public ObservableCollection<ServerClientVM> ServerClientList { get; private set; } public Model() { this.ServerClientList = new ObservableCollection<ServerClientVM>(); } } public class ChildsConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var en = value as IEnumerable<ServerClientVM>; if (en != null) { var lst = new List<object>(en); ServerClientVM sc = new ServerClientVM(); sc.DisplayName = "Новый сервер"; lst.Add(sc); return lst; } return value; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } }
С добавлением элемента в своей задаче справился через команду и передачу параметра команды.
- Изменено skysniper777 26 октября 2011 г. 12:50
26 октября 2011 г. 12:50 -
> var en = value as IEnumerable<ServerClientVM>;
if (en != null) {
var lst = new List<object>(en);
...
return lst;
этот код подменяет ObservableCollection на List; из-за этого перестает работать привязка TreeView;
можно сделать так:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var lst = value as ObservableCollection<ServerClientVM>; if (lst != null) { ServerClientVM sc = new ServerClientVM { DisplayName = "Новый сервер" }; lst.Add(sc); return lst; } return value; }
- Помечено в качестве ответа skysniper777 26 октября 2011 г. 13:23
26 октября 2011 г. 13:16 -
Malobukv спасибо за помощь, очень помогли в освоении WPF.
Хотя очень жаль что в итоге пришли к тому что изменяется сам ServerClientList.
В первом посте я писал что "Проблема в том что по логике в ServerClientList я его добавить не могу, т.к. это список серверов."
И например код foreach чтобы исключить ненужный последний элемент из списка, не будет работать
foreach (ServerClientVM scm in ServerClientList) { _testString = _testString + scm.DisplayName; }
26 октября 2011 г. 14:03 -
> Хотя очень жаль что в итоге пришли к тому что изменяется сам ServerClientList.
можно сделать обертку для ServerClientList
> И например код foreach чтобы исключить ненужный последний элемент из списка, не будет работать foreach (ServerClientVM scm in ServerClientList)
выбрать элементы определенного типа:
foreach(var itm in ServerClientList.OfType<ServerClientVM>()) { }
26 октября 2011 г. 20:29 -
> жаль что в итоге пришли к тому что изменяется сам ServerClientList [...] по логике в ServerClientList я его добавить не могу, т.к. это список серверов
пример который не меняет коллекции серверов и устройств.
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="300" Height="600" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:local="clr-namespace:WpfApplication1" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Window.Resources> <local:WrapperConverter x:Key="w"/> </Window.Resources> <TreeView x:Name="tv" ItemsSource="{Binding ServerClientList, Converter={StaticResource w}, ConverterParameter='Добавить сервер'}"> <TreeView.Resources> <DataTemplate DataType="{x:Type sys:String}"> <TextBlock Text="{Binding}" MouseUp="Add_MouseUp" /> </DataTemplate> <DataTemplate DataType="{x:Type local:Device}"> <TextBlock Text="{Binding DisplayName}" /> </DataTemplate> <HierarchicalDataTemplate DataType="{x:Type local:Server}" ItemsSource="{Binding Childs, Converter={StaticResource w}, ConverterParameter='Добавить камеру'}"> <TextBlock Text="{Binding DisplayName}" /> </HierarchicalDataTemplate> </TreeView.Resources> <TreeView.ItemContainerStyle> <Style TargetType="{x:Type TreeViewItem}"> <Setter Property="IsExpanded" Value="True"/> </Style> </TreeView.ItemContainerStyle> </TreeView> </Window>
using System; using System.Collections; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Globalization; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Media; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var model = new Model(); model.ServerClientList.Add(new Server { DisplayName = "dn", Childs = new ObservableCollection<Device> { new Device { DisplayName="d1" } } }); this.DataContext = model; } private void Add_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e) { switch ((e.OriginalSource as TextBlock).Text) { case "Добавить сервер": var model = this.DataContext as Model; model.ServerClientList.Add(new Server { DisplayName = "dn", Childs = new ObservableCollection<Device>() }); break; case "Добавить камеру": var ti = FindAncestor<TreeViewItem>(e.OriginalSource as DependencyObject); var ri = FindAncestor<TreeViewItem>(ti); (ri.DataContext as Server).Childs.Add(new Device { DisplayName = "nd" }); break; } } private T FindAncestor<T>(DependencyObject o) where T : class { DependencyObject cur = o; while (true) { cur = VisualTreeHelper.GetParent(cur); if (cur == null) break; if (cur.GetType() == typeof(T)) return cur as T; } return default(T); } } public class Server { public string DisplayName { get; set; } public ObservableCollection<Device> Childs { get; set; } } public class Device { public string DisplayName { get; set; } } class Model { public ObservableCollection<Server> ServerClientList { get; private set; } public Model() { this.ServerClientList = new ObservableCollection<Server>(); } } public class WrapperConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is ObservableCollection<Server> || value is ObservableCollection<Device>) { var res = new Wrapper(value as INotifyCollectionChanged); res.Add(parameter); return res; } return value; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } public class Wrapper : Collection<object>, INotifyCollectionChanged { public event NotifyCollectionChangedEventHandler CollectionChanged; public object Original {get; private set;} public Wrapper(INotifyCollectionChanged col) { foreach (var o in (IEnumerable)col) this.Add(o); col.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(col_CollectionChanged); this.Original = col; } void col_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Add) { var last = this.Last(); this.Remove(last); foreach (var o in e.NewItems) this.Add(o); this.Add(last); } var eh = this.CollectionChanged; if (eh != null) eh(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } }
- Помечено в качестве ответа skysniper777 27 октября 2011 г. 10:13
27 октября 2011 г. 8:09 -
в предыдущем примере можно использовать другой вариант WrapperConverter:
public class WrapperConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return (value is INotifyCollectionChanged) ? new CollectionExtention<object>(value as INotifyCollectionChanged, parameter) : value; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } class CollectionExtention<T> : IEnumerable, INotifyCollectionChanged { public event NotifyCollectionChangedEventHandler CollectionChanged; private IEnumerable Head; private IEnumerable Tail; public CollectionExtention(INotifyCollectionChanged col, params T[] parr) { col.CollectionChanged += (s, e) => { var eh = this.CollectionChanged; if (eh != null) eh(this, e); }; this.Head = (IEnumerable)col; this.Tail = parr; } IEnumerator IEnumerable.GetEnumerator() { return Enumerable.Concat(this.Head.OfType<object>(), this.Tail.OfType<object>()).GetEnumerator(); } }
- Изменено Malobukv 27 октября 2011 г. 9:43
- Помечено в качестве ответа skysniper777 27 октября 2011 г. 10:47
27 октября 2011 г. 9:08 -
Еще раз спасибо.
Велик и могуч WPF.
Я даже не совсем понимаю как работают последние варианты, но они мне абсолютно подходят.
27 октября 2011 г. 10:50