最近老没有时间上来写博文。今天有空来写写上次还没有说完的话题。上一篇提到说说我在WP7应用开发中遇到的 子控件 DataTemplete 中的按钮的命令绑定,刚开始接触似乎是个头疼的问题。那怎么解决呢?
我们仔细想想 silverlight 就是一个庞大的Composite组合模式的实现。包括了我们所说的可视树。不管怎么加Templete,怎么绑定,最终会出现在可视树上。 好就这么探索去吧。。
你会发现Silverlight, WP7中有类似功能的一些绑定中的 RelativeSource。但好像只是预留的接口,有些没有实现。
首先,观看 silverlight4中有未实现完整的相对绑定。
那我们自己来做吧!我们一些基础建设也抄他们的,呵呵
首先定义一些基础枚举:
public enum RelativeSourceMode
{
ParentDataContext = 0,
FindAncestor = 1
}
}
肯定需要一些相当绑定数据信息,同样参考一下Silverlight4
绑定可能会在同一个控件绑定几个事件,属性所以需要一个集合来管理吧,好做一个简单的。
怎么能给控件一个绑定自定义绑定属性呢? Attache 附加属性 是天生的绝配啊, 给注册一个附加Binding属性 让他可以绑定一个类
,所以做一个抽象的先让数据信息和集合都需继承,这样才能都实用啊。而集合又不需要其他的属性继承下得来,没有意义。 干脆搞个空类:
public abstract class RelativeSourceBase
{
protected RelativeSourceBase()
{
}
}
参考一下Silverlight4实现相当绑定数据收集工作。
public class RelativeSourceBinding : RelativeSourceBase
{
/// <summary>
/// Gets or sets the path to the binding source property.
/// </summary>
public string Path { get; set; }
/// <summary>
/// Gets or sets the name of the target dependency property.
/// </summary>
public string TargetProperty { get; set; }
/// <summary>
/// Gets or sets the XAML namespace. This namespace is used to get the class of an attached dependency property.
/// </summary>
public string TargetNamespace { get; set; }
/// <summary>
/// Gets or sets the type of ancestor to look for.
/// Define the full name (namespace and class name). Xaml Namespace do not work here.
/// Example: MyNamespace.MyUserControl or System.Windows.ListBox.
/// For types in System.Windows.dll you can just use the class name instead of the full name.
/// </summary>
public string AncestorType { get; set; }
// not implemented yet
//public int AncestorLevel { get; set; }
/// <summary>
/// Gets or sets a value that describes the location of the binding source relative
/// to the position of the binding target.
/// </summary>
public RelativeSourceMode RelativeMode { get; set; }
/// <summary>
/// Gets or sets a value that indicates the direction of the data flow in the binding.
/// </summary>
public BindingMode BindingMode { get; set; }
/// <summary>
/// Gets or sets the converter object that is called by the binding engine to
/// modify the data as it is passed between the source and target, or vice versa.
/// </summary>
public IValueConverter Converter { get; set; }
/// <summary>
/// Gets or sets the culture to be used by the System.Windows.Data.Binding.Converter.
/// </summary>
public CultureInfo ConverterCulture { get; set; }
/// <summary>
/// Gets or sets a parameter that can be used in the System.Windows.Data.Binding.Converter logic.
/// </summary>
public object ConverterParameter { get; set; }
/// <summary>
/// Gets or sets a value that indicates whether the System.Windows.FrameworkElement.BindingValidationError event is raised on validation errors.
/// </summary>
public bool NotifyOnValidationError { get; set; }
/// <summary>
/// Gets or sets a value that indicates whether the binding engine will report
/// validation errors from an System.ComponentModel.IDataErrorInfo implementation
/// on the bound data entity.
/// </summary>
public bool ValidatesOnDataErrors { get; set; }
/// <summary>
/// Gets or sets a value that indicates whether the binding engine will report
/// exception validation errors.
/// </summary>
public bool ValidatesOnExceptions { get; set; }
/// <summary>
/// Gets or sets a value that indicates whether the binding engine will report
/// validation errors from an System.ComponentModel.INotifyDataErrorInfo implementation
/// on the bound data entity.
/// </summary>
public bool ValidatesOnNotifyDataErrors { get; set; }
internal DependencyProperty TargetDependencyProperty { get; set; }
}
在搞个List来管理多个绑定,让其也继承基类
public class BindingList : RelativeSourceBase, IList<RelativeSourceBinding>, ICollection<RelativeSourceBinding>, IEnumerable<RelativeSourceBinding>, IList, ICollection, IEnumerable
{
private List<RelativeSourceBinding> _internalList;
public BindingList()
{
_internalList = new List<RelativeSourceBinding>();
}
#region IList<RelativeSourceBinding> Members
public int IndexOf(RelativeSourceBinding item)
{
return _internalList.IndexOf(item);
}
public void Insert(int index, RelativeSourceBinding item)
{
_internalList.Insert(index, item);
}
public void RemoveAt(int index)
{
_internalList.RemoveAt(index);
}
public RelativeSourceBinding this[int index]
{
get
{
return _internalList[index];
}
set
{
_internalList[index] = value; ;
}
}
#endregion
#region ICollection<RelativeSourceBinding> Members
public void Add(RelativeSourceBinding item)
{
_internalList.IndexOf(item);
}
public void Clear()
{
_internalList.Clear();
}
public bool Contains(RelativeSourceBinding item)
{
return _internalList.Contains(item);
}
public void CopyTo(RelativeSourceBinding[] array, int arrayIndex)
{
_internalList.CopyTo(array, arrayIndex);
}
public int Count
{
get { return _internalList.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(RelativeSourceBinding item)
{
return _internalList.Remove(item);
}
#endregion
#region IEnumerable<RelativeSourceBinding> Members
public IEnumerator<RelativeSourceBinding> GetEnumerator()
{
return _internalList.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _internalList.GetEnumerator();
}
#endregion
#region IList Members
int IList.Add(object value)
{
_internalList.Add((RelativeSourceBinding)value);
return (this.Count - 1);
}
void IList.Clear()
{
_internalList.Clear();
}
bool IList.Contains(object value)
{
return _internalList.Contains((RelativeSourceBinding)value);
}
int IList.IndexOf(object value)
{
return _internalList.IndexOf((RelativeSourceBinding)value);
}
void IList.Insert(int index, object value)
{
_internalList.Insert(index, (RelativeSourceBinding)value);
}
bool IList.IsFixedSize
{
get { return false; }
}
bool IList.IsReadOnly
{
get { return false; }
}
void IList.Remove(object value)
{
_internalList.Remove((RelativeSourceBinding)value);
}
void IList.RemoveAt(int index)
{
_internalList.RemoveAt(index);
}
object IList.this[int index]
{
get
{
return _internalList[index];
}
set
{
_internalList[index] = (RelativeSourceBinding)value;
}
}
#endregion
#region ICollection Members
void ICollection.CopyTo(Array array, int index)
{
throw new NotImplementedException();
}
int ICollection.Count
{
get { return _internalList.Count; }
}
bool ICollection.IsSynchronized
{
get { return false; }
}
object ICollection.SyncRoot
{
get { return _internalList; }
}
#endregion
}
数据搞定了,现在主攻逻辑实现了。。
1.实现附加属性。
2.分析附加属性绑定配置数据信息。
3.查找可视树找到对应UI元素
4.创建绑定。这是时候遇到了问题,需要多重"."属性分析。 还有绑定目标中是否通过名称怎么分析Type? 还好查资料有很多类型的分析代码。
[ContentProperty("Binding")]
public static class BindingAdapter
{
#region Binding (Attached DependencyProperty)
public static RelativeSourceBase GetBinding(DependencyObject obj)
{
return (RelativeSourceBase)obj.GetValue(BindingProperty);
}
public static void SetBinding(DependencyObject obj, RelativeSourceBase value)
{
obj.SetValue(BindingProperty, value);
}
public static readonly DependencyProperty BindingProperty = DependencyProperty.RegisterAttached("Binding", typeof(RelativeSourceBase), typeof(BindingAdapter), new PropertyMetadata(null, OnBinding));
private static void OnBinding(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
FrameworkElement targetElement = depObj as FrameworkElement;
if (targetElement != null)
{
// attach loading event
targetElement.Loaded += new RoutedEventHandler(targetElement_Loaded);
targetElement.Unloaded += new RoutedEventHandler(targetElement_Unloaded);
}
}
#endregion
#region Private methods
private static void targetElement_Loaded(object sender, RoutedEventArgs e)
{
try
{
FrameworkElement targetElement = sender as FrameworkElement;
// release handler to prevent memory leaks
targetElement.Loaded -= new RoutedEventHandler(targetElement_Loaded);
RelativeSourceBase bindings = GetBinding(targetElement);
if (bindings is RelativeSourceBinding)
{
// get the binding configuration
RelativeSourceBinding bindingConfiguration = bindings as RelativeSourceBinding;
ProcessBinding(targetElement, bindingConfiguration);
}
else if (bindings is BindingList)
{
// get the binding configuration
BindingList list = bindings as BindingList;
foreach (RelativeSourceBinding bindingConfiguration in list)
{
ProcessBinding(targetElement, bindingConfiguration);
}
}
}
catch (Exception ex)
{
// ignore this exception, because the SL binding engine does not throw exceptions when a binding is wrong.
}
}
static void targetElement_Unloaded(object sender, RoutedEventArgs e)
{
try
{
FrameworkElement targetElement = sender as FrameworkElement;
targetElement.Unloaded -= new RoutedEventHandler(targetElement_Unloaded);
RelativeSourceBase bindings = GetBinding(targetElement);
if (bindings is RelativeSourceBinding)
{
// get the binding configuration
RelativeSourceBinding bindingConfiguration = bindings as RelativeSourceBinding;
if (bindingConfiguration.TargetDependencyProperty != null)
targetElement.ClearValue(bindingConfiguration.TargetDependencyProperty);
}
else if (bindings is BindingList)
{
// get the binding configuration
BindingList list = bindings as BindingList;
foreach (RelativeSourceBinding bindingConfiguration in list)
{
if (bindingConfiguration.TargetDependencyProperty != null)
targetElement.ClearValue(bindingConfiguration.TargetDependencyProperty);
}
}
}
catch
{
}
}
private static void ProcessBinding(FrameworkElement targetElement, RelativeSourceBinding bindingConfiguration)
{
if (bindingConfiguration.RelativeMode == RelativeSourceMode.FindAncestor &&
!string.IsNullOrEmpty(bindingConfiguration.AncestorType))
{
// navigate up the tree to find the type
DependencyObject currentObject = VisualTreeHelper.GetParent(targetElement);
DependencyObject candidate = null;
DependencyObject ancestor = null;
while (true)
{
if (currentObject == null)
{
break;
}
Type currentType = currentObject.GetType();
while (currentType != null && currentType.IsSubclassOf(typeof(DependencyObject)))
{
if (currentType.FullName == bindingConfiguration.AncestorType)
{
ancestor = currentObject;
break;
}
// for types in assemblies System.Windows, System.Windows.Controls, System.Windows.Controls.Data, etc,
// its possible to define just the class name instead of the full class name including the namespace.
if (candidate == null && currentType.Name == bindingConfiguration.AncestorType && currentType.Assembly.FullName.StartsWith("System.Windows"))
{
// the name of the element is matching, but it is not the fullname.
// remeber the element in case if no element is matching to the ancestor type name
candidate = currentObject;
}
// next type up the hierarchy
currentType = currentType.BaseType;
}
// next parent
currentObject = VisualTreeHelper.GetParent(currentObject);
}
// concrete
if (ancestor == null)
{
ancestor = candidate;
}
if (ancestor != null && ancestor is FrameworkElement)
{
// bind them
CreateBinding(targetElement, ancestor, bindingConfiguration);
}
}
else if (bindingConfiguration.RelativeMode == RelativeSourceMode.ParentDataContext)
{
object currentDataContext = targetElement.DataContext;
// navigate up the tree to find the parent datacontext
DependencyObject currentObject = VisualTreeHelper.GetParent(targetElement);
while (true)
{
if (currentObject == null)
break;
FrameworkElement fe = currentObject as FrameworkElement;
if (fe != null)
{
if (fe.DataContext != null && fe.DataContext != currentDataContext)
{
// bind them
CreateBinding(targetElement, fe.DataContext, bindingConfiguration);
break;
}
}
// next parent
currentObject = VisualTreeHelper.GetParent(currentObject);
}
}
}
private static List<string> GetClassNames(Type type)
{
List<string> result = new List<string>();
// check
if (type == null && type.IsSubclassOf(typeof(DependencyObject)))
return result;
// process
do
{
result.Add(type.FullName);
type = type.BaseType;
} while (type != null && type.IsSubclassOf(typeof(DependencyObject)));
// return
return result;
}
private static void CreateBinding(FrameworkElement targetElement, object sourceElement, RelativeSourceBinding bindingConfiguration)
{
// input check
if (targetElement == null)
return;
if (sourceElement == null)
return;
if (bindingConfiguration == null)
return;
// check binding configuration
// ...target property must be set
if (bindingConfiguration.TargetProperty.IsNullOrWhiteSpace())
return;
// ...path property must be set
if (bindingConfiguration.Path.IsNullOrWhiteSpace())
return;
// support of attached property binding syntax: TargetProperty='(Grid.Row)'
string targetPropertyName = (bindingConfiguration.TargetProperty + "").Trim().TrimStart('(').TrimEnd(')') + "Property";
// find the target dependency property
DependencyProperty targetDependencyProperty = null;
if (targetPropertyName.Contains("."))
{
// it is an attached dependency property
string[] parts = targetPropertyName.Split('.');
if (parts.Length == 2 && !parts[0].IsNullOrWhiteSpace() && !parts[1].IsNullOrWhiteSpace())
{
Type attachedType = TypeLoader.GetType(parts[0], bindingConfiguration.TargetNamespace);
if (attachedType != null)
{
FieldInfo[] targetFields = attachedType.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
FieldInfo targetDependencyPropertyField = targetFields.FirstOrDefault(i => i.Name == parts[1]);
if (targetDependencyPropertyField != null)
targetDependencyProperty = targetDependencyPropertyField.GetValue(null) as DependencyProperty;
}
}
}
else
{
// it is a standard dependency property
FieldInfo[] targetFields = targetElement.GetType().GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
FieldInfo targetDependencyPropertyField = targetFields.FirstOrDefault(i => i.Name == targetPropertyName);
if (targetDependencyPropertyField != null)
targetDependencyProperty = targetDependencyPropertyField.GetValue(null) as DependencyProperty;
}
// set binding
if (targetDependencyProperty != null)
{
if (bindingConfiguration.TargetDependencyProperty != null
&& bindingConfiguration.TargetDependencyProperty != targetDependencyProperty)
targetElement.ClearValue(bindingConfiguration.TargetDependencyProperty);
bindingConfiguration.TargetDependencyProperty = targetDependencyProperty;
Binding binding = new Binding();
binding.Source = sourceElement;
binding.Path = new PropertyPath(bindingConfiguration.Path);
binding.Mode = bindingConfiguration.BindingMode;
binding.Converter = bindingConfiguration.Converter;
binding.ConverterParameter = bindingConfiguration.ConverterParameter;
binding.ConverterCulture = bindingConfiguration.ConverterCulture;
binding.NotifyOnValidationError = bindingConfiguration.NotifyOnValidationError;
#if !WINDOWS_PHONE
binding.ValidatesOnDataErrors = bindingConfiguration.ValidatesOnDataErrors;
#endif
binding.ValidatesOnExceptions = bindingConfiguration.ValidatesOnExceptions;
#if !WINDOWS_PHONE
binding.ValidatesOnNotifyDataErrors = bindingConfiguration.ValidatesOnNotifyDataErrors;
#endif
// set the binding on our target element
targetElement.SetBinding(targetDependencyProperty, binding);
}
}
#endregion
}
也把这个XMAL类型分析的小工具代码转给大家吧
/// <summary>
/// Provides functionality to load any type with its class name, namespace and assembly-name within the Silverlight environment.
/// </summary>
/// <remarks>
/// The Type.GetType method is different in Silverlight than in the standard .NET runtime. In Silverlight we have to provide the
/// fully qualified assembly name to get a type in a custom assembly. Only build in controls or types in the same assembly are
/// excluded from this rule. Full qualified assembly name means a syntax like the following:
/// MyComponent.MyType, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4bec85d7bec6698f.
/// This class uses the XamlReader capability to resolve type during parsing a xaml-string. While this is a little time consuming
/// the TypeLoader maintains a cache to get types faster.
/// </remarks>
public static class TypeLoader
{
// cache for resolved type
private static Dictionary<string, Type> _cache = new Dictionary<string, Type>();
/// <summary>
/// Gets the System.Type with the specified name, name space and assembly name.
/// </summary>
/// <param name="className">The class name without namespace.</param>
/// <param name="nameSpace">The name space</param>
/// <param name="assemblyName">The name of the assembly containing the type.</param>
/// <returns>The type matching the provided parameters or null if not found.</returns>
//[DebuggerStepThrough()]
public static Type GetType(string className, string nameSpace, string assemblyName)
{
// check
if (nameSpace.IsNullOrWhiteSpace())
return null;
string xamlNamespace = string.Format("clr-namespace:{0}", nameSpace);
// assembly name is optional
if (!assemblyName.IsNullOrWhiteSpace())
xamlNamespace += string.Format(";assembly={0}", assemblyName);
return GetType(className, xamlNamespace);
}
/// <summary>
/// Gets the System.Type with the specified name.
/// This method overload can be used for:
/// 1. core controls such as Button, Grid, ListBox, etc. without specifying the namespace or assembly name.
/// 2. with the qualified assembly name of the type without version and public key token like this: "MyNamespace.MyType, MyAssembly".
/// </summary>
/// <param name="className">Pure class name of Core Controls such as Button, Grid, ListBox, etc.</param>
/// <returns>The type matching the provided parameters or null if not found.</returns>
//[DebuggerStepThrough()]
public static Type GetType(string className)
{
if (className != null && className.Contains(","))
{
string[] qualifiedNameParts = className.Split(',');
if (qualifiedNameParts.Length == 2)
{
string[] fullClassNameParts = qualifiedNameParts[0].Split('.');
if (fullClassNameParts.Length > 0)
{
// classname
string newClassName = fullClassNameParts.Last().Trim();
// namespace
string nameSpace = "";
for (int i = 0; i < fullClassNameParts.Length - 1; i++)
{
nameSpace += fullClassNameParts[i] + ".";
}
nameSpace = nameSpace.TrimEnd('.');
string assemblyName = qualifiedNameParts[1].Trim();
return GetType(newClassName, nameSpace, assemblyName);
}
}
}
return GetType(className, "");
}
/// <summary>
/// Gets the System.Type with the specified name. The xaml namespace specifies the namespace and assembly name in the same syntax as in xaml.
/// </summary>
/// <param name="className">The class name without namespace.</param>
/// <param name="xamlNamespace">
/// The xaml namespace. This is the same syntax as used in XAML syntax.
/// Example: "clr-namespace:MyComponent.SubNamespace;assembly=MyAssemblyName
/// </param>
/// <returns>The type matching the provided parameters or null if not found.</returns>
//[DebuggerStepThrough()]
public static Type GetType(string className, string xamlNamespace)
{
// check input
if (className.IsNullOrWhiteSpace())
return null;
if (className.Contains("."))
throw new ArgumentException("className must not include the namespace. Please provide namespace with separate parameter.");
// check if type is already in cache
string key = xamlNamespace + "&" + className;
if (_cache.ContainsKey(key))
return _cache[key];
lock (_cache)
{
try
{
// check again because another thread might be faster and has already created the cache-entry
if (_cache.ContainsKey(key))
return _cache[key];
// create xaml with a simply Style element and set the TargetType property with the provided type name
string xaml = "<Style xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' ";
// set the xaml namesapce if provided
if (!xamlNamespace.IsNullOrWhiteSpace())
{
xaml += string.Format("xmlns:tmp='{0}' TargetType='tmp:{1}' />", xamlNamespace, className);
}
else
{
// Core controls such as Button, Grid, ListBox, etc do not need a namespace
xaml += string.Format("TargetType='{0}' />", className);
}
// let the XamlParser load the type via the TargetType property
Style style = XamlReader.Load(xaml) as Style;
if (style != null)
{
Type targetType = style.TargetType;
AddToCache(key, targetType);
return targetType;
}
}
catch (Exception ex)
{
// Try to load type in executing assembly
if (!xamlNamespace.IsNullOrWhiteSpace())
{
// note: Type.GetType uses needs assembly-qualified name of the type to get. If the type is
// in the currently executing assembly or in Mscorlib.dll, it is sufficient to supply
// the type name qualified by its namespace.
Type type = Type.GetType(string.Format("{0}.{1}", xamlNamespace.Replace("clr-namespace:", "").TrimEnd(';'), className));
if (type != null)
{
// add to cache
AddToCache(key, type);
return type;
}
}
//****** DONT SET VALUE TO NULL, BECAUSE OF CASES WHEN AN ASSEMBLY IS *****
//****** LOADED DYNAMICALLY INTO THE APPLICATION DOMAIN *****
// don't let the exception repeat. Set null as cache value
AddToCache(key, null);
//**************************************************************************/
}
}
return null;
}
private static void AddToCache(string key, Type type)
{
_cache.Add(key, type);
CompositionTarget.Rendering -= new EventHandler(CompositionTarget_Rendering);
CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
}
static void CompositionTarget_Rendering(object sender, EventArgs e)
{
CompositionTarget.Rendering -= new EventHandler(CompositionTarget_Rendering);
_cache.Clear();
}
}
至此快成功了试一下:
<my:CommandButton CommandParameter="{Binding}" >
<localBinding:BindingAdapter.Binding>
<localBinding:RelativeSourceBinding Path="ClickCommmand" TargetProperty="Command"
RelativeMode="ParentDataContext" />
</localBinding:BindingAdapter.Binding>
......................
<my:CommandButton CommandParameter="{Binding}" >
<localBinding:BindingAdapter.Binding>
<localBinding:RelativeSourceBinding Path="DataContext.ClickCommmand" TargetProperty="Command"
RelativeMode="FindAncestor" AncestorType="Grid" />
</localBinding:BindingAdapter.Binding>
.............
<ComboBox Grid.Column="1">
<local:BindingAdapter.Binding>
<local:BindingList>
<local:RelativeSourceBinding Path="DataContext.Picklist" TargetProperty="ItemsSource" RelativeMode="FindAncestor" AncestorType="UserControl" />
<local:RelativeSourceBinding Path="DataContext.Tooltip"
TargetProperty="(ToolTipService.ToolTip)" RelativeMode="FindAncestor"
AncestorType="UserControl" />
<local:RelativeSourceBinding Path="DataContext.Tooltip" TargetProperty="(DemoAttachedElement.Value)"
TargetNamespace="clr-namespace:********;assembly=*******" RelativeMode="FindAncestor" AncestorType="UserControl" />
</local:BindingList>
</local:BindingAdapter.Binding>
</ComboBox>