WPFToolkit AutoComplete, styling the textbox & dropdown list - wpf

I have the following code creating a AutoCompleteBox (using WPFToolkit) that combines Companies and Employees into a single search feature. I am forced to use an ObservableCollection as the AutoCompleteBox does not know how to handle CompositeCollections (at least according to what I could find on the subject).
I would like to style the dropdown and textbox such that a Company's text is red and an Employee's text is green. I can set the text color for the entire dropdown as shown in the ResourceDictionary below but cannot find a way to use the DataTrigger (what I have below does not work).
I am not sure how to Bind the DataTrigger to the ModelType property of each object in SearchCollection.
EDIT:
After looking at the code again I realize that there is not a Property called ModelType exposed in the VM, the ModelType property is on each of the objects in SearchCollection. How can I bind to the ModelType of each object in the collection?
In XAML:
<toolkit:AutoCompleteBox Name="OmniSearchTextBox"
ItemsSource="{Binding SearchCollection}"
SelectedItem="{Binding Selection, Mode=TwoWay, UpdateSourceTrigger=Explicit}"
KeyUp="OmniSearch_KeyUp"
MouseLeftButtonUp="OmniSearch_MouseLeftButtonUp"
IsTextCompletionEnabled="False"
FilterMode="Contains"
VerticalAlignment="Top" Margin="10,0" >
In VM:
public sealed class SearchViewModel : ViewModelBase
{
private ViewModelLocator _vmLocator;
private ObservableCollection<Company> _companyList;
private ObservableCollection<Employee> _employeeList;
/// <summary>
/// Initializes a new instance of the SearchViewModel class.
/// </summary>
public SearchViewModel()
{
try
{
_vmLocator = new ViewModelLocator();
_searchCompCollection.Add(companies);
_searchCompCollection.Add(employees);
foreach (Company co in CompanyList)
{
_searchCollection.Add(co);
}
foreach (Employee emp in EmployeeList)
{
_searchCollection.Add(emp);
}
}
catch (Exception ex)
{
}
}
private const string CompanyListPropertyName = "CompanyList";
public ObservableCollection<Company> CompanyList
{
get
{
return (_vmLocator.CompanyVM).CompanyList;
}
set
{
if (_companyList == value)
{
return;
}
_companyList = value;
RaisePropertyChanged(CompanyListPropertyName);
}
}
private const string EmployeeListPropertyName = "EmployeeList";
public ObservableCollection<Employee> EmployeeList
{
get
{
return (_vmLocator.EmployeeVM).EmployeeList;
}
set
{
if (_employeeList == value)
{
return;
}
_employeeList = value;
RaisePropertyChanged(EmployeeListPropertyName);
}
}
private ObservableCollection<ModelBase> _searchCollection = new ObservableCollection<ModelBase>();
public ObservableCollection<ModelBase> SearchCollection
{
get { return _searchCollection; }
}
private string _selection = null;
private string _origSelection = null;
public string Selection
{
get
{
return _selection;
}
set
{
if (_selection != value)
{
_origSelection = _selection;
_selection = value;
try
{
var item = _searchCollection.Single<Object>(x => x.ToString() == _selection);
this.SelectedObject = item;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
}
private object _selectedObject = null;
private object _origSelectedObject = null;
public object SelectedObject
{
get {
return _selectedObject;
}
set
{
if (_selectedObject != value)
{
_origSelectedObject = _selectedObject;
_selectedObject = value;
switch(_selectedObject.GetType().BaseType.Name)
{
case "Company":
RaisePropertyChanged("SelectedObject",
(Company)_origSelectedObject,
(Company)_selectedObject,
true);
break;
case "Employee":
RaisePropertyChanged("SelectedObject",
(Employee)_origSelectedObject,
(Employee)_selectedObject,
true);
break;
default:
break;
}
RaisePropertyChanged("SelectedObject", _origSelectedObject, _selectedObject, true);
}
}
}
}
In ResourceDictionary (ModelType is a property available in each Model that simply returns the class type, i.e., Company or Employee):
<Style TargetType="{x:Type toolkit:AutoCompleteBox}">
<Setter Property="Foreground" Value="DarkCyan" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SearchCollection.ModelType}"
Value="Company">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=SearchCollection.ModelType}"
Value="Employee">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>

Related

Bind object to combobox and textbox

I have the following model:
public class Tag : ObservableObject
{
private int _id = -1;
public int Id
{
get { return _id; }
set
{
if (value != _id)
{
_id = value;
OnPropertyChanged("Id");
}
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
private string _freeText;
public string FreeText
{
get { return _freeText; }
set
{
if (value != _freeText)
{
_freeText = value;
OnPropertyChanged("FreeText");
}
}
}
}
public class Item : ObservableObject
{
// ...
private Tag _tag;
public Tag Tag
{
get { return _tag; }
set
{
if (value != _tag)
{
_tag= value;
OnPropertyChanged("Tag");
}
}
}
}
The following View Model:
public class AppViewModel : ViewModelBase
{
private Item _item;
public Item Item
{
get { return _item; }
set
{
if (_item != value)
{
_item = value;
OnPropertyChanged("Item");
}
}
}
private List<Tag> _tags;
public List<Tag> Tags
{
get { return _tags; }
set
{
if (_tags != value)
{
_tags = value;
OnPropertyChanged("Tags");
}
}
}
//...
}
Where my Tags list is populated with the following data:
Tags = new List<Tag>()
{
new Tag()
{
Id = 0,
Name = "Free Text"
},
new Tag()
{
Id = 1,
Name = "Tag 1"
},
new Tag()
{
Id = 2,
Name = "Tag 2"
},
new Tag()
{
Id = 3,
Name = "Tag 3"
}
}
And the following View:
<ComboBox x:Name="cmbTags"
ItemsSource="{Binding Tags}"
DisplayMemberPath="Name"
SelectedItem="{Binding Item.Tag}"/>
<TextBox x:Name="txtTagFreeText" Text="{Binding ElementName=cmbTags, Path=SelectedItem.FreeText}">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cmbTags, Path=SelectedItem.Id}" Value="0">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
What I want to do is to have the ComboBox Items Source binded to my Tags list, and the SelectedItem binded to my Item. I managed to set that up.
Then, if the Tag with the Id 0 is selected, I want to show the TextBox, that is also set up.
What I am having trouble with, is to bind the Text of the TextBox, with the Property FreeText of my Item.Tag.
The binding is kinda working, but it's being applied to all my Items, so whenever I change my Item, the TextBox will allways show my last input.
I have also tried to change the TextBox Text binding to:
{Binding Item.Tag.FreeText}
But the I have the same problem.
Anyone knows what I am doing wrong?
Do I need to post more code?
Any help or feedback is appreciated.
Thank you.
You are binding to a null instance the Item Property of the AppViewModel should Initialize with an instance, but this will not solve the problem you should bind the ComboBox with an other Property in the ViewModel lets name it SelectedTag in the setter of, handle the logic that you want.
public class AppViewModel : ObservableObject
{
private Item _item = new Item();
public Item Item
{
get { return _item; }
set
{
if (_item != value)
{
_item = value;
OnPropertyChanged("Item");
}
}
}
private Tag _SelectedTag;
public Tag SelectedTag
{
get { return _SelectedTag; }
set
{
if (_SelectedTag != value)
{
_SelectedTag = value;
OnPropertyChanged("SelectedTag");
Item.Tag = _SelectedTag;
if (_SelectedTag.Id == 0)
{
_SelectedTag.FreeText = "";
}
}
}
}
private List<Tag> _tags;
public List<Tag> Tags
{
get { return _tags; }
set
{
if (_tags != value)
{
_tags = value;
OnPropertyChanged("Tags");
}
}
}
// ...
}
and the XAML Code:
<ComboBox x:Name="cmbTags"
ItemsSource="{Binding Tags}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedTag,Mode=TwoWay}"/>
<TextBox Text="{Binding SelectedTag.FreeText,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedTag.Id}" Value="0">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
UPDATE
An other way you could bind directly by listening on the Item PropertyChanged Event when the Tag changed Raise Tag Property on the AppViewModel and do you logic.
public class AppViewModel : ObservableObject
{
private Item _item;
public Item Item
{
get { return _item; }
set
{
if (_item != value)
{
_item = value;
_item.PropertyChanged -= _item_PropertyChanged;
_item.PropertyChanged += _item_PropertyChanged;
OnPropertyChanged("Item");
}
}
}
private List<Tag> _tags;
public List<Tag> Tags
{
get { return _tags; }
set
{
if (_tags != value)
{
_tags = value;
OnPropertyChanged("Tags");
}
}
}
public AppViewModel()
{
Item = new Item();
Tags = new List<Tag>()
{
new Tag()
{
Id = 0,
Name = "Free Text"
},
new Tag()
{
Id = 1,
Name = "Tag 1"
},
new Tag()
{
Id = 2,
Name = "Tag 2"
},
new Tag()
{
Id = 3,
Name = "Tag 3"
}
};
}
private void _item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Item.Tag))
{
if (Item.Tag.Id == 0)
{
Item.Tag.FreeText = string.Empty;
}
OnPropertyChanged(nameof(Item));
}
}
}
And bind the ComboBox with The Item Tag directly
<TextBox x:Name="txtTagFreeText" Text="{Binding Item.Tag.FreeText,Mode=TwoWay}">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Item.Tag.Id}" Value="0">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>

How to pass <collection> as a parameter to constructor of the Markup extension

I have enum which i need to bind to combobox.
public enum RuleType
{
Default,
Required,
Hide,
Disable,
Validate,
Calculate,
Maxlength,
Trigger
}
// array of this class is return type of extension
public class EnumMember
{
public Object Value { get; set; }
public string Discription { get; set; }
}
And I have witten markup extension to bind above enum directly in the xaml:
[MarkupExtensionReturnType(typeof(Array))]
public class EnumValuesExtension : MarkupExtension
{
public EnumValuesExtension()
{
}
public EnumValuesExtension(Type enumType)
{
if (enumType == null)
throw new ArgumentNullException("enumType");
this.EnumType = enumType;
// this.Excluded = filterCollection;
}
public EnumValuesExtension(Type enumType, RuleType filterCollection)
{
if (enumType == null)
throw new ArgumentNullException("enumType");
this.EnumType = enumType;
this.Excluded = filterCollection;
}
public Type EnumType
{
get { return _enumType; }
set
{
if (_enumType == value)
return;
_enumType = value;
}
}
// Returning all elements of enum
public override object ProvideValue(IServiceProvider serviceProvider)
{
return from Object enumValue in Enum.GetValues(EnumType)
select new EnumMember
{
Value = enumValue,
Discription = enumValue.ToString()
};
}
}
}
XAML Binding is:
// "FilterDefaultElement" is Dependency Property which retrieve all enum elements.
<Style x:Key="filterRuleCombo" TargetType="{x:Type ComboBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=radio0,Path=IsChecked}" Value="True">
<Setter Property="ItemsSource"
Value="{Binding FilterDefaultElement}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<ComboBox Grid.Column="0" Grid.Row="1" Style="{StaticResource filterRuleCombo}"
DisplayMemberPath="Discription" Margin="0,5,0,100">
</ComboBox>
My Question is:
I want to write extension constructor like
public EnumValuesExtension(Type enumType, ObservableCollection<EnumMember> obj)
{
}
which should able to accept collection parameter.
I am able to pass only one argument to constructor.
How can i pass "ObservableCollection obj" as a parameter to the constuctor of the markup extension?

WPF DataGrid CanUserAddRows = True

I seem to have a problem with adding rows to a DataGridthrough the interface itself.
Here is a screenshot of the UI:
As you can see, 0 rows were found in the database so nothing shows up in the DataGrid on the right side.
But id like there to be one empty row there, for manually adding rows.
The DataGrid.CanUserAddRows is set to True but has no effect.
Here is the xaml for the DataGrid, I have taken the liberty of removing some of the code to make it smaller.
PrivilegeDetailsView.xaml
<UserControl ...
d:DataContext="{d:DesignInstance impl:PrivilegeDetailsViewModel}">
<DataGrid ...
ItemsSource="{Binding RolesHasPrivilegesOnObjects}"
AutoGenerateColumns="False"
CanUserAddRows="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Type" CanUserSort="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type int:IRoleHasPrivilegeOnObjectListItemViewModel}">
<Image Source="{Binding Icon}" ToolTip="{Binding ToolTip}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="*" Header="Name" Binding="{Binding Name}"/>
<DataGridCheckBoxColumn Header="Select" Binding="{Binding HasSelect, UpdateSourceTrigger=PropertyChanged}">
<DataGridCheckBoxColumn.ElementStyle>
<Style TargetType="CheckBox">
<Style.Triggers>
<DataTrigger Binding="{Binding CanHaveSelect}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</DataTrigger>
<DataTrigger Binding="{Binding CanHaveSelect}" Value="False">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridCheckBoxColumn.ElementStyle>
</DataGridCheckBoxColumn>
...
</DataGrid.Columns>
</DataGrid>
</UserControl>
PrivilegeDetailsView.xaml.cs
public partial class PrivilegeDetailsView : IPrivilegeDetailsView
{
public PrivilegeDetailsView() { InitializeComponent(); }
public DataGrid PrivilegesOnObjectsDataGrid { get { return PrivilegeDataGrid; } }
public IViewModel ViewModel
{
get { return (IViewModel)DataContext; }
set { DataContext = value; }
}
}
Here is the ViewModel (VM) for the xaml View above:
PrivilegeDetailsViewModel.cs
public class PrivilegeDetailsViewModel : ViewModelBase, IPrivilegeDetailsViewModel
{
private readonly IEventAggregator _eventAggregator;
private readonly IPrivilegeViewModel _privilegeViewModel;
private readonly IRoleHasPrivilegeOnObjectViewModelAdapterRepository _roleHasPrivilegeOnObjectViewModelAdapterRepository;
private ObservableCollection<IRoleHasPrivilegeOnObjectListItemViewModel> _rolesHasPrivilegesOnObjects;
public PrivilegeDetailsViewModel(IPrivilegeDetailsView view,
IRoleHasPrivilegeOnObjectViewModelAdapterRepository roleHasPrivilegeOnObjectViewModelAdapterRepository,
IPrivilegeViewModel privilegeViewModel,
IEventAggregator eventAggregator) : base(view)
{
_roleHasPrivilegeOnObjectViewModelAdapterRepository = roleHasPrivilegeOnObjectViewModelAdapterRepository;
_privilegeViewModel = privilegeViewModel;
_eventAggregator = eventAggregator;
Initialize();
}
protected override sealed void Initialize()
{
_privilegeViewModel.PropertyChanged += PrivilegeViewModelOnPropertyChanged;
_eventAggregator.GetEvent<ToggleSelectPrivilegeEvent>().Subscribe(ToggleSelectPrivilege);
...
}
public new IPrivilegeDetailsView View
{
get { return (IPrivilegeDetailsView)base.View; }
}
public ObservableCollection<IRoleHasPrivilegeOnObjectListItemViewModel> RolesHasPrivilegesOnObjects
{
get { return _rolesHasPrivilegesOnObjects; }
set
{
_rolesHasPrivilegesOnObjects = value;
OnPropertyChanged();
}
}
public void Save()
{
if(RolesHasPrivilegesOnObjects == null) return;
_roleHasPrivilegeOnObjectViewModelAdapterRepository.SaveChanges(RolesHasPrivilegesOnObjects);
}
private void ToggleExecutePrivilege(object obj)
{
var toggle = !View.PrivilegesOnObjectsDataGrid.SelectedItems.Cast<IRoleHasPrivilegeOnObjectListItemViewModel>()
.All(x => x.HasExecute);
foreach(var selectedItem in View.PrivilegesOnObjectsDataGrid
.SelectedItems
.Cast<IRoleHasPrivilegeOnObjectListItemViewModel>()
.Where(selectedItem => selectedItem.Object
.CanHavePrivilege("EXECUTE"))) {
selectedItem.HasExecute = toggle;
}
}
...
private void PrivilegeViewModelOnPropertyChanged(object s, PropertyChangedEventArgs e)
{
switch(e.PropertyName)
{
//When the SelectedSchema changes in the parent VM, I get the new rows to be shown in the DataGrid.
case "SelectedSchema":
RolesHasPrivilegesOnObjects = _roleHasPrivilegeOnObjectViewModelAdapterRepository
.GetPrivilegesOnObjectsAssociatedWith((IRoleEntityViewModel)_privilegeViewModel.SelectedRole,
(IContainerEntityViewModel)_privilegeViewModel.SelectedSchema);
break;
}
}
}
This is the VM for each row in the DataGrid
RoleHasPrivilegeOnObjectEntityViewModel.cs
public class RoleHasPrivilegeOnObjectEntityViewModel : EntityViewModelBase<RoleHasPrivilegeOnObjectEntityViewModel,
RoleHasPrivilegesOnObject>,
IRoleHasPrivilegeOnObjectListItemViewModel
{
private readonly RoleHasPrivilegesOnObject _roleHasPrivilegesOnObject;
public RoleHasPrivilegeOnObjectEntityViewModel(RoleHasPrivilegesOnObject roleHasPrivilegesOnObject)
{
_roleHasPrivilegesOnObject = roleHasPrivilegesOnObject;
Role = new RoleEntityViewModel(_roleHasPrivilegesOnObject.Role);
Object = new ObjectEntityViewModel(_roleHasPrivilegesOnObject.Object);
}
public override EntityType EntityType { get { return EntityType.NONE; } }
public override RoleHasPrivilegesOnObject OriginalEntity { get { return _roleHasPrivilegesOnObject; } }
public IRoleEntityViewModel Role { get; set; }
public IObjectEntityViewModel Object { get; set; }
public string ToolTip { get { return _roleHasPrivilegesOnObject.ToolTip; } }
public bool HasExecute
{
get { return _roleHasPrivilegesOnObject.HasExecute; }
set
{
_roleHasPrivilegesOnObject.HasExecute = value;
OnPropertyChanged();
}
}
public bool CanHaveExecute { get { return _roleHasPrivilegesOnObject.CanHaveExecute; } }
public override string Icon { get { return Object != null ? Object.Icon : string.Empty; } }
public override string NAME
{
get { return _roleHasPrivilegesOnObject.NAME; }
set
{
_roleHasPrivilegesOnObject.NAME = value;
OnPropertyChanged();
}
}
...
}
I know this is a lot of code, I have stripped away a lot and put in place a few dots (...) to show that more code exist. NOTE: Im using EF5 and PRISM
How can I make the DataGrid accept new rows through the GUI?
I believe your problem is using ObservableCollection<IRoleHasPrivilegeOnObjectListItemViewModel> as ItemsSource. In order for DataGrid to be able to create a new row, there has to be a type that can be constructed with an empty constructor.
If you changed it to say ObservableCollection<RoleHasPrivilegeOnObjectEntityViewModel> instead, i'm pretty sure your rows will start getting added.
What I ended up doing was partially/Mostly what Maverik suggested.
I changed ObservableCollection<IRoleHasPrivilegeOnObjectListItemViewModel> to be ObservableCollection<RoleHasPrivilegeOnObjectEntityViewModel> and created a default constructor, which it didn't previously have.
The issue then was that the RoleHasPrivilegeOnObjectEntityViewModel needs some fields and properties set in order to function, so I created a public Initialize function to provided the necessary parameters.
I added an event handler to the DataGrid's InitializingNewItem event, where i called the Initialize function.
private void PrivilegesOnObjectsDataGridOnInitializingNewItem(object s, InitializingNewItemEventArgs e)
{
var newItem = e.NewItem as RoleHasPrivilegeOnObjectEntityViewModel;
if (newItem == null) return;
var role = _privilegeViewModel.SelectedRole;
var schema = _privilegeViewModel.SelectedSchema;
newItem.Initialize(role.OriginalEntity, schema.OriginalEntity);
}
When trying to adda new row, clicking the ComboBox didn't fire off the InitializeNewItem event. But clicking any other column fired off the InitializeNewItem event, and since at first each Row's VM had it's own AvailableObjectTypes property, the ComboBox ItemSource was not set if the ComboBox was selected before any other column, thus making it empty.
That was not an acceptable behaviour so moving AvailableObjectTypes to the PrivilegeDetailsViewModel and changing the ComboBox's ItemSource binding to this helped
ItemsSource="{Binding DataContext.AvailableObjectTypes, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"

WPF TreeView not being populated

I am trying to dynamically populate a WPF tree by using a ViewModel, however, for some reason it's not working. Either the bindings either aren't properly or I am messing up somewhere in code behind.
Here's a sample of what I have.
In XAML I define my TreeView like so...
<TreeView DockPanel.Dock="Left" Width="200" DataContext="{Binding MessageTree}" ItemsSource="{Binding MessageTree}">
<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.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="viewModel:Mail" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Subject}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
In Code Behing i have...
private Mail MessageTree { get; set; }
And
using (var mail = new MailParser())
{
int count = mail.GetMessageCount(DateTime.Today.AddDays(-10), DateTime.Today.AddDays(1));
MessageTree = new Mail();
for (int i = count - 1; i >= 0; i--)
{
MailMessage msg = mail.RetrieveMessage(i);
if (msg != null)
{
MessageTree.Add(msg);
}
if (backgroundWorker != null)
{
decimal perc = (100.0m - (((i + 1.0m)*100.0m)/count));
backgroundWorker.ReportProgress((int) perc, "Recebendo mensagens... " + perc.ToString("N2") + "%");
if (backgroundWorker.CancellationPending)
{
e.Cancel = true;
break;
}
}
}
}
Mail is defined as
public sealed class Mail : INotifyPropertyChanged
{
private readonly ObservableCollection<Mail> _children;
private readonly MailMessage _msg;
private readonly Mail _parent;
private bool _isExpanded;
private bool _isSelected;
public Mail()
{
_msg = new MailMessage {Subject = "Empty"};
_parent = null;
_children = new ObservableCollection<Mail>();
}
public Mail(MailMessage msg, Mail parent = null)
{
_msg = msg;
_parent = parent;
_children = new ObservableCollection<Mail>();
}
public IEnumerable<Mail> Children
{
get { return _children; }
}
public string Subject
{
get { return _msg.Subject; }
}
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value != _isExpanded)
{
_isExpanded = value;
OnPropertyChanged();
}
if (_isExpanded && _parent != null)
_parent.IsExpanded = true;
}
}
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void Add(MailMessage msg)
{
_children.Add(new Mail(msg, this));
OnPropertyChanged("Children");
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I can't find anything in it so different from examples found online that it wouldn't work. The Add method is incomplete, I still need some logic to decide whether to add them to the collection or to the collection of one of the collection members, but as is all my Mail objecys are beeing added to the collection but not showing up in the TreeView.
What totally obvious thing am i missing? Shouldn't the TreeView automaticly update as I add items to the collection?
What I want is for the TreeView to show The children of the MessageTree property, and those children's children.
EDIT: Couldn't see the whole thing on my phone - amended answer based on ability to actually see everything. :)
MOREEDIT: updated based on comments, let's start from scratch!
First off, if you're set on using the window/whatever as the datacontext, let's make it `INotifyPropertyChange...next, let's make "MessageTree" a collection of mails, not just a single one (it'll make binding semantics easier, trust me)
public class WhateverContainsTheTree : Window, INotifyPropertyChanged
{
public WhateverContainsTheTree()
{
this.Loaded += OnLoaded;
this._messageTree = new ObservableCollection<Mail>();
this.DataContext = this;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
_worker = new BackgroundWorker();
_worker.DoWork += WorkerWorkin;
_worker.RunWorkerAsync();
}
private BackgroundWorker _worker;
private ObservableCollection<Mail> _messageTree;
public ObservableCollection<Mail> MessageTree
{
get { return _messageTree; }
set { _messageTree = value; RaisePropertyChanged("MessageTree"); }
}
public event PropertyChangedEventHandler PropertyChanged = delegate {};
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private void WorkerWorkin(object sender, DoWorkEventArgs e)
{
// obviously, change this to your stuff; I added a ctor so I could pass a string
Thread.Sleep(3000);
Console.WriteLine("Ok, setting message tree");
Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Normal,
(Action)(() =>
{
var mail1 = new Mail("Mail#1:Changed from background thread");
var mail2 = new Mail("Mail#2:Submail of mail #1");
var mail3 = new Mail("Mail#3:Submail of mail #2");
var mail4 = new Mail("Mail#4:Submail of mail #1");
var mail5 = new Mail("Mail#5:Submail of mail #4");
mail1.Children.Add(mail2);
mail1.Children.Add(mail4);
mail2.Children.Add(mail3);
mail4.Children.Add(mail5);
MessageTree.Add(mail1);
})
);
}
}
Also, like I'd said in the original response, let's slightly tweak Mail.Children:
public ObservableCollection<Mail> Children
{
get { return _children; }
}
And here's what I used for the treeview xaml:
<TreeView DockPanel.Dock="Left" Width="200" ItemsSource="{{Binding MessageTree}}">
<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.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="viewModel:Mail" ItemsSource="{{Binding Children}}">
<TextBlock Text="{{Binding Subject}}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
If this STILL doesn't work, I'll just paste in the whole LINQPad blob I put together to test this.
Without seeing the entire setup, I'm not positive but my guess would be that since MessageTree is a plain CLR property (rather than something that raises PropertyChanged or a DependencyProperty or something, that the binding is occurring before your MessageTree = new Mail(); call. When you set it to a new instance, the binding system isn't getting notified since it is a plain property.
Another potential issue is that you say that code is in the code-behind. Just using that Binding syntax won't pick up a property from the code-behind. It's possible that you're setting that up somewhere else in the code that you didn't show us. But generally you aren't going to be binding from the View to the code-behind, you'd be binding to a ViewModel that was used as the DataContext for the view itself.
Had to give a name to the TreeView (Tree) and then after
MessageTree = new Mail();
insert
Tree.ItemSource = MessageTree.Children;
I find this ugly but at least it works now. Thank you all for trying to help.

I need Wpf tree view like this

ViewModel has 2 field. Name, Childs
I need like this
1. When click on the root element, do 2 operation
first. expand yourself
second. select first child. If child element has childs, repeat 1.
otherwise do nothing
Only last child (leaf) selectable
UPDATE
Figured out a much better way to do this. This will also account for changes in the ObservableCollection.
The Xaml can just look like this
<Window.Resources>
<HierarchicalDataTemplate x:Key="Level1"
ItemsSource="{Binding Path=Childs}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView ItemsSource="{Binding}"
...>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
And then we handle the IsSelected Property in the Model/ViewModel instead.
public class MyViewModel : INotifyPropertyChanged
{
private static MyViewModel s_lastSelectedTestItem = null;
public MyViewModel(string name)
{
Name = name;
m_isSelected = false;
Childs = new ObservableCollection<MyViewModel>();
Childs.CollectionChanged += new NotifyCollectionChangedEventHandler(TestItems_CollectionChanged);
}
void TestItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (IsSelected == true && Childs.Count > 0)
{
Childs[0].IsSelected = true;
IsExpanded = true;
}
}
public string Name
{
get;
set;
}
public ObservableCollection<MyViewModel> Childs
{
get;
set;
}
private bool m_isSelected;
public bool IsSelected
{
get
{
return m_isSelected;
}
set
{
m_isSelected = value;
if (m_isSelected == true)
{
if (s_lastSelectedTestItem != null)
{
s_lastSelectedTestItem.IsSelected = false;
}
s_lastSelectedTestItem = this;
if (Childs.Count > 0)
{
IsExpanded = true;
Childs[0].IsSelected = true;
m_isSelected = false;
}
}
OnPropertyChanged("IsSelected");
}
}
private bool m_isExpaned;
public bool IsExpanded
{
get
{
return m_isExpaned;
}
set
{
m_isExpaned = value;
OnPropertyChanged("IsExpanded");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
What about to capture select item event on the treeview and expand the first child of the selected item? It seems easy to do.

Resources