2012年2月19日 星期日

WPF體系的邏輯樹/視覺樹常用操作

 

適用WPF,部分適用於Silverlight。

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;

namespace OrcaPlayer.Common
{
public class TreeHelper
{
private static object _SyncRoot = new object();

<summary>
IsolatedStorageHelper Static Constructor.
</
summary>
static TreeHelper()
{

}

private static TreeHelper _instance = null;
<summary>
</
summary>
public static TreeHelper Instance
{
get
{
lock (_SyncRoot)
{
if (_instance == null)
{
_instance = new TreeHelper();
}
return _instance;
}
}
}


public DependencyObject FindVirtualTreeRoot(DependencyObject initial)
{
DependencyObject current = initial;
DependencyObject result = initial;

while (current != null)
{
result = current;

if (current is Visual || current is Visual3D)
{
current = VisualTreeHelper.GetParent(current);
}
else
{
current = LogicalTreeHelper.GetParent(current);
}
}

return result;
}

public DependencyObject FindLogicalTreeRoot(DependencyObject initial)
{
DependencyObject current = initial;
DependencyObject result = initial;

while (current != null)
{
result = current;
current = LogicalTreeHelper.GetParent(current);
}

return result;
}

//using System.Windows;
public DependencyObject GetTemplateParent(DependencyObject depObj)
{
FrameworkElement fe = depObj as FrameworkElement;
FrameworkContentElement fce = depObj as FrameworkContentElement;

DependencyObject result;

if (fe != null)
{
result = fe.TemplatedParent;
}
else if (fce != null)
{
result = fce.TemplatedParent;
}
else
{
result = null;
}

return result;
}

//最近邏輯樹上的父節點
public DependencyObject FindClosestLogicalAncestor(DependencyObject initial)
{
DependencyObject current = initial;
DependencyObject result = initial;

while (current != null)
{
DependencyObject logicalParent = LogicalTreeHelper.GetParent(current);

if (logicalParent != null)
{
result = current;
break;
}

current = VisualTreeHelper.GetParent(current);
}

return result;
}

//最近視覺樹上的父節點
public DependencyObject FindClosestVisualAncestor(DependencyObject initial)
{
if (initial is Visual || initial is Visual3D)
{
return initial;
}

DependencyObject current = initial;
DependencyObject result = initial;

while (current != null)
{
result = current;

if (current is Visual || current is Visual3D)
{
result = current;
break;
}
else
{
current = LogicalTreeHelper.GetParent(current);
}
}

return result;
}

//顯示邏輯樹
//private TreeView tree = new TreeView();
public void PopulateLogicalTree(TreeViewItem parentItem, object currentElement)
{
TreeViewItem item = new TreeViewItem();

item.IsExpanded = true;

if (parentItem == null)
{
item.Header = currentElement.GetType().Name;
tree.Items.Add(item);
}
else
{
item.Header = currentElement.GetType().Name;
parentItem.Items.Add(item);
}

DependencyObject curObject = currentElement as DependencyObject;

if (curObject != null)
{
foreach (object chElement in LogicalTreeHelper.GetChildren(curObject))
{
PopulateLogicalTree(item, chElement);
}
}
}

//顯示視覺樹
//private TreeView tree = new TreeView();
public void PopulateVisualTree(TreeViewItem parentItem, DependencyObject currentElement)
{
TreeViewItem item = new TreeViewItem();

item.IsExpanded = true;

if (parentItem == null)
{
item.Header = currentElement.GetType().Name;
tree.Items.Add(item);
}
else
{
item.Header = currentElement.GetType().Name;
parentItem.Items.Add(item);
}

int visualChildrenCount = VisualTreeHelper.GetChildrenCount(currentElement);

for (int i = 0; i < visualChildrenCount; i++)
{
DependencyObject visualChild = VisualTreeHelper.GetChild(currentElement, i);
PopulateVisualTree(item, visualChild);
}
}

//ex: CheckBox chb = this.FindFirstVisualChild<CheckBox>(listViewItem)
public T FindFirstVisualChild<T>(DependencyObject parentElement) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parentElement); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parentElement, i);

if (child != null && child is T)
{
return child as T;
}
else
{
T result = FindFirstVisualChild<T>(child);

if (result != null)
{
return result;
}
}
}

return null;
}

//TextBlock textBlock = GetFrameworkElementByName<TextBlock>(rowPresenter, "textBlock");
public T GetFrameworkElementByName<T>(FrameworkElement parentElement, String name) where T : FrameworkElement
{
FrameworkElement child = null;

for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(parentElement); i++)
{
Debug.WriteLine("TreeHelper.cs::GetFrameworkElementByName " + parentElement.GetType().ToString() + " " + i.ToString());
child = VisualTreeHelper.GetChild(parentElement, i) as FrameworkElement;

if (child != null && child.Name == name && child.GetType() == typeof(T))
{
break;
}

else if (child != null)
{
child = GetFrameworkElementByName<T>(child, name);
}
}

return child as T;
}

//ContentPresenter cp = VisualTreeHelper.GetChild(CInfoContentControl, 0) as ContentPresenter;
//Button DownloadButton = TreeHelper.Instance.FindVisualChild<Button>(cp, 0, 3, 0, 0, 1) as Button;
//第二參數為第一參數下的的每一層子元素(子元素從0起算)
public T FindVisualChild<T>(DependencyObject parentElement, params int[] list) where T : DependencyObject
{
DependencyObject child = null;
for (int i = 0; i < list.Length; i++)
{
Debug.WriteLine("i=[" + i.ToString() + "] list[i]=[" + list[i].ToString() + "]");

if (i == 0)
{
if (VisualTreeHelper.GetChildrenCount(parentElement) > 0)
{
child = VisualTreeHelper.GetChild(parentElement, list[0]);
Debug.WriteLine("子元素型別=[" + child.GetType().ToString() + "]");
}
else
{
Debug.WriteLine(i.ToString() + " 子元素個數=[" + VisualTreeHelper.GetChildrenCount(parentElement).ToString() + "]");
}
}
else
{
if (VisualTreeHelper.GetChildrenCount(child) > 0)
{
child = VisualTreeHelper.GetChild(child, list[i]);
Debug.WriteLine("子元素型別=[" + child.GetType().ToString() + "]");
}
else
{
Debug.WriteLine(i.ToString() + " 子元素個數=[" + VisualTreeHelper.GetChildrenCount(child).ToString() + "]");
}
}
}

if (child != null && child is T)
{
return child as T;
}
else
{
return null;
}
}


public static T GetValue<T>(this BindingExpression expression, object dataItem)
{
if (expression == null || dataItem == null)
{
return default(T);
}

string bindingPath = expression.ParentBinding.Path.Path;
string[] properties = bindingPath.Split('.');

object currentObject = dataItem;
Type currentType = null;

for (int i = 0; i < properties.Length; i++)
{
currentType = currentObject.GetType();
PropertyInfo property = currentType.GetProperty(properties[i]);
if (property == null)
{
currentObject = null;
break;
}
currentObject = property.GetValue(currentObject, null);
if (currentObject == null)
{
break;
}
}

return (T)currentObject;
}

}
}


參考資料:


2012年1月26日 星期四

在啟動Expression Blend 4 時Crash

這是.Net 4.5 Developer Preview的相容性問題。這個問題已經被微軟修正了,但是修正的時間太晚了,所以沒有被放在下載的檔案中(preview build)。

修正方式為執行以下命令:

  • %windir%\Microsoft.NET\Framework\v4.0.30319\ngen uninstall "%ProgramFiles(x86)%\Microsoft Expression\Blend 4\Microsoft.Expression.Framework.dll"
  • %windir%\Microsoft.NET\Framework\v4.0.30319\ngen uninstall "%ProgramFiles(x86)%\Microsoft Expression\Blend 4\Microsoft.Expression.Blend.dll"
  • %windir%\Microsoft.NET\Framework\v4.0.30319\ngen uninstall "%ProgramFiles(x86)%\Microsoft Expression\Blend 4\Microsoft.Expression.Project.dll"
  • 如果有安裝Blend 4 SP1或後續的SPx,需要執行                                                                                                                                       %windir%\Microsoft.NET\Framework\v4.0.30319\ngen uninstall "%ProgramFiles(x86)%\Microsoft Expression\Blend 4\Microsoft.Expression.WindowsPhone.dll"

如果是使用x86系統的話,需要移除路徑中的"(x86)"。

參考資料:

2011年9月20日 星期二

馬上可用的WCF診斷設定

 

  1. 以下設定的initializeData目錄需有網站權限。
  2. switch這邊設定為All,代表會輸出所有診斷的訊息,可以依據需要來調整。
<system.diagnostics>
    <sources>
      <source name="System.ServiceModel.MessageLogging" switchValue="All">
        <listeners>
          <add type="System.Diagnostics.DefaultTraceListener" name="Default">
            <filter type="" />
          </add>
          <add initializeData="D:\WebSites\TEMP\site_messages.svclog"
    type="System.Diagnostics.XmlWriterTraceListener"
    name="ServiceModelMessageLoggingListener" traceOutputOptions="Timestamp">
            <filter type="" />
          </add>
        </listeners>
      </source>
      <source name="System.ServiceModel" switchValue="All"
          propagateActivity="true">
        <listeners>
          <add type="System.Diagnostics.DefaultTraceListener" name="Default">
            <filter type="" />
          </add>
          <add initializeData="D:\WebSites\TEMP\site_tracelog.svclog"
    type="System.Diagnostics.XmlWriterTraceListener"
    name="ServiceModelTraceListener" traceOutputOptions="Timestamp">
            <filter type="" />
          </add>
        </listeners>
      </source>
    </sources>
<system.diagnostics>
	
<system.serviceModel>
    <diagnostics>      
      <messageLogging logMalformedMessages="true"
		  logMessagesAtTransportLevel="true"
                    logEntireMessage="true"           
		  logMessagesAtServiceLevel="true"           
		  maxMessagesToLog="3000"/>
    </diagnostics>
</system.serviceModel>
參考資料:

  1. WCF管理與診斷

  2. Configuring Tracing的Trace Level部分

2011年9月3日 星期六

[ASP.NET]使用反射機制驅動事件

問題:在ASP.NET的Callback發生時,可以同時處理Postback機制的事件,但是Postback的頁面會刷掉Callback的結果,造成畫面閃動的現象。

但是我還是想要由Ccallback執行Poastback的事件。

目標:可以應用在ASP.NET的Web From上,由一個控制項來驅動另一個控制項的事件。注意,在同一個類別中不適用(用一般的方法就好啦)。

方法:1. 註冊一個事件,可以使用

        protected void Button1_Click(object sender, EventArgs e)
        {            
        }

(.aspx 的 AutoEventWireup="true")



或是

        protected void Page_Load(object sender, EventArgs e)
        {
            Button1.Click +=new EventHandler(Button1_Click);
        }


2. 找到畫面上的目標控制項,就是要驅動事件的控制項。例如要驅動Button的Click事件


Button btn = this.Page.FindControl("button1") as Button;


3. 使用反射方法找到Click事件的委託(Delegate),再執行這個事件。

RaiseEvent(btn, "Click", new EventArgs());
private void RaiseEvent(object sender, string eventName, EventArgs args)
{
    PropertyInfo property = sender.GetType().GetProperty("Events",
                                             BindingFlags.Instance |
                                             BindingFlags.NonPublic);
    EventHandlerList ehList = (EventHandlerList)property.GetValue(sender, null);
    FieldInfo fieldInfo = sender.GetType().GetField("Event" + eventName,
                                                  BindingFlags.Static |
                                                  BindingFlags.NonPublic);
    Delegate d = ehList[fieldInfo.GetValue(null)];
    object[] objects = new object[] { sender, new EventArgs() };
    d.DynamicInvoke(objects);
}

 


參考文件:


2011年6月22日 星期三

如何建立樹狀(分層)資料繫結控制項?

樹狀資料繫結控制項一般呈現為樹狀結構,使用的資料來源也是具有樹狀結構的資料,像是XML或是SiteMap等資料。

簡而言之,只要2個步驟:

  1. 繼承自HierarchicalDataBoundControl
  2. 改寫PerformDataBinding(),取得資料來源,並進行資料繫結。

第2步驟相當於一般資料繫結控制項的PerformSelect()和PerformDataBinding()。

為什麼不是使用PerformSelect()和PerformDataBinding()?關鍵在於"樹狀"結構,必須要使用遞迴來處理,一層一層的往下構建。

在細節方面,有幾個地方需要注意:

  1. 如果已經初始化資料繫結控制項,而資料來源在PreRender事件後變更,那麼就必須要執行OnDataPropertyChanged()方法。
    if (Initialized) //BaseDataBoundControl.Initialized
    {
        OnDataPropertyChanged(); //BaseDataBoundControl.OnDataPropertyChanged()
    }

  2. 需使用樹狀列舉IHierarchicalEnumerable介面來處理資料。包括IHierarchicalEnumerable.GetHierarchyData()方法與IHierarchyData介面。
    private void RecurseDataBindInternal(TreeNode RootNode, IHierarchicalEnumerable enumerable, int depth)
    {
        foreach (object item in enumerable)
        {
            IHierarchyData data = enumerable.GetHierarchyData(item);
            ...
        }
    }

  3. 如果有設定DataTextField就使用DataBinder.GetPropertyValue()來取取得屬性,不然就用TypeDescriptor.GetProperties()來做。
    if (DataTextField.Length > 0)
    {
        System.Xml.XmlElement elm = data.Item as System.Xml.XmlElement;
        if (elm.HasAttributes)
        {
            newNode.Text = DataBinder.GetPropertyValue(data, DataTextField, null);
        }
        else
        {
            newNode.Text = "(Family)";
        }
    }
    else
    {
        PropertyDescriptorCollection props = TypeDescriptor.GetProperties(data);
        newNode.Text = string.Empty;
        if (props.Count >= 1)
        {
            if (null != props[0].GetValue(data).ToString())
            {
                newNode.Text = props[0].GetValue(data).ToString();
            }
        }
    }

  4. 遞迴使用自訂義RecurseDataBindInternal()方法來新增結點並進行資料繫結。
    IHierarchicalEnumerable enumerable = view.Select();
    if (enumerable != null)
    {
        Nodes.Clear();
        try
        {
            RecurseDataBindInternal(RootNode, enumerable, 1);
        }
        catch(Exception e)
        {
        }
    }
主要的程式碼如下(已修正MSDN中屬性的BUG):
namespace Samples.AspNet.CS.Controls
{    
    [ToolboxData("<{0}:GeneologyTree runat=server></{0}:GeneologyTree>")]
    public class GeneologyTree:HierarchicalDataBoundControl
    {
        private TreeNode rootNode;
        public TreeNode RootNode
        {
            get
            {
                if (rootNode == null)
                {
                    rootNode = new TreeNode(string.Empty);
                }
                return rootNode;
            }
        }
        private ArrayList nodes;
        public ArrayList Nodes
        {
            get
            {
                if (nodes == null)
                {
                    nodes = new ArrayList();
                }
                return nodes;
            }
        }
        public string DataTextField
        {
            get
            {
                object o = ViewState["DataTextField"];
                return (o == null) ? string.Empty : (string)o;
            }
            set
            {
                ViewState["DataTextField"] = value;
                if (Initialized) //BaseDataBoundControl.Initialized
                {
                    OnDataPropertyChanged(); //BaseDataBoundControl.OnDataPropertyChanged()
                }
            }
        }
        private int _maxDepth = 0;
        protected override void PerformDataBinding()
        {
            base.PerformDataBinding();
            if (!IsBoundUsingDataSourceID && DataSource == null)
            {
                return;
            }
            HierarchicalDataSourceView view = GetData(RootNode.DataPath);
            if (view == null)
            {
                throw new InvalidOperationException("No view returned by data source control.");
            }
            IHierarchicalEnumerable enumerable = view.Select();
            if (enumerable != null)
            {
                Nodes.Clear();
                try
                {
                    RecurseDataBindInternal(RootNode, enumerable, 1);
                }
                catch(Exception e)
                {
                }
            }
        }
        private void RecurseDataBindInternal(TreeNode RootNode, IHierarchicalEnumerable enumerable, int depth)
        {
            foreach (object item in enumerable)
            {
                IHierarchyData data = enumerable.GetHierarchyData(item);
                if (null != data)
                {
                    TreeNode newNode = new TreeNode();
                    RootViewNode rvNode = new RootViewNode();
                    rvNode.Node = newNode;
                    rvNode.Depth = depth;
                    if (DataTextField.Length > 0)
                    {
                        System.Xml.XmlElement elm = data.Item as System.Xml.XmlElement;
                        if (elm.HasAttributes)
                        {
                            newNode.Text = DataBinder.GetPropertyValue(data, DataTextField, null);
                        }
                        else
                        {
                            newNode.Text = "(Family)";
                        }
                    }
                    else
                    {
                        PropertyDescriptorCollection props = TypeDescriptor.GetProperties(data);
                        newNode.Text = string.Empty;
                        if (props.Count >= 1)
                        {
                            if (null != props[0].GetValue(data).ToString())
                            {
                                newNode.Text = props[0].GetValue(data).ToString();
                            }
                        }
                    }
                    nodes.Add(rvNode);
                    if (data.HasChildren)
                    {
                        IHierarchicalEnumerable newEnumerable = data.GetChildren();
                        if (newEnumerable != null)
                        {
                            RecurseDataBindInternal(newNode, newEnumerable, depth + 1);
                        }
                    }
                    if (_maxDepth < depth) _maxDepth = depth;
                }
            }
        }
        protected override void RenderContents(HtmlTextWriter writer)
        {
            writer.Write("<PRE>");
            int currentDepth = 1;
            int currentTextLength = 0;
            foreach (RootViewNode rvNode in Nodes)
            {
                if (rvNode.Depth != currentDepth)
                {
                    writer.WriteLine(""); //子系需換行
                    int halfLine = currentTextLength / 2;
                    string indent = new string(' ', halfLine);
                    writer.WriteLine(indent + "|"); //子系需縮排並加上連接線
                    ++currentDepth;
                    currentTextLength = 0;
                    writer.Write(indent);
                }
                string output = " " + rvNode.Node.Text + " ";
                writer.Write(output);
                currentTextLength = currentTextLength + output.Length;
            }
            writer.Write("</PRE>");
        }
    }
}
使用的XML檔案(geneology.xml):
<?xml version="1.0" encoding="utf-8" ?>
<family>
    <member title="great-grandfather">
        <member title="grandfather" >
            <member title="child" />
            <member title="father" >
                <member title="son" />
            </member>
        </member>
    </member>
</family>
測試的網頁:
<cc1:GeneologyTree ID="GeneologyTree1" runat="server" 
        DataSourceID="XmlDataSource1" DataTextField="title" />
<asp:XmlDataSource ID="XmlDataSource1" runat="server" 
    DataFile="~/App_Data/geneology.xml"></asp:XmlDataSource>

 


測試的結果:


image


參考資料: