EssentialWPF_chapter6_Data
Resource:
Static Resources is static assignment, just can be used once.
Dynamic resource
The lookup path for resources is
1. Element hierarchy
2. Application.Resources
3. Type theme
4. System theme
A resource needs to be used more than once: FrameworkElementFactory. For elements
in control templates, we create a factory instead of creating the elements
directly.
Most visual objects (brushes, pens, meshes, etc.) don’t require a
factory, because they support multiuse by virtue of deriving from
Freezable.3
(Freezable objects support sharing by having a “frozen” mode, in which they cannot be
changed. The frozen mode allows for a single instance to be used by multiple objects, without
consumers having to watch for changes in the object)
<Button Background='{DynamicResource toShare}' Click='Clicked'>
this.Resources["toShare"] = newBrush;
button1.SetResourceReference(
Button.BackgroundProperty,
"toShare");
We can explicitly set the
DataContext property on any element and that data source will be used for
any bindings on that element (or its children).
Panel1.DataContext = new Name();
When any resource is changed, the entire tree is updated. Therefore,
static and dynamic resource references can be used in many places without
a cost per reference being incurred. What this means is that we should not
make frequent changes of resources to update the UI. It also means that we
should not worry about using lots of resource references.
resources are optimized for coarse-grained changes.
Basic Binding:
Binding bind = new Binding();
bind.Source = textBox1;
bind.Path = new PropertyPath("Text");
contentControl1.SetBinding(ContentControl.Content, bind);
<ContentControl Margin='5' x:Name='contentControl1'
Content='{Binding ElementName=textBox1,Path=Text}' />
ValueConverter
public class HumanConverter : IValueConverter {
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture) {
Human h = new Human();
h.Name = (string)value;
return h;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture) {
return ((Human)value).Name;
}
}
<ContentControl Margin="5" BorderThickness="3">
<ContentControl.Content >
<Binding ElementName="textbox1" Path="Text">
<Binding.Converter>
<l:HumanConverter></l:HumanConverter>
</Binding.Converter>
</Binding>
</ContentControl.Content>
<ContentControl.ContentTemplate >
<DataTemplate DataType="{x:Type l:Human}">
<Border Margin='5' Padding='5' BorderBrush='Black' BorderThickness='3' CornerRadius='5'>
<TextBlock Text='{Binding Path=Name}' />
</Border>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
Property Change:
To enable our Person object model to support change notification,
we have three options: (1) implement INotifyPropertyChanged,
(2) Add events that report changes to properties, or (3) create Dependency-
Property-based properties.
Generally, when creating data models we should implement INotify-
PropertyChanged. Using DependencyProperty-based properties adds the
requirement of deriving from DependencyObject, which in turn ties the
data object to an STA thread.
public class Name : INotifyPropertyChanged
{
private string _first;
public string First
{
get { return _first; }
set { _first = value; NotifyChanged("First"); }
} private void NotifyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this,new PropertyChangedEventArgs(property));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
<StackPanel.DataContext >
<l:Name x:Name="myName" First="daf" Last="ddfdf"></l:Name>
</StackPanel.DataContext>
<Label Grid.Row='0' Grid.Column='0'>First</Label>
<TextBox Grid.Row='0' Grid.Column='1'
Text='{Binding Path=First}' />
<Label Grid.Row='1' Grid.Column='0'>Last</Label>
<TextBox Grid.Row='1' Grid.Column='1'
Text='{Binding UpdateSourceTrigger=PropertyChanged,Path=Last}' />
bind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
Lists are more complicated than just property changes:
INotifyCollectionChanged
public class NotifyCollectionChangedEventArgs : EventArgs {
public NotifyCollectionChangedAction Action { get; }
public IList NewItems { get; }
public int NewStartingIndex { get; }
public IList OldItems { get; }
public int OldStartingIndex { get; }
}
public enum NotifyCollectionChangedAction {
Add,
Remove,
Replace,
Move,
Reset,
}
The ObservableCollection<T> has inherited INotifyCollectionChanged, so we can use it directly.
public class Persion
{
private IList<Address> addresses = new ObservableCollection<Address>();
public IList<Address> Addresses
{
get { return addresses; }
set { addresses = value; }
}
}
public class Address:INotifyPropertyChanged
<StackPanel.Resources >
<DataTemplate x:Key='addressTemplate'>
<StackPanel Orientation='Horizontal'>
<TextBlock Text='{Binding Path=Street1}' />
<TextBlock Text=',' />
<TextBlock Text='{Binding Path=City}' />
<TextBlock Text=',' />
<TextBlock Text='{Binding Path=State}' />
<TextBlock Text=',' />
<TextBlock Text='{Binding Path=Zip}' />
</StackPanel>
</DataTemplate>
</StackPanel.Resources>
<StackPanel.DataContext >
<l:Persion></l:Persion>
</StackPanel.DataContext>
<ListBox ItemsSource="{Binding Path =Addresses}" ItemTemplate="{DynamicResource addressTemplate}"> //这里我还在想用IValueConverter,用template
</ListBox>
<Button Click="AddButton">Add</Button>
private void AddButton(object sender, RoutedEventArgs e)
{
Address a = new Address();
a.Street1 = "street1";
a.City = "city";
a.State = "state";
a.Zip = "zip";
((Persion)(CollectionStackPanel.DataContext)).Addresses.Add(a);
}
XML Binding:
public class Window1 : Window {
public Window1() {
XmlDocument doc = new XmlDocument();
doc.LoadXml(...);
XmlDataProvider dataSource = new XmlDataProvider();
dataSource.Document = doc;
Binding bind = new Binding();
bind.Source = dataSource;
bind.XPath = "/Media/Book/@Title";
ListBox list = new ListBox();
list.SetBinding(ListBox.ItemsSourceProperty, bind);
Title = "XML Binding";
Content = list;
}
}
<Window x:Class='BookScratch.Window1'
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
Text='XML Binding'
DataContext='{DynamicResource dataSource}'
>
<Window.Resources>
<XmlDataProvider x:Key='dataSource'>
...
</XmlDataProvider>
</Window.Resources>
<ListBox
ItemsSource= '{Binding XPath=/Media/Book/@Title}' />
</Window>
DataTemplate:
data templates allow us to define how a piece of
data will appear.
The DataTemplate type resembles ControlTemplate quite a bit. They
both leverage FrameworkElementFactory to define the display tree. Control-
Template defines a display tree for a control, within which we use template
binding to wire up display properties to control properties. DataTemplate,
however, defines a display tree for a data item, within which we use data
binding to wire up display properties to data properties. DataTemplate also
automatically sets the data context of the display tree to be the template
data item.
public class Window1 : Window {
public Window1() {
XmlDocument doc = new XmlDocument();
doc.LoadXml(...);
XmlDataProvider dataSource = new XmlDataProvider();
dataSource.Document = doc;
Binding bind = new Binding();
bind.Source = dataSource;
bind.XPath = "/Media/Book";
ListBox list = new ListBox();
list.SetBinding(ListBox.ItemsSourceProperty, bind);
Title = "XML Binding";
Content = list;
}
}
///////////create our template
DataTemplate template = new DataTemplate();
template.DataType = typeof(XmlElement);
FrameworkElementFactory textFactory =
new FrameworkElementFactory(typeof(TextBlock));
Binding bind = new Binding();
bind.XPath="@Title";
textFactory.SetBinding(TextBlock.TextProperty, bind);
template.VisualTree = textFactory;
<Window ...
xmlns:sx='clr-namespace:System.Xml;assembly=System.Xml'
Title='XML Binding'
DataContext='{DynamicResource dataSource}'
>
<Window.Resources>
<XmlDataProvider x:Key='dataSource'>
...
</XmlDataProvider>
<DataTemplate x:Key='template' DataType='{x:Type sx:XmlElement}'>
<TextBlock Text='{Binding XPath=@Title}' />
</DataTemplate>
</Window.Resources>
<ListBox
ItemsSource= '{Binding XPath=/Media/Book }'
ItemTemplate='{DynamicResource template}' />
</Window>
DataTemplate Selector:
DataTemplateSelector provides a single method, SelectTemplate,
that allows us to perform any logic we want to determine which template
to return. We can find the template in the contained element (i.e., ListBox)
and return some hard-coded templates, or even dynamically create a template
for each item.
<Window ... Title='XML Binding'>
<Window.Resources>
<XmlDataProvider x:Key='dataSource'>
<x:XData>
<Media xmlns=''>
<Book Author='John' Title='Fish are my friends' />
<Book Author='Dave' Title='Fish are my enemies' />
<Book Author='Jane' Title='Fish are my food' />
<CD Artist='Jane' Title='Fish sing well' />
<DVD Director='John' Title='Fish: The Movie'>
<Actor>Jane</Actor>
<Actor>Dave</Actor>
</DVD>
</Media>
</x:XData>
</XmlDataProvider>
</Window.Resources>
<ListBox
ItemsSource =
'{Binding XPath=/Media/Book/@Title,Source={StaticResource dataSource}}'
/>
</Window>
public class LocalNameTemplateSelector : DataTemplateSelector {
public override DataTemplate SelectTemplate(object item,
DependencyObject container) {
XmlElement data = item as XmlElement;
if (data != null) {
return ((FrameworkElement)container).FindResource(data.LocalName)
as DataTemplate;
}
return null;
}
}
<Window.Resources>
<XmlDataProvider x:Key='dataSource'>
...
</XmlDataProvider>
<DataTemplate x:Key='Book' DataType='{x:Type sx:XmlElement}'>
...
</DataTemplate>
<DataTemplate x:Key='CD' DataType='{x:Type sx:XmlElement}'>
...
</DataTemplate>
<DataTemplate x:Key='DVD' DataType='{x:Type sx:XmlElement}'>
...
</DataTemplate>
</Window.Resources>
<ListBox
ItemsSource= '{Binding XPath=/Media/*}'>
<ListBox.ItemTemplateSelector>
<l:LocalNameTemplateSelector
xmlns:l='clr-namespace:EssentialWPF' />
</ListBox.ItemTemplateSelector>
</ListBox>
Hierarchical Binding:
<ListBox ItemsSource='{Binding}'> // a period (.) path can be used to bind to the current source. For example, Text=”{Binding}” is equivalent to Text=”{Binding Path=.}”.
<ListBox
ItemsSource='{Binding}' Width ="300">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text='{Binding Path=Name}' />
<ListBox Height='75' Width="250">
<!-- ItemsSource for the inner ListBox
is going to be the collection of
child items. -->
<ListBox.ItemsSource>
<Binding Path='.'>
<Binding.Converter>
<l:GetFileSystemInfosConverter />
</Binding.Converter>
</Binding>
</ListBox.ItemsSource>
<!-- The items inside of the inner ListBox
get to be just TextBlocks. -->
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
"{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/
Use Resource:
<StackPanel Name="filesStackPanel" xmlns:io='clr-namespace:System.IO;assembly=mscorlib'>
<StackPanel.Resources >
<DataTemplate DataType='{x:Type io:DirectoryInfo}'>
<StackPanel>
<TextBlock Text='{Binding Path=Name}' />
<ListBox>
<ListBox.ItemsSource>
<Binding Path='.'>
<Binding.Converter>
<l:GetFileSystemInfosConverter />
</Binding.Converter>
</Binding>
</ListBox.ItemsSource>
</ListBox>
</StackPanel>
</DataTemplate>
</StackPanel.Resources>
Wait! Where is the recursion? Data templates can be discovered
by the DataType property. This means that when ListBox sees an
item of type DirectoryInfo, it will automatically find the template that we
just defined. Within that template is a nested list box that will do the same
thing.
For a control, like TreeView or Menu, that natively supports hierarchy,
public class HierarchicalDataTemplate : DataTemplate {
public BindingBase ItemsSource { get; set; }
public DataTemplate ItemTemplate { get; set; }
public DataTemplateSelector ItemTemplateSelector { get; set; }
}
Datacontext 是一个directoryInfo[], 但是datatemplate用的时候好像直接作为DirectoryInfo,itemssource 接受collection value.是因为itemsSource 在一个一个调用item时,用directoryInfo 得template 来处理。
<HierarchicalDataTemplate DataType='{x:Type io:DirectoryInfo}'>
<HierarchicalDataTemplate.ItemsSource>
<Binding Path='.'>
<Binding.Converter>
<l:GetFileSystemInfosConverter />
</Binding.Converter>
</Binding>
</HierarchicalDataTemplate.ItemsSource>
<TextBlock Text='{Binding Path=Name}' />
</HierarchicalDataTemplate>
</StackPanel.Resources>
<TreeView ItemsSource='{Binding}'/>
Collection Views:
Up to now we have talked about three objects in play to perform binding:
the data source, the binding, and the target element. With list binding
there is actually a fourth player: the collection view.
We can think of the collection view as a lightweight wrapper on the underlying data that allows us to have multiple “views” on top of the same data.
with larger data sets it
becomes increasingly important not to load the data into memory more
than once.
同一组数据,不同的view。
The most important service that a collection view supplies is currency
management, tracking the current item in a list. IsSynchronizedWithCurrentItem
This property synchronizes the list selection with the current item in the view.