2009/08/12

Class, Abstract Class, and Interface

最近在研究用 C# 寫 Plugin, 為了 plugin 寫了二篇文章,分別是Class Interface, 及 Plugins

這邊就有個問題,一般我們在寫程式,大概都只會用到類別 "Class", 書上講的抽象類別(Abstract Class)似乎很少用到,更別提界面(interface)。不過在用 Plugin 時,一直都是用界面(Interface), 為什麼呢?若看得懂英文的,請參考Abstract Class vs Interface一文。

.抽象類別: 抽象類別其實非常像一般的類別,可以擁有 Private 的成員及方法,當然不同的地方就在於它也可以擁有 Abstract 的成員及方法。

.界面: 可以簡單說,界面是更抽象的類別,只能擁有 Public 及 Abstract 的成員及方法。要換一種說法就是,界面不必宣告,一切都當成 public && abstract,而且而且,因為是抽象的,所以都不能實作。

用下面的範例來看會比較容易明白:


//Abstarct Class
public abstract class Vehicles
{
private int noOfWheel;
private string color;
public abstract string Engine
{
get;
set;
}
public abstract void Accelerator();
}

//Interface
public interface Vehicles
{
string Engine
{
get;
set;
}
void Accelerator();
}


文中提到一種使用時機,若繼承關係中,基礎類別有可能被用來產生實例的話,當然就只能用一般類別,但是若不會發生這種可能,用抽象類別會是比較好的。至於界面,則是這個基礎類別若有「預設行為」的話只能用抽象類別,否則它就只是個界面。

寫到這兒,也大概了解為何 Plugin 只能用界面了,因為,不可能為 Plugin 給什麼預設行為不是?

讓我們從抽象類別與界面的字面意義來思考,抽象類別畢竟是類別,只是它是抽象的,所以在繼承中需要被「overlay」,就像看到「筆」這個抽象概念時,你會知道可以寫,而且不管哪種筆的寫都「差不多」一樣。可是界面之所以是界面,就是因為你可以預期它要提供的功能是什麼,可是界面只停留在界面,怎麼實作是沒個準頭的,要實際繼承的人去實作。而之所以被稱為界面,也限定了繼承的人一定要「全部」實作。

2009/08/10

主開機磁區(MBR)被 grub/lilo 蓋掉了怎麼回覆?

這問題一直都存在,早期的文獻(好像我也貼過)都說用 fdisk /mbr 來修復,不過自從檔案系統用 NTFS 之後,此一工具也不見了。我的狀況是電腦裝的是 windows 7, 後來用 Linux 要將它安裝到我的隨身碟,不知道什麼時候做了蠢事,要用原來的方式從硬碟開機時發現停在不正常的 grub 上,因為此時隨身碟不見了。

好吧,至少原來用 grub 開機的方式我辦得到,也就是說,我讓系統停在 grub shell 上,然後用傳統的方式來試著開機進 Windows,因為我是用隨身碟,此時硬碟變成 (hd1,0):

root (hd1,0)
rootnoverify (hd1,0)
chainloader +1
makeactive
boot

注意哦,就算進 windows 7(我相信 vista 也一樣)也找不到 fdisk, 所以就先停在 Windows 開機選單畫面,注意看的話可以看到 "F8" 修復的字樣,就給它用力按 F8 吧。

上面的方式也許有人不知道怎樣進去那個 F8 畫面,也可以拿原版光碟來,裡面會有相同的畫面。

好了,假設你在系統的 F8 畫面,請選擇 Command Window 吧,然後用下面的命令:

bootsect /nt60 c: /mbr /force

上面的 C: 是因為我用 F8 以硬碟開機的方式進去系統,相信若是用安裝光碟的話不是 c:,請自行修改。

最後,要理解二件事:
一、前面的 grub 設定方式可以讓你從硬碟開機進之前安裝的 Windows 7/Vista, 但是卻尚未修改開機設定,因此下次若用純硬碟開機還是會停在不完全的 Grub 選單中。
二、為何要按 F8? 因為 bootsect 不在正常的 Windows 7 中,只存在 Windows 7 的修復控制台中,上面說的就是透過按 F8 或是用安裝光碟來進到修復控制台。

2009/08/04

Data Binding
本文主要來自Piping Value Converters一文,有興趣的人也可以直接看MSDN Data Binding Overview

先來看看該文範例執行時的畫面如下:


而用來 Data Binding 的資料如下:

<?xml version="1.0" encoding="utf-8" ?>
<Tasks>
  <Task Name="Paint the living room" Status="0" />
  <Task Name="Wash the floor" Status="-1" />
  <Task Name="Study WPF" Status="1" />
  <Task Name="Rebuild kernel" Status="-1" />
  <Task Name="Kernel Install" Status="0" />
  <Task Name="Rebuild Driver" Status="1" />
</Tasks>

問題:

根據所提供的資料,如何把它依某一個屬性(範例中是用 Status)轉換成不同的 UI 元素?以此例除了 Pending, Complete, Active 外,還有顏色。

說明:

資料在繫結時(Data Binding),若有設定 Converter 屬性的話會先經過 IValueConverter型別的 Converter 方法轉換,對單一的轉換就是這麼簡單,可是若要多重轉換時怎麼處理才好呢?該文撰寫了一個繼承自 IValueConverter 的 ValueConverterGroup 來達成此目標。在實作上, ValueConverterGroup 可以擁有多個 Converters, 在呼叫 Converter 時,會依序呼叫。比較特別需要注意的是,前一個 converter 的輸出會當成下一個 converter 的輸入,直到最後一個 converter 的輸出被當成 target 為止。

由 ValueConverterGroup 實例來看問題:

範例中定義下面三個 ValueConverterGroup 實例(Instance), 關於 ValueConverterGroup 的定義則見後面的說明。重溫前面的問題,我們只有一個 Status 屬性,可是要做三件事,一是將數值型態的 Status 轉換成易讀的 Pending, Complete, Active,一是不同的 Status 以不同的顏色來顯示,三是對不同的 Status 顯示不同的 Tooltip.

    <!-- 第一部份: 將 Status 屬性值轉換成像 Pending, Complete, Active 這樣的字串以便顯示在 GUI 上 -->
    <local:ValueConverterGroup x:Key="statusDisplayNameGroup">
      <local:IntegerStringToProcessingStateConverter  />
      <local:EnumToDisplayNameConverter />
    </local:ValueConverterGroup>

    <!-- 第二部份: 根據 Status 屬性值轉換成不同的顏色,以便反應不同的狀態 -->
    <local:ValueConverterGroup x:Key="statusForegroundGroup">
      <local:IntegerStringToProcessingStateConverter  />
      <local:ProcessingStateToColorConverter />
      <local:ColorToSolidColorBrushConverter />
    </local:ValueConverterGroup>

    <!-- 第三部份: 根據 Status 屬性值轉換成不同的 Tooltip -->
    <local:ValueConverterGroup x:Key="statusDescriptionGroup">
      <local:XmlAttributeToStringStateConverter />
      <local:IntegerStringToProcessingStateConverter  />
      <local:EnumToDescriptionConverter />
    </local:ValueConverterGroup>   

從上面的實例可以看到三個 Key 值分別是 statusDisplayNameGroup, statusForegroundGroup, statusDescriptionGroup,使用上可以由 <DataTemplate> 來引用,範例如下,剛好可以看到三個 Converter 分別引用了這三個 Key 值:

    <DataTemplate x:Key="taskItemTemplate">
      <StackPanel
        Margin="2"
        Orientation="Horizontal"
        ToolTip="{Binding XPath=@Status, Converter={StaticResource statusDescriptionGroup}}"
        >
        <TextBlock Text="{Binding XPath=@Name}" />
        <TextBlock Text="   (" xml:space="preserve" />
        <TextBlock
          Text="{Binding XPath=@Status, Converter={StaticResource statusDisplayNameGroup}}"
          Foreground="{Binding XPath=@Status, Converter={StaticResource statusForegroundGroup}}" />
        <TextBlock Text=")" />
      </StackPanel>
    </DataTemplate>


有興趣的人也可以分析上面的 來看最後 GUI 所呈現的每一筆資訊都由 @Name, (, @Status, ) 四個部份組成,運用上將 Status 用三個 Converter 來轉換成需要的元素即達成目的。

ValueConverterGroup 的定義:

這邊我只列出 Convert 的定義:

object IValueConverter.Convert( object value, Type targetType, object parameter, CultureInfo culture )
{
object output = value;

for( int i = 0; i < this.Converters.Count; ++i )
{
IValueConverter converter = this.Converters[i];
Type currentTargetType = this.GetTargetType( i, targetType, true );
output = converter.Convert( output, currentTargetType, parameter, culture );

// If the converter returns 'DoNothing' then the binding operation should terminate.
if( output == Binding.DoNothing )
break;
}

return output;
}

上面定義被標示成紅色斜線的部份,也就是上面在說明一節提到的「前一個 converter 的輸出會當成下一個 converter 的輸入」的實作方式。順帶一提的是,在 Data Binding 時對錯誤資料的處理可以透過 Binding.DoNothing 來終止繫結。

2009/08/03

Picasaweb Viewer

請參考Picasaweb Viewer

這個範例讓視窗的寬、高隨著內容調整大小,其作法是在 xaml 的<window> 裡面設定 SizeToContent="WidthAndHeight"
整個視窗分成左右兩部份,左邊是取自 http://picasaweb.google.com/data/feed/api/user/USER_NAME?kind=album 的RSS feed 列表,右邊則是照片列表。因為都是清單,因此需要將新專案的 <Grid> 拿掉,放兩個 <ListBox> 上去。

Resource
在繼續修改之前,可以先將 Resource 設定好,其內容如下:

    <DockPanel.Resources >
     
      <XmlNamespaceMappingCollection x:Key="mapping">
        <XmlNamespaceMapping Uri="http://www.w3.org/2005/Atom" Prefix="default"/>
        <XmlNamespaceMapping Uri="http://schemas.google.com/photos/2007" Prefix="gphoto"/>
      </XmlNamespaceMappingCollection>
     
      <XmlDataProvider x:Key="Picasa" XmlNamespaceManager="{StaticResource mapping}" Source="http://picasaweb.google.com/data/feed/api/user/USER_NAME?kind=album">
      </XmlDataProvider>
    </DockPanel.Resources>

左半部「相簿清單」

可以注意到這邊會採用 XmlDataProvider 來做 Data Binding, 而這正是左半部的 ListBox 的資料來源,因此我們來看看左半部怎麼寫的:

      <ListBox DockPanel.Dock="Left" SelectionChanged="OnSelctionChanged" DataContext="{StaticResource Picasa}"  ItemsSource="{Binding XPath=default:feed/default:entry/default:title}">
      </ListBox>

在 DataContext 設定其值為 {StaticResource Picasa},這樣就可以把資料 Binding 好。同時請注意到 SelectionChanged 被設定成 OnSelctionChanged,如此一來就會在左邊的「項目」被點選而改變時,會呼叫 OnSelctionChanged() 來改變右邊的圖片清單。

右半部「照片清單」

至於右邊的清單寫法清微複雜,可以分成三部份來讀:

DataContext

      <ListBox.DataContext>
        <XmlDataProvider XmlNamespaceManager="{StaticResource mapping}">
        </XmlDataProvider>
      </ListBox.DataContext>

可以注意到右半部的 DataContext 與左半部有相同的 XmlNamespaceManager, 也許你一下子沒看出來,不過可以從左半部的 DataContext 使用 Picasa 這個資源就可以追出來。而最重要的 ItemSource 屬性則必須靠使用者點選左半部來改變,因此在 OnSelectionChanged() 中我們可以看到其定義,先讓我們瞧瞧整個事件的定義:

        public void OnSelctionChanged(Object source, RoutedEventArgs args)
        {
            ListBox lb = args.Source as ListBox;
            string simplestr;
            XmlDataProvider provider = MyListBox.DataContext as XmlDataProvider;
            if (provider != null)
            {
                simplestr = lb.SelectedValue.ToString().Substring(0,lb.SelectedValue.ToString().IndexOf('-'));
                simplestr = simplestr.Replace(" ", "");
                simplestr = simplestr.Replace(",", "");
                provider.Source = new Uri(@"http://picasaweb.google.com/data/feed/api/user/wade.fs/album/" + simplestr  + "?kind=photo");
            }
        }

  透過 provider.Source 的設值來動態改變 ListBox 的 ItemSource 屬性。

ItemTemplate

那麼我們如何設定每個清單是什麼樣的元素?可以透過 ListBox 的 ItemTemplate 屬性來設定,範例如下:

      <ListBox.ItemTemplate>
        <DataTemplate>
          <Image Source="{Binding XPath=@src}" Width="300"></Image>
        </DataTemplate>
      </ListBox.ItemTemplate>

而照片的網址則來自 RSS <content> 項目裡面的 "@src" 來指定,可以從 XPath 知道此項關聯。這邊又可以見到一項 Data Binding 的用法。

ItemsPanel

最後,照片清單的顯示若太多,常常會發生把視窗擠的變形到無法接受,若我們要採用比較像瀏覽器的方式的話,可以透過 ItemsPanel 來設定其 Layout, 範例如下:

      <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
          <WrapPanel/>
        </ItemsPanelTemplate>
      </ListBox.ItemsPanel>

Proxy Server

若您的環境是透過 Proxy Server 的話,又該如何在 WPF 中使用呢?可以在執行檔所在地產生一個 EXECUTE_FILE.exe.config,其內容很簡單,我們讓它使用與 IE 相同的 Proxy Server 設定值,範例如下:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.net>
    <defaultProxy useDefaultCredentials="true" enabled="true"></defaultProxy>
  </system.net>
</configuration>