I have an ObservableCollection that I populate during runtime which is bound to the TreeView. When the collection is updated, the root object of the collection appears in the TreeView without any way to expand it (see the first image).
I assumed this meant there was an issue with the binding, however, removing TreeView.ItemCotainerStyle tag makes the arrow appear and everything works as intended (see the second image). This behaviour works in reverse too, if the style tag isn't in the view and I add it after the collection is updated then the arrows will appear.
The style tags aren't necessary for any function in my project to work, they're just leftover from an example I was working from.
<TreeView ItemsSource="{Binding CompileMessages}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight"
Value="Normal" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type models:CompileMessagesDto}"
ItemsSource="{Binding Path=Children}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="{Binding Path=State}"
Margin="3"
Foreground="White" />
<TextBlock Text="{Binding Path=Path}"
FontWeight="Normal"
Foreground="White"
FontSize="12"
Margin="3" />
<TextBlock Text="{Binding Path=Description}"
FontWeight="Normal"
FontSize="12"
Foreground="#ff8000"
Margin="3" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
public class CompileMessagesDto
{
public string State { get; set; } = "";
public string Path { get; set; } = "";
public string Description { get; set; } = "";
public string Parent { get; set; }
public ObservableCollection<CompileMessagesDto> Children { get; set; } = new ObservableCollection<CompileMessagesDto>();
}
TreeView before modification
TreeView after removing the style tag
Initialising the collection with test values displays the arrows, it's only after the collection is modified during runtime that the TreeView behaves like this. I use MaterialDesignInXaml if that helps at all.
Edit (how the collection is updated)
It's definitely not the best way to do it but I loop through a collection of compiler messages and add them to the collection. I'm left with every object and a reference to their parent.
foreach (CompilerResultMessage message in messages)
{
CompileMessagesDto compileMessage = new CompileMessagesDto { State = message.State.ToString(), Path = message.Path, Description = message.Description, Parent = parent };
MessageCrawl(message.Messages, message.Path);
CompileMessages.Add(compileMessage);
}
Then I set each object's children, and remove every object from the collection that isn't the root object. Leaving the root with the tree of children in it.
List<CompileMessagesDto> removeItems = new List<CompileMessagesDto>();
foreach (CompileMessagesDto message in CompileMessages)
{
message.Children = new ObservableCollection<CompileMessagesDto>(CompileMessages.Where(c => c.Parent == message.Path));
if (message.Parent != "Root") { removeItems.Add(message); }
}
foreach (CompileMessagesDto message in removeItems)
{
CompileMessages.Remove(message);
}
You are overwriting the binding source CompileMessagesDto.Children:
message.Children = new ObservableCollection<CompileMessagesDto>(...);
Since CompileMessagesDto doesn't implement INotifyProeprtyChanged the Binding won't be able to recognize the property change.
General rule of data binding: the binding source must always implement it's properties as DependencyProperty if the source is a subclass of DependencyObject. Otherwise the source object must implement INotifyPropertyChanged.
If you don't do this, your will create potential memory leaks.
To solve your problem either let CompileMessagesDto implement INotifyPropertyChanged and raise the PropertyChanged event from the CompileMessagesDto.Children property (recommended solution).
Alternatively keep the current ObservableCollection instance and use Add and Remove to modify it.
// Using for-statement allows to get rid of the 'removedItems' collection and the second foreach-statement (improve performance)
for (int index = CompileMessages.Count - 1; index >= 0; index--)
{
CompileMessages
.Where(c => c.Parent == message.Path)
.ToList()
.ForEach(message.Children.Add);
if (message.Parent != "Root")
{
CompileMessages.Remove(message);
}
}
Also note that your Style does throw errors. CompileMessagesDto does neither have a IsExpanded nor a IsSelected property.
Related
I have an ItemsControl bound to an ItemsSource. Each item can have one of several DataTemplates assigned depending on the value of various properties on the item. These properties can change at runtime, and the DataTemplates need to be swapped out individually. In WPF I was able to do so with the following (partial simplified xaml):
<ItemsControl
ItemsSource="{Binding Items}">
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="ContentTemplate">
<Setter.Value>
<MultiBinding Converter="{StaticResource RowTemplateConverter}">
<Binding Path="(local:Row.Sum)" />
<Binding Path="(local:Row.Avg)" />
<Binding Path="(local:Row.ShowFlagA)" />
<Binding Path="(local:Row.ShowFlagB)" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
I've run into several issues trying to move this to UWP:
MultiBinding is not supported
To compensate for the above, I tried consolidating the converter logic into a single string property of the Row but the DataTemplate doesn't appear to be assigned. Also the binding syntax I used gives runtime XAML errors, not sure why.
<ItemsControl
ItemsSource="{Binding Items}" >
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="ContentTemplate">
<Setter.Value>
<Binding Path="RowTemplate" Converter="{StaticResource RowTemplateConverter}"/>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
DataTemplateSelector and ItemContainerStyleSelectors won't work because they're only evaluated once, and I need the templates updated on various property changes
I've seen some answers here that say to null the above Selectors and re-assign them. This is the closest I've been able to get to my desired behavior, but the performance is poor with dozens of items, and fast changing properties, so I'm unable to use this.
You can write an attached behavior to accomplish this. Alternatively extend e.g. ItemsControl (or a derived type).
The key is to reassign the item container's content in order to invoke the DataTemplateSelector again.
The attacehed property will reset the content to trigger the DataTemplateSelector. Your view model will track the changes of the data items that require to re-evaluate the actual DataTemplate and finally trigger the attached property. This is done by simply assigning the changed item to a view model property that binds to the attached behavior.
First create a template selector by extending DataTemplateSelector:
public class DataItemTemplateSelector : DataTemplateSelector
{
public DataTemplate ActivatedTemplate { get; set; }
public DataTemplate DeactivatedTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
switch (item)
{
case DataItem dataItem when dataItem.IsActivated: return this.ActivatedTemplate;
default: return this.DeactivatedTemplate;
}
}
}
Implement the attached behavior that modifies the container of the changed item:
public class TemplateSelector : DependencyObject
{
public static object GetChangedItem(DependencyObject obj)
{
return (object)obj.GetValue(ChangedItemProperty);
}
public static void SetChangedItem(DependencyObject obj, object value)
{
obj.SetValue(ChangedItemProperty, value);
}
public static readonly DependencyProperty ChangedItemProperty =
DependencyProperty.RegisterAttached("ChangedItem", typeof(object), typeof(TemplateSelector), new PropertyMetadata(default(object), OnChangedItemChanged));
private static void OnChangedItemChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (!(attachingElement is ItemsControl itemsControl))
{
throw new ArgumentException($"Attaching element must be of type '{nameof(ItemsControl)}'");
}
var container = (itemsControl.ItemContainerGenerator.ContainerFromItem(e.NewValue) as ContentControl);
var containerContent = container.Content;
container.Content = null;
container.Content = containerContent; // Trigger the DataTemplateSelector
}
}
Apply the attached property and bind it to your view model. Also assign the template selector:
<Page>
<Page.Resources>
<local:DataItemTemplateSelector x:Key="TemplateSelector">
<local:DataItemTemplateSelector.ActivatedTemplate>
<DataTemplate x:DataType="local:DataItem">
<TextBlock Text="{Binding Text}" Foreground="Red" />
</DataTemplate>
</local:DataItemTemplateSelector.ActivatedTemplate>
<local:DataItemTemplateSelector.DeactivatedTemplate>
<DataTemplate x:DataType="local:DataItem">
<TextBlock Text="{Binding Text}" Foreground="Black" />
</DataTemplate>
</local:DataItemTemplateSelector.DeactivatedTemplate>
</local:DataItemTemplateSelector>
</Page.Resources>
<Grid>
<ListBox ItemsSource="{x:Bind MainViewModel.DataItems}"
local:TemplateSelector.ChangedItem="{x:Bind MainViewModel.UpdatedItem, Mode=OneWay}"
ItemTemplateSelector="{StaticResource TemplateSelector}" />
</Grid>
</Page>
Finally let the view model track the relevant property changes and set the changed property e.g. to a UpdatedItem property which binds to the attached behavior (see above):
public class MainViewModel : ViewModel, INotifyPropertyChanged
{
public MainViewModel()
{
DataItems = new ObservableCollection<DataItem>();
for (int index = 0; index < 10; index++)
{
DataItem newItem = new DataItem();
// Listen to property changes that are relevant
// for the selection of the DataTemplate
newItem.Activated += OnItemActivated;
this.DataItems.Add(newItem);
}
}
// Trigger the attached property by setting the property that binds to the behavior
private void OnItemActivated(object sender, EventArgs e) => this.UpdatedItem = sender as DataItem
public ObservableCollection<DataItem> DataItems { get; }
private DataItem updatedItem;
public DataItem UpdatedItem
{
get => this.updatedItem;
set
{
this.updatedItem = value;
OnPropertyChanged();
}
}
}
this only updates the container of the item that you select in the view model.
Yep, The DataItemTemplateSelector works when preparing items. it will not response the item's property change even if it has implement INotifyPropertyChanged interface, the better way is use IValueConverter to update the uielement base on the specific property.
For example
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
object img = null;
switch (value.ToString())
{
case "horizontal":
img = new BitmapImage(new Uri("ms-appx:///Assets/holder1.png"));
break;
case "vertical":
img = new BitmapImage(new Uri("ms-appx:///Assets/holder2.png"));
break;
default:
break;
}
return img;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
For please refer IValueConverter document.
It is necessary to display the element, presented in the form as in the picture, in the form of a tree. The object itself is created at compile time.
An attempt to make in this form
<TreeView
Grid.Row="1"
Grid.Column="0"
ItemsSource="{Binding LanguageInformation.Items}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:LocalizationBrowserViewModel}" ItemsSource="{Binding LanguageInformation.Items}">
<TextBlock Text="{Binding}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Class for presentation
public class Element
{
public string Name { get; set; }
public ObservableCollection<object> Items { get; set; }
public Element()
{
this.Items = new ObservableCollection<object>();
}
}
The Element class now allows only one level of hierarchy. To support N levels, the Element class needs to be changed as below
public class Element
{
public string Name { get; set; }
public ObservableCollection<Element> Items { get; set; }
public Element()
{
this.Items = new ObservableCollection<Element>();
}
}
TreeView needs to be replaced with a ListView because the screenshot provided in the question contains multiple columns. TreeView does not allow multiple columns but a ListView.
ListView in WPF supports HierarchicalDataTemplate. You can refer HierarchicalDataTemplate in WPF for the implementation.
I am currently having a class myCommand
class myCommand : INotifyCollectionChanged , INotifyPropertyChanged
{
public string Name { get; set; }
public ObservableCollection<myFile> subCommand { get; set; }
}
I have two TreeView items in my window.
tvCommandList which contains all available commands and tvFinalList to hold all selected commands.
I used contextmenu to copy the items from tvCommandList to tvFinalList;
In the mainWindow, I have two ObservableCollection items which are bound to the TreeViewItems.
ObservableCollection<myCommand> cmdlist = null;
ObservableCollection<myCommand> finallist = null;
These are bound to the TreeView in the XAML file.
<Grid>
<Grid.Resources>
<ResourceDictionary>
<Style x:Key="styleTemplate" TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsInitiallySelected, Mode=TwoWay}" />
</Style>
<HierarchicalDataTemplate DataType="{x:Type data:myCommand}"
ItemsSource="{Binding subCommand, Mode=TwoWay}">
<TextBlock Text="{Binding Name, Mode=TwoWay}" />
</HierarchicalDataTemplate>
</ResourceDictionary>
</Grid.Resources>
</Grid>
<TreeView x:Name="tvSendList" ItemsSource="{Binding}" DataContext="{Binding cmdlist}">
<TreeView x:Name="tvRecvList" ItemsSource="{Binding}" DataContext="{Binding finallist}">
I copy TreeViewItem from cmdlist to finallist and edit them for customsing data.
The issue I am facing here is that if I modify an item (Update the Name value) in the finallist, the cmdlist item also gets updated, which
I am not sure how to go about to resolve this.
I tried moving the ResourceDictionary to each TreeView specifically, but still facing the same issue
You should also clone, i.e. create a copy of, each individual myFile object when you create a copy of the collection, e.g.:
finalist = new ObservableCollection<myFile>(cmdlist.Select(x => new myFile()
{
Property1 = x.Property1,
Property2 = x.Property2
//copy all property values...
}));
I am trying to get the values/contents of the "selected" check-boxes so that i can use it to get data from sqlite database in the back-end . But I am unable to get the value of the checkbox from the listview.
This is the listview -
<ListView x:Name="listview" Background="Azure" SelectionMode="Multiple"
ItemsSource="{Binding Strings}" RenderTransformOrigin="0.5,0.5" Width="343">
<ListView.ItemTemplate>
<DataTemplate x:Name="datatemplate">
<CheckBox x:Name="CheckBoxZone" IsChecked="{Binding RelativeSource={RelativeSource AncestorType=ListViewItem},Path=IsSelected}"
Content="{Binding que_text}" Margin="0,5,0,0"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Style="{StaticResource ResourceKey=button_style }" x:Name="addToSurvey" Content="Add" Click="AddToSurvey_Click" />
this is the function-
private void AddToSurvey_Click(object sender, RoutedEventArgs e)
{
//foreach (var item in listview.SelectedItems)
for (int i=0;i< listview.SelectedItems.Count;i++)
{
string que = listview.Items[i].ToString(); //did not work
}
}
this is the Questions class -
public class Questions
{
public int que_id { get; set; }
public string que_text { get; set; }
}
the checkbox hold the que_text value which I need to retrieve for getting the que_id from the database.
In WPF, we use what we call MVVM. Instead of going and poking at the ListViewItem controls like in winforms, we'll put properties for the information we need on our viewmodel classes, and we'll use bindings to tell the UI how to update the viewmodel classes and vice versa.
So we'll add an IsSelected property to Question. We'll also rename that class from Questions to Question, and the collection of questions will now be named Questions instead of Strings.
public class Question
{
public int ID { get; set; }
public string Text { get; set; }
public bool IsSelected { get; set; }
}
Here's your ListView. We bind CheckBox.IsSelected to ListViewItem.IsSelected, so the user can check them just by clicking anywhere on the item. Then we bind Question.IsSelected to ListViewItem.IsSelected in an ItemContainerStyle.
<ListView
x:Name="listview"
Background="Azure"
SelectionMode="Multiple"
ItemsSource="{Binding Questions}"
>
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox
IsChecked="{Binding RelativeSource={RelativeSource AncestorType=ListViewItem}, Path=IsSelected}"
Content="{Binding Text}" Margin="0,5,0,0"
/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
And here's how we do stuff with the selected questions in that event handler. I'm guessing that your Strings collection was a member of the Window or whatever view you have; let me know if that's not the case and we'll figure out how to get to it. Remember, we're calling that collection Questions now.
private void AddToSurvey_Click(object sender, RoutedEventArgs e)
{
string allQuestionsText = "";
foreach (var question in Questions.Where(q => q.IsSelected))
{
// I don't know what you really want to do in here, but it sounds like you do.
allQuestionsText += question.Text + "\n";
}
}
I have one problem using TreeView in WPF using the MVVM design pattern.I found a solution that helped me a lot for understanding the principals, but when I applied it in my project, I can't get the desired results. The article is this one.
My base class is:
public class ClientTreeViewProducts
{
public List<ClientTreeViewProducts> Items { get; set; }
public string Name { get; set; }
}
In the ViewModel I have a public property that I bind to the ItemsSource property of the TreeView:
private List<ClientTreeViewProducts> _treeViewSource;
public List<ClientTreeViewProducts> TreeViewSource
{
get { return _treeViewSource; }
set
{
if(_treeViewSource!=value)
{
_treeViewSource = value;
RaisePropertyChanged("TreeViewSource");
}
}
}
I have a function that fills the List, and it seems to be OK.
And finally here is my xaml that manages the bindings and the HierarchicalDataTemplate :
<TreeView ItemsSource="{Binding TreeViewSource}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type helpers:ClientTreeViewProducts}"
ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The problem is when I fill some data in the TreeViewSource property, the tree view remains empty (visually). Can someone find what the problem is, because I'm head-banging about this problem for 3 days.
Thank you in advance!