Some WPF binding questions
In my current project I have a need to display log output into a separate window but for whatever reason I'm not getting it to work. As I'm pretty new to WPF data binding I suspect the problem is me ;O).
Here's how it's supposed to work:
There's three view model classes, each representing a an aggregate for another (digested for clarity)
// The VM starting point, using a Singleton instance
// It holds a collection of log "sources", each representing a separate class' logging
internal sealed class ClassLogVM : DependencyObject
{
private readonly Dictionary<object, ClassLogSourceVM> _sourceIndex;
private readonly object _syncRoot;
/* dependency property initialization omitted */
private static ClassLogVM _s_singleton;
public static ClassLogVM Singleton
{
get { return _s_singleton ?? (_s_singleton = new ClassLogVM()); }
}
public IEnumerable<ClassLogSourceVM> Items
{
get { return (IEnumerable<ClassLogSourceVM>) GetValue(_s_itemsProp); }
set { SetValue(_s_itemsProp, value); }
}
private void classLogLogged(object sender, ClassLogEventArgs args)
{
VM.InvokeInUiThread(() =>
{
开发者_开发百科 ClassLogSourceVM source;
var caller = args.Caller == null ? "(unknown)" : args.Caller.ToString();
lock (_syncRoot)
if (!_sourceIndex.ContainsKey(caller))
{
source = new ClassLogSourceVM(caller);
((List<ClassLogSourceVM>) Items).Add(source);
_sourceIndex.Add(caller, source);
}
else
source = _sourceIndex[caller];
source.Add(new ClassLogItemVM(args.Timestamp, args.Message));
});
}
private ClassLogVM()
{
_syncRoot = new object();
_sourceIndex = new Dictionary<object, ClassLogSourceVM>();
Items = new List<ClassLogSourceVM>();
ClassLog.Logged += classLogLogged;
}
}
// represents a collection of log entries (see: Items property)
public class ClassLogSourceVM : DependencyObject
{
public string Name
{
get { return (string) GetValue(_s_nameProp); }
set { SetValue(_s_nameProp, value); }
}
public IEnumerable<ClassLogItemVM> Items
{
get { return (IEnumerable<ClassLogItemVM>)GetValue(_s_itemsProp); }
private set { SetValue(_s_itemsProp, value); }
}
public void Add(ClassLogItemVM item)
{
((List<ClassLogItemVM>)Items).Add(item);
}
public override string ToString()
{
return GetValue(_s_nameProp) + " (Count: " + Items.Count() + ")";
}
public ClassLogSourceVM(string name)
{
if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
SetValue(_s_nameProp, name);
Items = new List<ClassLogItemVM>();
}
}
// represents an individual log entry
public sealed class ClassLogItemVM : DependencyObject
{
public DateTime Timestamp
{
get { return (DateTime) GetValue(_s_timestampProp); }
}
public string Message
{
get { return (string) GetValue(_s_messageProp); }
set { SetValue(_s_messageProp, value); }
}
public override string ToString()
{
return Timestamp.ToString("HH:mm:ss:fff") + ": " + Message;
}
public ClassLogItemVM(DateTime timestamp, string message)
{
SetValue(_s_timestampProp, timestamp);
SetValue(_s_messageProp, message);
}
}
This is how I set up the XAML (user control called "ClassLogView") ...
<UserControl x:Class="GeDevelop.GVSViewer.Views.Debug.ClassLogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:Debug="clr-namespace:GeDevelop.GVSViewer.ViewModel.Debug"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
DataContext="{Binding Debug:ClassLogVM.Singleton.Items}">
<UserControl.Resources>
<CollectionViewSource x:Key="sources" Source="{Binding}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Source" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<DataTemplate DataType="{x:Type Debug:ClassLogSourceVM}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</UserControl.Resources>
<Grid d:DataContext="{Binding Debug:ClassLogVM.Singleton}">
<TreeView ItemsSource="{Binding Source={StaticResource sources}}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type Debug:ClassLogSourceVM}" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
The result is the Treeview is empty.
Now, my questions are: 1. What have I missed, causing the TreeView to display nothing? 2. I need to programmatically assign the DataContext to the user control despite the fact I have stated it in the tag. Why?
Would appreciate an explanation. WPF sure isn't for the faint hearted... ;O)
as far as I know that's not how you create DependencyProperties. DependencyObject is the core/base class of most of the UIElements, i would be very cautious to base my class on it.
Google or search in StackOverflow for DependencyProperty if you really want to use it.
In most cases, wrapping your DomainModel/BusinessObject with INotifyPropertyChanged is more than enough to get WPF binding pick it up
精彩评论