2013年5月29日 星期三

[W8App] ListBox Binding data (4) – Update data asynchronous

W8App的非同步(Async)與傳統桌面系統的多緒(multi-thread)在操作上有一點點的不太一樣,傳統的多緒系統CreateThread通常都會對應一個WaitForSingleObject,等待該工作的完成。但非同步的機制確不需要再額外coding一個等待機制隨時的檢查資料是否有更新(或是檢查資料是否全部更新完)

Async

以ListBox為例,我們可以把與ListBox繫結的資料,以非同步的方式一筆筆的加到ObservableCollection集合中。這對於大量或是遠端的資料的使用上可避免UI長時間的占用請形有很大的幫助。

不過當ListBox繫結非同步資料時有一點必需注意,新增與移除資料必需是在UI元件同樣的執行緒上執行,

   1: await Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
   2: {
   3:     // read items collection from file or network ...
   4:     foreach (var item in items)
   5:     {
   6:         SampleDataGroup group = SampleDataSource.GetGroup("AllGroups");
   7:         group.Items.Add(new SampleDataItem("","","","","","",group));
   8:     }
   9: });

Window.Current會取得目前的window,但是要注意的是若新增(移除)的非同步機制是在另一個頁面或是Runtime dll中時,可能會取到不正確的window,這時就必需先記錄ListBox所在的windows資料。


參考資料:

1. Async Made Simple with C++ PPL, Rahul V. Patil, Microsoft Corporation

2013年5月27日 星期一

[筆記] XmlElement can not call SetAttribute in foreach?

C#對我而言只是會用而已,有些語法還不是很了解,今天遇到一個問題不清楚是C#的限制還是Bug?

我使用XmlDocument讀取xml文件,並檢查文件中的Bookmark元素們的Bookmarkname屬性,當屬性不存在或為空字元(“”)時視為舊版本,從Filename屬性讀出資料更新Bookmarkname屬性。

原本是使用C#的foreach來檢查每個Bookmark元素

   1: var elements = _xmlProject.GetElementsByTagName("Bookmark");
   2: foreach(var element in elements)
   3: {
   4:     String bookmarkname = element.GetAttribute(@"Bookmarkname");
   5:     if (bookmarkname == null || bookmarkname == "")
   6:     {
   7:         bookmarkname = element.GetAttribute(@"Filename");
   8:         element.SetAttribute(@"Bookmarkname", bookmarkname);
   9:     }
  10: }

不過當發現元素為舊版格式不存在Bookmarkname屬性而呼叫SetAttribute後,下一筆element讀取時一定會發生Invalidate exception,若改為for語法就一切正常,更改後為



   1: var elements = _xmlProject.GetElementsByTagName("Bookmark");
   2: int count = elements.Count;
   3: for(int i=0 ; i<count ; i++)
   4: {
   5:     var element = elements[i] as XmlElement;
   6:  
   7:     String bookmarkname = element.GetAttribute(@"Bookmarkname");
   8:     if (bookmarkname == null || bookmarkname == "")
   9:     {
  10:         bookmarkname = element.GetAttribute(@"Filename");
  11:         element.SetAttribute(@"Bookmarkname", bookmarkname);
  12:     }
  13: }

所以是C#的foreach並不是list的形式所以資料集合不能有記憶體配置的變化?還只是單純的bug?

2013年5月24日 星期五

[W8App] ListBox Binding data (3) – Binding Dynamic Data

XAML的Data Binding方式可隨開發者的需求而有許多不同的設計方式,我想要的需求是一個ListBox元件能隨時的更換繫結的資料集合,在程式中必需要Binding資料時呼叫以下的程式碼來建立繫結ListBox (myListBox)與資料集合(myList),若要更換資料集合時,只需要重新assing另一個物件即可

   1: myListBox.ItemsSource = myList;

myList是使用List<T>或是ObservableCollection<T>模版的考量方式可以從何時指定ItemsSource來看。


如果是先把所有的item加入到myList後才進行Binding,那麼用List<T>就可以了



   1: myList.Add(item1);
   2: myList.Add(item2);
   3: ...
   4: myListBox.ItemsSource = myList

如果是先進行Binding,之後才“慢慢”的加入item,那麼就必需用ObservableCollection<T>



   1: myListBox.ItemsSource = myList
   2: ...
   3: myList.Add(item1);
   4: myList.Add(item2);
   5: ...




如果我想要產生每個Item控制項包含一張Icon圖與文字的ListBox


ListBox_Binding


XAML程式碼應該會長的像這樣:



   1: <ListBox x:Name="myListBox">
   2:     <ListBox.ItemTemplate>
   3:         <DataTemplate>
   4:             <Grid>
   5:                 <Image Source="{Binding Image}" />
   6:                 <TextBlock Text="{Binding Title}"/>
   7:             </Grid>
   8:         </DataTemplate>
   9:     </ListBox.ItemTemplate>
  10: </ListBox>

ListBox的ItemsSource與資料集合Binding是在程式中完成,所以XAML中不特意指明Binding的對像,但是DataTemplate中的元件與資料集合就必需指明與元件Binding的路徑名稱,如上面例子的#5, #6所示。

2013年5月22日 星期三

[W8App] ListBox Binding data (2) – INotifyPropertyChanged item

如果想要讓UI與Binding data能動態的更新,那麼Binding Item必需繼承自INotifyPropertyChanged,當Binding的屬性改變時,送出PropertyChanged的事件讓UI知悉。

繼承自INotifyPropertyChanged的item大概都長的像下面這段程式碼一樣:

   1: public class myItem : INotifyPropertyChanged
   2: {
   3:     public event PropertyChangedEventHandler PropertyChanged;
   4:  
   5:     protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
   6:     {
   7:         if (object.Equals(storage, value)) return false;
   8:  
   9:         storage = value;
  10:         OnPropertyChanged(propertyName);
  11:         return true;
  12:     }
  13:  
  14:     protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
  15:     {
  16:         var eventHandler = PropertyChanged;
  17:         if (eventHandler != null)
  18:         {
  19:             eventHandler(this, new PropertyChangedEventArgs(propertyName));
  20:         }
  21:     }
  22: }

必需實作三件事:
#3 要有PropertyChangedEventHandler
#9 要儲存set value
#19 要發送出屬性改變的事件


將Binding的屬性透過SetProperty<T>儲存並送出事件,如ImagePath屬性:



   1: public String ImagePath
   2: {
   3:     set { SetProperty<String>(ref _strImagePath, value); }
   4:     get { return _strImagePath; }
   5: }

#3 SetProperty的第三個參數為預設引數時,並不是真的為空字元,而是屬性的名稱(如本例為”ImagePath”)。





PS:如果開發的Project是使用VS2012的Grid App 或 Split App的專案時,當專案建立完成時可在的專案的Common目錄下找到BindableBase class,BindableBase已完成INotifyPropertyChanged所需實作的函式,可直接繼承自BindableBase省去複製重複的程式碼工作。

2013年5月20日 星期一

[筆記] Make the center alignment MediaElement

由於有各式各樣的影片格式,21:9、16:9、4:3…。要保持影片播放時保持在設定的位置的中間就必需設定擁有MediaElement的ContentControl的HorizontalContentAlignment與VerticalContentAlignment為Center。

[XAML example]

   1: <ContentControl HorizontalContentAlignment="Center" VerticalContentAlignment="Center">
   2:     <MediaElement x:Name="myVideoElement"/>
   3: </ContentControl>

2013年5月17日 星期五

[筆記] TimeSpanTo###Converter

TimeSpan to double:

   1: value.TotalSeconds

TimeSpan to hh : mm : ss Format:



   1: String.Format(@"{0:hh\:mm\:ss}", value)


TimeSpan (string double) to hh : mm : ss Format:



   1: Int64 totalsec = System.Convert.ToInt64(value);
   2:  
   3: long hour =   (long)totalsec / 3600L;
   4: long minute = (long)Math.Max(0, (totalsec - hour * 3600) / 60);
   5: long second = (long)Math.Max(0, totalsec - hour * 3600 - minute * 60);
   6:  
   7: String strvalue = hour.ToString() + ":" + minute.ToString() + ":" + second.ToString();

2013年5月15日 星期三

[W8App] ListBox Binding data (1) – Overview

在決定要Binding元件之前,先要檢查要Binding的資料本身的特性。

binding_1

如果想讓Binding的物件能反映資料的改變,則資料的本身必需繼承自INotifyPropertyChanged,透過OnPropertyChanged發送事件讓物件更新。

如果物件的集合在程式運作過程中不會有增減的特性,則將繼承自INotifyPropertyChanged的元件套入List<T>模版中即可

如果物件的集合在程式的執行過程中會動態的增減,則繼承自INotifyPropertyChanged的元件必需套入 ObservableCollection<T>模版。

由於要Binding ListBox屬於UI元件,若資料的增減行為是發生在非同步的執行緒中,則會發生UI元件在不同執行緒運作的錯誤,所以必需記錄GUI元件所在的Window,讓元件增減的處理發生在Window相同的執行緒中。

2013年5月13日 星期一

[秘技] 實作Converter (繼承自IValueConverter)

其實只是要秀一下我發現的一個秘技。為何Data Binding時需要Converter,如何實作Converter,如何Binding的方式教科書已經說的太多,只是我實作程式碼時常想,同樣的工作同樣的程式碼為何要一再重覆的coding。教科書沒教我們的Implement Converter技法其實很簡單。

當你把Cursor設定在IValueConverter上時有沒有覺得有一個怪怪的小藍色底線看了很不舒服很想把他消除:

valueconvert_1

把滑鼠移動過去會變成一個可以按的小按鈕

valueconvert_2

選擇Implement與Explicitly implement有什麼不同我還不曉得,不過按了之後的結果是一樣的,Convert與ConvertBack都implement好了(雖然都是throw NotImplementedException):

valueconvert_4

剩下的工作就是自已實作要如何convert而已。

從滑鼠移到小藍點到變成按鈕要花點時間,如果不想等,可以直接對IValueConvert按右鍵,也有Implement Interface的選項可選:

valueconvert_3

這個秘技似乎只有新建的class可以用,如果已經有Implement過Convert與ConvertBack後,Implement Interface的選單會自然的消失。

2013年5月10日 星期五

GetPrivateProfileShortArray / GetPrivateProfileDoubleArray

regex練習之作,雖然讀ini是很古老的東西,但不可否認還真的蠻實用的。GetPrivateProfile嚴格的說是沒有讀array的機制,假設我的ini中有二個array key分別為:

[Para]
Para1 = 0.0952, 1.0, .0, .14, 1.0, 0.0, 1.0
Para2 =    2,    3,    2,    1,    1,    7,     1,    2,    0,    1

我設計GetPrivateProfileDoubleArray用來讀浮點數陣列(Para1),透過regex可以很快的把數值分割出來,再將分割的字串轉換回浮點數即可,用法如下:

   1: double pPara1[10] = { 0 };
   2: GetPrivateProfileDoubleArray("Para","Para1",pPara1,10,strIniPath);

GetPrivateProfileDoubleArray函式有一個缺點,ini中的數值格式最少要有一位小數點,如果是整數會被忽略。GetPrivateProfileDoubleArray程式碼:



   1: DWORD GetPrivateProfileDoubleArray(LPCTSTR lpszSec,LPCTSTR lpszKey,double* pArray,int nSafeSize,LPCTSTR lpszFilePath)
   2: {
   3:     TCHAR szData[_MAX_PATH] = { 0 };
   4:  
   5:     if(GetPrivateProfileString(lpszSec,lpszKey,"",szData,_MAX_PATH,lpszFilePath) == 0)    return 0;
   6:     
   7:     const regex refloat("\\d*\\.\\d+");
   8:     const sregex_token_iterator end;
   9:  
  10:     string s = szData;
  11:     int i = 0;
  12:     for(sregex_token_iterator value(s.begin(), s.end(), refloat) ; value != end ; ++value)
  13:     {
  14:         if(i >= nSafeSize)    break;
  15:  
  16:         string sv = *value;
  17:  
  18:         pArray[i++] = atof(sv.c_str());
  19:     }
  20:  
  21:     return i;
  22: }

用GetPrivateProfileShortArray讀整數陣列(Para2)



   1: short pPara2[10] = { 0 };
   2: GetPrivateProfileShortArray("Para","Para2",pPara2,10,strIniPath);

GetPrivateProfileShortArray程式碼:



   1: DWORD GetPrivateProfileShortArray(LPCTSTR lpszSec,LPCTSTR lpszKey,short* pArray,int nSafeSize,LPCTSTR lpszFilePath)
   2: {
   3:     TCHAR szData[_MAX_PATH] = { 0 };
   4:  
   5:     if(GetPrivateProfileString(lpszSec,lpszKey,"",szData,_MAX_PATH,lpszFilePath) == 0)    return 0;
   6:     
   7:     const regex refloat("\\d+");
   8:     const sregex_token_iterator end;
   9:  
  10:     string s = szData;
  11:     int i = 0;
  12:     for(sregex_token_iterator value(s.begin(), s.end(), refloat) ; value != end ; ++value)
  13:     {
  14:         if(i >= nSafeSize)    break;
  15:  
  16:         string sv = *value;
  17:  
  18:         pArray[i++] = atoi(sv.c_str());
  19:     }
  20:  
  21:     return i;
  22: }

2013年5月8日 星期三

[W8App] Make Native Runtime Component (2): Delegate and Event

除了async / await等待非同步的RunTime Component完成工作外,Native Runtime Component能否透過callback (或 message)來取得運算更新?W8App已不再用傳統的callback / message loop來進行元件之間的溝通,取而代之的是delegate / event機制,我們必需做三件事使呼叫端能得知元件的事件觸發。

1. 元件必需提供delegate handler:
#1 delegate的宣告必需在class的宣告之前
#9 class中必需有delegate hander event,讓元件可發送事件,呼叫端可聆聽事件
#5 class中必需有dispatcher用來記錄呼叫端執行緒的dispatcher

   1: public delegate void ProcessedHandler(int retry);
   2: public ref class myTRDLL sealed
   3: {
   4: private:
   5:   Windows::UI::Core::CoreDispatcher^ m_dispatcher;
   6:  
   7:   // DO SOMETHING
   8: public:
   9:   event ProcessedHandler^ ProcessedEvent;
  10: };

2. 呼叫端(C# app)必需 += 聆聽這個事件



   1: _myRTDLL.ProcessedEvent += OnRTDLLProcessedEvent;

3. 元件觸發事件
#4 在產生非同步的task前記錄呼叫端AP執行緒的dispatcher
#16 觸發ProcessedEvent事件
#14 確保AP是在main thread接收到event。如果忘了做這個動作,會收到0x8001010E的例外事件。



   1: IAsyncOperation<int>^ myRTDLL::RunProcessAsync(int maxretry)
   2: {
   3:   auto window = Windows::UI::Core::CoreWindow::GetForCurrentThread();
   4:   m_dispatcher = window->Dispatcher;
   5:  
   6:   return create_async([this,maxretry]() {
   7:     int retry = 0;
   8:  
   9:     for( ; retry<maxretry ; retry++)
  10:     {
  11:       if(RunProcess(retry))  break;
  12:     }
  13:  
  14:     m_dispatcher->RunAsync( CoreDispatcherPriority::Normal, ref new DispatchedHandler([this, retry]() 
  15:     {
  16:       this->ProcessedEvent(retry);
  17:     }, Platform::CallbackContext::Any));
  18:  
  19:     return (retry < maxretry) ? retry : -1;
  20:   });
  21: }

(當初沒做dispatch這個動作花了好多時間debug…)