WPF Validation and Controlling TextBox Styles - wpf

I have build my own custom validation framework for WPF using attribute based validation. I am stuck on the last step which is to highlight the TextBox. Actually, it does highlight the textboxes but all the textboxes are dependent on a single property HasError.
public class RegistrationViewModel : ViewModel
{
[NotNullOrEmpty("FirstName should not be null or empty")]
public string FirstName { get; set; }
[NotNullOrEmpty("Middle Name is required!")]
public string MiddleName { get; set; }
[NotNullOrEmpty("LastName should not be null or empty")]
public string LastName { get; set; }
public bool HasError
{
get
{
**return Errors.Count > 0; // THIS IS THE PROBLEM**
}
}
}
And here is the XAML code:
<Style x:Key="textBoxStyle" TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=HasError}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
The problem with the above code is that it will highlight all the textboxes that uses "textBoxStyle" even though they are valid. This is because the HasError does not validate on individual property basis but as a whole.
Any ideas?
UPDATE 1:
The ViewModel contains the Errors collection:
public class ViewModel : ContentControl, INotifyPropertyChanged
{
public static DependencyProperty ErrorsProperty;
static ViewModel()
{
ErrorsProperty = DependencyProperty.Register("Errors", typeof(ObservableCollection<BrokenRule>), typeof(ViewModel));
}
public ObservableCollection<BrokenRule> Errors
{
get { return (ObservableCollection<BrokenRule>)GetValue(ErrorsProperty); }
set
{
SetValue(ErrorsProperty,value);
OnPropertyChanged("HasError");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
UPDATE 2:
My validation engine:
public bool Validate(object viewModel)
{
_brokenRules = new List<BrokenRule>();
// get all the properties
var properties = viewModel.GetType().GetProperties();
foreach(var property in properties)
{
// get the custom attribute
var attribues = property.GetCustomAttributes(typeof (EStudyValidationAttribute), false);
foreach(EStudyValidationAttribute att in attribues)
{
bool isValid = att.IsValid(property.GetValue(viewModel,null));
if(isValid) continue;
// add the broken rule
var brokenRule = new BrokenRule()
{
PropertyName = property.Name,
Message = att.Message
};
_brokenRules.Add(brokenRule);
}
}
var list = _brokenRules.ToObservableCollection();
viewModel.GetType().GetProperty("Errors").SetValue(viewModel,list,null);
return (_brokenRules.Count() == 0);
}

You can implement IDataErrorInfo interface in your ViewModels, and in XAML check attached properties Validation.HasError on elements for controls with validation error; it's better because it's standart mechanizm in .Net.
<Style x:Key="textBoxStyle" TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Validation.HasError}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
When binding to property in TextBoxes, you need to set binding ValidatesOnDataError property to true.
<TextBox x:Name="someTextBox" Text="{Binding Path=someProperty, ValidatesOnDataErrors=True}">
public class ViewModel : ContentControl, INotifyPropertyChanged,IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public string this[string propertyName]
{
get
{
return ValidateProperty(this,propertyName);
}
}
public string Error
{
get
{
return "";
}
}
}
You can even use your implemented validation method, but check validation by property.

You might be interested in the WPF Application Framework (WAF) and the sample applications BookLibrary and EmailClient. They are using the validation attributes from the DataAnnotations namespace together with the IDataErrorInfo interface. You need only two additional lines of code in the validated classes to get this work (e.g. BookLibrary.Domain / Book.cs).

Related

Howto do i get correct CommandParameter Binding with dynamic menus in wpf and mvvm

i'm implementet a dynamic menu service in my WPF Application. Every Command can or should have different CommandParameters.
The Problem:
With my solution to set the CommandParamter binding in xaml the CanExecute property from the command doesent update anymore.
What i have so far
I'm using mvvm-light and fody-propertychanged.
Here the menu class:
public class MyMenu : INotifyPropertyChanged
{
private ObservableCollection<MyMenu> myChildren;
public MyMenu()
{
myChildren = new ObservableCollection<MyMenu>();
}
public string Header { get; set; }
public string Image { get; set; }
public string CommandName { get; set; } //used to set the CommandParameter binding
public ICommand Command { get; set; }
public ObservableCollection<MyMenu> Children {
get
{
return myChildren;
}
private set
{
myChildren = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
This class is used by the MenuService:
public sealed class MenuService : INotifyPropertyChanged
{
private static readonly Lazy<MenuService> lazy = new Lazy<MenuService>(() => new MenuService());
public static MenuService Instance { get { return lazy.Value; } }
private ObservableCollection<MyMenu> myMainMenu;
public event PropertyChangedEventHandler PropertyChanged;
private MenuService()
{
myMainMenu = new ObservableCollection<MyMenu>();
}
public ObservableCollection<MyMenu> MainMenu
{
get
{
return myMainMenu;
}
private set
{
myMainMenu = value;
}
}
}
In the constructor of the viewmodel i get the instance of the MenuService and add some items:
private void AddMenuItems()
{
MyMenu OpenUserLoginMenuItem = new MyMenu
{
Header = "_Login",
Image = "./Icons/IconLogin.png",
Command = OpenSelectTestprocedureWindowCommand,
CommandName = "OpenUserLoginDialogCommand"
};
MyMenu OpenSelectTestprocedureMenuItem = new MyMenu
{
Header = "_Select Testprocedure",
Image = "./Icons/IconSelectTestprocedure.png",
Command = OpenSelectTestprocedureWindowCommand,
CommandName = "OpenSelectTestprocedureWindowCommand"
};
MainMenu.Add(OpenUserLoginMenuItem);
MainMenu.Add(OpenSelectTestprocedureMenuItem);
}
Then i have a bindable property in the viewmodel:
public ObservableCollection<MyMenu> MainMenu
{
get
{
return myMenuService.MainMenu;
}
}
Here the command implementation as RelayCommand:
//in the constructor
OpenSelectTestprocedureWindowCommand = new RelayCommand<ShowTestschrittViewParameter>(OpenSelectTestablaufWindow, CanOpenSelectTestablaufWindow);
OpenUserLoginDialogCommand = new RelayCommand<Type>(OpenUserLoginDialog);
private void OpenUserLoginDialog(Type aWindowType)
{
myNavigationService.ShowWindowModal(aWindowType);
}
private bool CanOpenSelectTestablaufWindow(ShowTestschrittViewParameter showTestschrittViewParameter)
{
if (myDataService.CurrentTestProcedure != null)
{
if (myDataService.CurrentTestProcedure.TestProcedureState == Logic.Model.GlobalTypes.TestProcedureState.Running) return false;
}
return new ViewModelLocator().UserLoginDialogViewModel.User.NameIsValid;
}
private void OpenSelectTestablaufWindow(ShowTestschrittViewParameter showTestschrittViewParameter)
{
myNavigationService.ShowTestschrittView(showTestschrittViewParameter);
}
Then in the MainView i have the following xaml:
<Menu Grid.Row="2" ItemsSource="{Binding MainMenu}" Name="DynamicMenu">
<!--<Menu.ItemTemplate>
<DataTemplate DataType="{x:Type luih:MyMenu}">
<StackPanel>
<Label Content="{Binding Header}"/>
<Image Source="{Binding Image}"/>
</StackPanel>
</DataTemplate>
</Menu.ItemTemplate>-->
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="ItemsSource" Value="{Binding Children}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding CommandName}" Value="OpenUserLoginDialogCommand">
<Setter Property="CommandParameter" Value="{x:Type local:UserLoginDialog}"/>
</DataTrigger>
<DataTrigger Binding="{Binding CommandName}" Value="OpenSelectTestprocedureWindowCommand">
<Setter Property="CommandParameter" Value="{x:Type local:UserLoginDialog}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Menu.ItemContainerStyle>
</Menu>
Attention. The CommandParameter binding type in the xaml is currently not correct. This is another problem, i will solve by my self. But for testing purposes it should work. It will give me an exception because of wrong type.
But when i do the CommandParameter binding in the Style.Trigger with the DataTrigger, the CanExecute property doesent update anymore at runtime. When i'm comment this section out, everything works fine. But then i have no CommandParameters.
Any help and suggestions are welcome.
i'm found the problem.
RelayCommand from mvvm light evaluetes the type of the parameter for the CanExecute function. This must be the correct declared type or null.
So for testing purposes i had to set the binding like so:
<Style TargetType="{x:Type MenuItem}">
<Setter Property="CommandParameter" Value="{x:Null}"/>
</Style>

How to change the menu content by using DataTrigger in XAML?

I have two kinds of menu items according to the login. So, by using the property in the ViewModel Class
bool IsAdmin {get; set;}
I have to change the menu item content.I am not familiar with data template. I want to define all the menu items in the xaml itself (might be using the data templates).
How we can bind the differnt menu items by using the data trigger.
Can anyone can give a smaller example for this. using only this property and no c# codes.
Use ContentControl and Styles for max flexability in changing the view betwin Admin or not Admin views
<UserControl.Resources>
<!--*********** Control templates ***********-->
<ControlTemplate x:Key="ViewA">
<Views:AView/>
</ControlTemplate>
<ControlTemplate x:Key="ViewB">
<Views:BView />
</ControlTemplate>
</UserControl.Resources>
<Grid>
<ContentControl DataContext="{Binding}" Grid.Row="1">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Template" Value="{StaticResource ViewA}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsAdmin}" Value="True">
<Setter Property="Template" Value="{StaticResource ViewB}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl >
</Grid>
Please keep in mind that you will have to implement the INPC interface on your VM in order to be able to change the state (is admin or not) on the fly.If not the change will be accepted only once(on the creation of the class that is holding the IsAdmin property). Here is the INPC implementation example:
public class UserControlDataContext:BaseObservableObject
{
private bool _isAdmin;
public bool IsAdmin
{
get { return _isAdmin; }
set
{
_isAdmin = value;
OnPropertyChanged();
}
}
}
/// <summary>
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}

Validate Wpf datagrid for duplicate using IDataError

I have a datagrid which binded using ObservableCollection.Now I want to validate for duplicated entries in a cell against my entire collection.Iam using IDataError for validaton .But my problem how to get the Collection in IDataError object .
Edit
my xaml is:
<dg:DataGrid Name="dgPurchaseReturnEntry"
ItemsSource="{Binding}"
SelectionUnit="CellOrRowHeader"
>
<dg:DataGrid.Columns>
<dg:DataGridComboBoxColumn
Width="300"
Header="Product Name"
SelectedValueBinding="{Binding Path=Product_Id,UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Product_Id"
DisplayMemberPath="Product_Name"
ItemsSource="{Binding Source={StaticResource ProductDataProvider}}">
<dg:DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
<Setter Property="IsEditable" Value="True" />
</Style>
</dg:DataGridComboBoxColumn.EditingElementStyle>
</dg:DataGridComboBoxColumn>
</dg:DataGrid.Columns>
my object is:
public class clsPurchaseBillEntryList : INotifyPropertyChanged, IDataErrorInfo
{
private int _Product_Id;
#region Property Getters and Setters
public int Product_Id
{
get { return _Product_Id; }
set
{
_Product_Id = value;
OnPropertyChanged("Product_Id");
}
#endregion
#region INotifyPropertyChanged Members
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
//// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
#region IDataErrorInfo Members
public string Error
{
get
{
StringBuilder error = new StringBuilder();
// iterate over all of the properties
// of this object - aggregating any validation errors
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(this);
foreach (PropertyDescriptor prop in props)
{
string propertyError = this[prop.Name];
if (!string.IsNullOrEmpty(propertyError))
{
error.Append(propertyError);
}
}
return error.ToString();
}
}
public string this[string name]
{
get
{
string result = null;
if (name == "Qty")
{
}
return result;
}
}
}
Now How do I get my collection in my validation class that IDataError?
The ability to iterate through the items in a collection cannot and should not be done inside one of those objects/items. With this in mind, the standard IDataErrorInfo interface cannot help with this matter directly. However, it is possible to validate item uniqueness with a small customisation.
The basic idea is to add an ExternalErrors property into the data type bass class, or individual data type classes that can be use from outside the class. In this way, we can perform our uniqueness check in a view model or wherever your collection property has been defined and feed any errors that arise into the IDataErrorInfo interface functionality.
Rather than write out the whole story again, I'd advise you to take a look at an earlier answer that I provided which demonstrates this clearly. Please take a look at my answer to the Proper validation with MVVM question for more details. If you have any further questions, just let me know.

WPF Autogenerated DataGrid Cell changed event when bound to ItemSource

I have a simple datagrid which has columns autogenerated and is bound to an item source. This item source is updated at some intervals and I can't find how to fire an event for a single cell changed. I want to change the color of the cell based on if the update to the data source changed the previous value of the cell.
I looked at Highlighting cells in WPF DataGrid when the bound value changes as well as http://codefornothing.wordpress.com/2009/01/25/the-wpf-datagrid-and-me/ but I am still unsure about how to go about implementing this. Some example code would be really helpful to get started on the right path.
If you're binding to a DataTable, I don't think this would be a productive path to go down. Doing any kind of styling based on the contents of a DataTable bound DataGrid is nearly impossible in WPF. There are several suggestions on StackOverflow, but they are usually pretty hacky, event-driven (which is generally bad news in WPF), and a maintenance nightmare.
If however, the ItemsSource you are binding to is an ObservableCollection, where RowViewModel is the class that represents the data in a single row of the DataGrid, then it shouldn't be too bad. Make sure that RowViewModel implements INotifyPropertyChanged, and simply update the individual RowViewModels with their updated data. Then, you can add the logic to expose an additional property on your RowViewModel that indicates if a particular value is new - just use some styles/triggers in the XAML to set the background color based on the value of this new property.
Here's an example of the latter: If you edit one of values in the first column, it will turn the cell red. The same thing will happen if you change the value in your ItemViewModel programmatically via Database update.
The XAML:
<Window x:Class="ShowGridUpdates.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Item1, UpdateSourceTrigger=PropertyChanged}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Setters>
<Setter Property="Background" Value="Blue"/>
<Setter Property="Foreground" Value="White"/>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding Item1Changed}" Value="True">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Item2}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
The Code-Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
public class ViewModel : PropertyChangedNotifier
{
public ViewModel()
{
Items = new ObservableCollection<ItemViewModel>()
{
new ItemViewModel(){Item1="Item1FistValue", Item2="Item2FirstValue"},
new ItemViewModel(){Item1="whocareswhatvalue", Item2="Icertainlydont"}
};
//just to get the initial state correct
foreach (var item in Items)
{
item.Item1Changed = false;
}
}
private ObservableCollection<ItemViewModel> _items;
public ObservableCollection<ItemViewModel> Items
{
get
{
return _items;
}
set
{
_items = value;
OnPropertyChanged("Items");
}
}
}
public class ItemViewModel : PropertyChangedNotifier
{
private string _item1;
private string _item2;
private bool _item1Changed;
public bool Item1Changed
{
get
{
return _item1Changed;
}
set
{
_item1Changed = value;
OnPropertyChanged("Item1Changed");
}
}
public string Item1
{
get
{
return _item1;
}
set
{
if (_item1 != value)
Item1Changed = true;
else
Item1Changed = false;
_item1 = value;
OnPropertyChanged("Item1");
}
}
public string Item2
{
get
{
return _item2;
}
set
{
_item2 = value;
OnPropertyChanged("Item2");
}
}
}
public class PropertyChangedNotifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

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.

Resources