Tuesday, September 17, 2019

如何透過Winform使用Wpf UserControl

English version

前言


你可以在這篇文章知道以下事情
  1. 讓WPF control寄生在一個Winform上面
  2. 只要編輯xaml就可以客制畫面,不用重新compile
  3. 如何在WPF裡使用Dependancy Injection ( 我看了N個範例發現不太容易找 )
  4. 如何在編輯時讓WPF的designer顯示跟runtime類似
這邊文章適合
  1. 有C# winform開發知識的人
  2. 沒空從頭開始看WPF
  3. 想試試看WPF,又有一個winform project可以玩
  4. 不知道怎麼對WPF做Dependancy Injeection
為甚麼我要用WPF
  1. 希望User可以自行修改畫面,但是又不用重新compile
  2. User懂一點xml, 但是不懂怎麼寫C#
先備知識
  1. MVVM-- Model-View-ViewModel: 有點類似MVC
    1. Model: 資料
    2. View: WPF designer顯示的畫面. 等同 winform UserControl.Designer.cs
    3. ViewModel: WPF的Control.Datacontext. 控制顯示的邏輯
    4. 在MVVM, code-behind 越少越好
  2. Code-Behind:
    1. 等同C# UserControl.cs
  3. Loose xaml:
    1. 沒有 x:Class attribute的xaml, x:Class 等同 winform designer partial class.

   第一步: 建立一個Winform來Host WPF control, 可參考 microsoft document

    這個範例很好,但是他使用code-behind去處理click event,這樣會讓xaml變成非loose xaml,讓他不能被動態讀取,或是讀取出來還要用code-behind做一堆事情,才譨處理,我會建議用ICommand這個介面去處理click,在後面的範例也會提到,如果對WPF比較熟了,建議看看MVVM Light,  Prism 幫你處理繁瑣的事情

如何在Winform放入WPF control

Example here. 範例使用.Net 4.0, 4.5以上應該也可以適用
  1. 打開 Example.sln.
  2. 點選 WPFHost project. 加上以下dll
    1. PresentationCore
    2. PresentationFramework
    3. System
    4. WindowsBase
  3. Form1.cs 示範winform怎麼 host WPF control.
  4. RSource 示範dependancy injection到 WPF usercontrol. 他會連上Device Register,在此不討論
  5. ctrlHost 負責WPF hosting,而且必須加進一個panel control裡面
  6. 根據microsoft document,必須把這段程式放在Form_Load內,我還沒測試過放在別的地方會怎麼樣
  private void Form1_Load( object sender, EventArgs e )
  {
   RSource Source = new RSource();
   ctrlHost = new ElementHost();
   ctrlHost.Dock = DockStyle.Fill;
   panel1.Controls.Add( ctrlHost );
   wpfAddressCtrl = new WpfCustomControl.UserControl1( Source );
   wpfAddressCtrl.InitializeComponent();
   ctrlHost.Child = wpfAddressCtrl;
  } 


建立一個符合MVVM的WPF control

Example here ( 跟上面是同一個,只是怕你先跳過來看 )
  1. 打開WpfCustomControls project
  2. 右鍵點 UserControl1.xaml 選"View code",或展開他選UserControl1.xaml.cs.
  3. this.Content代表View的來源.
  4. this.DataContext 就是 ViewModel, 所以我要把 RSource丟進 CncViewModel.
  5. 注意 GetXamlWithoutxClassDirective,讀取loose xaml的時候要記得把 x:Class attribute 移除,我暫時沒找到更好的方法
  void LoadXAMLMethod()
  {
   // Changing content need to be out of constructor
   try {
    System.Diagnostics.Debug.WriteLine( "LoadXaml" );
    string szActualPath = Path.GetFullPath( @".\..\..\..\WpfCustomControls\UserControl1.xaml" );
    string szXaml = GetXamlWithoutxClassDirective( szActualPath );
    UserControl rootObject = XamlReader.Parse( szXaml ) as UserControl;
    this.Content = rootObject;
    this.DataContext = new CncViewModel( m_Source );
   }
   catch( FileNotFoundException ex ) {
    MessageBox.Show( ex.Message.ToString() );
   }
  }

  private string GetXamlWithoutxClassDirective( string m_szFilepath )
  {
   string szFailedText = string.Empty;
   string szFullText = string.Empty;
   using( StreamReader sr = new StreamReader( m_szFilepath ) ) {
    szFullText = sr.ReadToEnd();
   }
   int nDirectiveStartIndex = szFullText.IndexOf( "x:Class" );
   if( nDirectiveStartIndex == -1 ) {
    return szFullText;
   }
   // finding x:Class="abcdeftg.gxy"
   int nStartQuote = szFullText.IndexOf( "\"", nDirectiveStartIndex + 1 );
   if( nStartQuote == -1 ) {
    return szFailedText;
   }
   int nEndQuote = szFullText.IndexOf( "\"", nStartQuote + 1 );
   if( nEndQuote == -1 ) {
    return szFailedText;
   }
   //  where "after" second " 
   int nDirectiveEndIndex = nEndQuote + 1;
   int nDirectiveLength = nDirectiveEndIndex - nDirectiveStartIndex;
   string szNotDirectiveText = szFullText.Remove( nDirectiveStartIndex, nDirectiveLength );
   return szNotDirectiveText;
  }

   可以發現我直接在runtime 讀UserControl1.xaml in runtime. 這樣User就能直接把xaml desiner當作設計用GUI.

如何連結View跟Model (Data)?

    可以參考Data Binding Overview,或是看下面的懶人版:
  1. {Binding PropertyOfViewModel} 讓你可以把資料直接綁到DataContext的properties上,注意只有properties, field不支援
  2. DataContext會從Parent繼承,除非有額外指定
    ( ref: What is this "DataContext" you speak of? )

   我用ViewModel做了一些自製語法,並且有設定this.DataContext = new CncViewModel( m_Source );

  1. 因為要讓使用者自訂圖片與多國語系,我用特殊語法支援字串
  2. 使用 IIndexableGetter讓自訂語法 STR[InsertKeyHere]可以拿到多國語系字串,PIC[Filepath] 則會顯示特定位置的圖片.
  3. 注意 ICommand MyCommand 他讓xaml變成loose xaml, 不再需要event handler

 public class CncViewModel : INotifyPropertyChanged
 {
  public CncViewModel( RSource source )
  {
   m_R = source;
   m_Pic = new MockPicture();
   m_Str = new MockStringLookup();

   // Using a timer to update value
   tmr = new System.Timers.Timer();
   tmr.AutoReset = true;
   tmr.Interval = 300;
   tmr.Elapsed += M_Source_TimeTicked;
   tmr.Enabled = true;

  }
  Timer tmr;
  private void M_Source_TimeTicked( object sender, EventArgs e )
  {
   firePropChanged( "R" );
   firePropChanged( "STR" );
  }
  void firePropChanged( string szProp )
  {
   if( PropertyChanged == null ) {
    return;
   }
   PropertyChanged( this, new PropertyChangedEventArgs( szProp ) );
  }
  public IIndexableGetter<int> R
  {
   get
   {
    return m_R;
   }
  }
  public IIndexableGetter<string> STR
  {
   get
   {
    return m_Str;
   }
  }
  public IIndexableGetter<BitmapImage> PIC
  {
   get
   {
    return m_Pic;
   }
  }

  public ICommand MyCommand
  {
   get
   {
    return new RelayCommand( ParseSyntecCmd );
   }
  }

  public void ParseSyntecCmd( string[] arr )
  {
   Console.WriteLine( "Cmd fired" );
   if( arr == null ) {
    Console.WriteLine( "Null" );
    return;
   }
   foreach( var szStr in arr ) {
    // Implement string command here
    Console.WriteLine( "Prm: " + szStr );
   }
  }

  // private
  IIndexableGetter<BitmapImage> m_Pic;
  IIndexableGetter<int> m_R;
  IIndexableGetter<string> m_Str;
  public event PropertyChangedEventHandler PropertyChanged;
 }


 public class MockStringLookup : IIndexableGetter<string>
 {
  public string this[ string szIndex ]
  {
   get
   {
    // Implement your own custom string or multilanguage mechanism.
    string[] szSplit = szIndex.Split( ':' );
    if( szSplit.Length == 0 ) {
     return string.Empty;
    }
    return szSplit[ szSplit.Length - 1 ];
   }
  s}
 }sdfsd






  1. Button.Command 示範如何使用CommandParameter
    1. 如果只有一行,使用CommandParameter="SomeSyntax".
    2. Example Button.CommandParameter below shows how to define an array of string in xaml.
  2. Button.Style應該滿好懂得,R[6000]=1背景是粉紅色  R[6000]=0變成白色

        <Button
            Grid.Column="2"
            x:Name="CycleStart" 
            Margin="22,10,105,0" VerticalAlignment="Top" Height="49"
            Command="{Binding MyCommand}">
            <Button.CommandParameter>
                <x:Array Type="{x:Type s:String}">
                    <s:String>MySyntax_Inv R6000.0</s:String>
                    <s:String>MySyntax_R6001=2</s:String>
                </x:Array>
            </Button.CommandParameter>
            <Button.Style>
                <Style TargetType="{x:Type Button}">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding R[6000]}"
                            Value="1">
                            <Setter Property="Background" Value="Pink" />
                        </DataTrigger>
                        <DataTrigger Binding="{Binding R[6000]}"
                            Value="0">
                            <Setter Property="Background" Value="White" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Button.Style>
            <Image x:Name="image" Height="43" Width="43" Source="{Binding PIC[Images\\play.png]}"/>
        </Button>

WPF Designer模擬runtime顯示

Example here

   使用了自訂語法之後,Designer就沒辦法正常顯示資料,因為要透過runtime的資料才能正常顯示尺寸(有些control設定時,會根據字型大小改變,一個方式是每次改好就執行程式,另一個方式則是建立MockViewModel讓它顯示於Designer上,另一個副作用是xaml intellisense也會有用,下面是注意事項

  1. 實作 MockDesignViewModel.
  2. xmlns:d的意思是使用 xaml blend namespace  "d". 
  3. mc:Ignorable="d" 表示runtime可忽略d
  4. d:DataContext 表示只在deisntime建立 MockDesignViewModel
  5. 顯示如果有問題,可以參考 How to debug in wpf design view 


<--! Example of Usercontrol using a design instance >
<UserControl x:Class="WpfCustomControl.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
             xmlns:s="clr-namespace:System;assembly=mscorlib"
             xmlns:local="clr-namespace:WpfCustomControl"
             mc:Ignorable="d" 
             d:DesignHeight="800" d:DesignWidth="600"
             d:DataContext="{d:DesignInstance Type=local:MockDesignViewModel, IsDesignTimeCreatable=True}"
             >
</UserControl>


// Exmaple of a mock design view model.
 public class MockDesignViewModel
 {
  public MockDesignViewModel()
  {
   m_Pic = new MockPicture();
   m_R = new MockRegister();
   m_Str = new MockStringLookup();
  }

  public IIndexableGetter<BitmapImage> PIC
  {
   get
   {
    return m_Pic;
   }
  }

  public IIndexableGetter<int> R
  {
   get
   {
    return m_R;
   }
  }

  public IIndexableGetter<string> STR
  {
   get
   {
    return m_Str;
   }
  }

  IIndexableGetter<BitmapImage> m_Pic;
  IIndexableGetter<int> m_R;
  IIndexableGetter<string> m_Str;
 }

 public class MockStringLookup : IIndexableGetter<string>
 {
  public string this[ string szIndex ]
  {
   get
   {
    string[] szSplit = szIndex.Split( ':' );
    if( szSplit.Length == 0 ) {
     return string.Empty;
    }
    return szSplit[ szSplit.Length - 1 ];
   }
  }
 }

 public class MockRegister : IIndexableGetter<int>
 {
  public int this[ string szIndex ]
  {
   get
   {
    int nIndex = 0;
    bool bParse = Int32.TryParse( szIndex, out nIndex );
    return nIndex;
   }
  }
 }

 public class MockPicture : IIndexableGetter<BitmapImage>
 {
  public MockPicture()
  {
   // This is where solution is in design time
   string szWorkingDir = Environment.CurrentDirectory;

   // Get out of 3 layer folder
   m_szPictureDir = szWorkingDir;
  }

  public BitmapImage this[ string szIndex ]
  {
   get
   {
    string szCombinedpath = m_szPictureDir + "\\" + szIndex;
    string szActualPath = Path.GetFullPath( szCombinedpath );
    var uri = new Uri( szActualPath );
    BitmapImage bmp = new BitmapImage( uri );
    return bmp;
   }
  }
  // Private
  string m_szPictureDir;
 }


References:
  1. Basic knowledge of WPF
    1. Understanding the change in mindset when switching from WinForms to WPF
    2. What is this "DataContext" you speak of?
    3. A Simple MVVM Example
  2. WPF Hosting
    1. Walkthrough: Hosting a WPF Composite Control in Windows Forms.
  3. Data binding
    1. Data Binding Overview
  4. ICommand
    1. ICommand MVVM implementation
  5. DesignTime settings 
    1. How to debug in wpf design view

Monday, September 16, 2019

Quick Guide for Using WPF UserControl in Winform

Preface


In this article, you will know following things in a tiny example.
  1. Create a winform to host WPF control.
  2. Let user change layout by editing xaml without compilation, so you don't have to worry about version control of your user.
  3. Inject dependantcy in WPF from winform.
    ( It is easy, but I found it's hard to know when I have zero knowledge about WPF. )
  4. How to let user self design in WPF designer looks identical ( or similar ) as in runtime.
This article suites for who
  1. Having experience in winform C# development.
  2. Don't have time to start from scratch.
  3. Wants to give WPF a try in your winform project.
  4. Searching hard but don't know how to pass reference into WPF viewmodel.

Why would I want to try wpf:
  1. I want my users to change GUI layout without compilation.
  2. My users don't know how to write C# code, but they have some knowledge of xml.
Before running through the example, here are some terms you need to know.
  1. MVVM-- Model-View-ViewModel: Kind of similar to MVC.
    1. Model: Your data.
    2. View: What you see in WPF designer. Equivalent to winform UserControl.Designer.cs
    3. ViewModel: In a WPF control, it s your Control.Datacontext. It controls how view to display model. 
    4. In MVVM, code-behind is better to be as tiny as possible.
  2. Code-Behind:
    1. It is equivalent to C# UserControl.cs
  3. Loose xaml:
    1. Xaml without x:Class attribute, where x:Class is equivalent to winform designer partial class.

   First of all, you need a winform to host WPF control, and there is a good example in microsoft document

   Though this example is complete enough, it uses code-behinde to handle button click event.
This xaml of example is not loose xaml.. You can't load it in runtime property, unless doing something dirty (subscribing events) in code behind.
I will suggest to use ICommand interface to handle button click and it will be implemented in my example. I don't use any third-party library in this example, but you may want to take a look at MVVM Light  or Prism if you dig deeper in WPF.

How to host WPF Control

Example here. It is using dot net 4.0, but 4.5 or above will work as well.
  1. Click Example.sln.
  2. Look at WPFHost project. And following dlls are needed
    1. PresentationCore
    2. PresentationFramework
    3. System
    4. WindowsBase
  3. Form1.cs will show you how to host WPF control.
  4. RSource is dependency injected into WPF usercontrol. It is connected to device registers, but it's out of scope of this article.
  5. ctrlHost is hosting WPF control and MUST be added in a panel control.
  6. In microsoft document, this code of snippet is placed in Form_Load. I don't know what would went wrong if it's in ctor. Haven't try it yet.
  private void Form1_Load( object sender, EventArgs e )
  {
   RSource Source = new RSource();
   ctrlHost = new ElementHost();
   ctrlHost.Dock = DockStyle.Fill;
   panel1.Controls.Add( ctrlHost );
   wpfAddressCtrl = new WpfCustomControl.UserControl1( Source );
   wpfAddressCtrl.InitializeComponent();
   ctrlHost.Child = wpfAddressCtrl;
  } 


Make a WPF Control with MVVM

Example here ( same as above one, just in case you're jumping into here )
  1. Look at WpfCustomControls project
  2. Right click on UserControl1.xaml and choose "View code", or expand this control and click UserControl1.xaml.cs.
  3. this.Content is the View.
  4. this.DataContext is the ViewModel, and that's why I put my RSource reference in CncViewModel.
  5. Also in GetXamlWithoutxClassDirective, I remove x:Class attribute while loading. This method is pretty straight-forward but I can't find any substitute of it..
  void LoadXAMLMethod()
  {
   // Changing content need to be out of constructor
   try {
    System.Diagnostics.Debug.WriteLine( "LoadXaml" );
    string szActualPath = Path.GetFullPath( @".\..\..\..\WpfCustomControls\UserControl1.xaml" );
    string szXaml = GetXamlWithoutxClassDirective( szActualPath );
    UserControl rootObject = XamlReader.Parse( szXaml ) as UserControl;
    this.Content = rootObject;
    this.DataContext = new CncViewModel( m_Source );
   }
   catch( FileNotFoundException ex ) {
    MessageBox.Show( ex.Message.ToString() );
   }
  }

  private string GetXamlWithoutxClassDirective( string m_szFilepath )
  {
   string szFailedText = string.Empty;
   string szFullText = string.Empty;
   using( StreamReader sr = new StreamReader( m_szFilepath ) ) {
    szFullText = sr.ReadToEnd();
   }
   int nDirectiveStartIndex = szFullText.IndexOf( "x:Class" );
   if( nDirectiveStartIndex == -1 ) {
    return szFullText;
   }
   // finding x:Class="abcdeftg.gxy"
   int nStartQuote = szFullText.IndexOf( "\"", nDirectiveStartIndex + 1 );
   if( nStartQuote == -1 ) {
    return szFailedText;
   }
   int nEndQuote = szFullText.IndexOf( "\"", nStartQuote + 1 );
   if( nEndQuote == -1 ) {
    return szFailedText;
   }
   //  where "after" second " 
   int nDirectiveEndIndex = nEndQuote + 1;
   int nDirectiveLength = nDirectiveEndIndex - nDirectiveStartIndex;
   string szNotDirectiveText = szFullText.Remove( nDirectiveStartIndex, nDirectiveLength );
   return szNotDirectiveText;
  }

   You may find I'm loading UserControl1.xaml in runtime. Because I will use UserControl1.xaml as designer interface, so User may user xaml designer as GUI to design their own view.

How do I connect View to data?

    Look at Data Binding Overview would be better. Also I have simple summary here for you:
  1. Using {Binding PropertyOfViewModel} will let you bind properties of your DataContext directly. Only properties work, a field is not allowed.
  2. Every element of DataContext is inherit from parent until you assign a different one.
    (ref: What is this "DataContext" you speak of?)

   Here's my ViewModel and my syntax implementation.
Remember I set this.DataContext = new CncViewModel( m_Source );

  1. In my case, I need to let user put custom picture and string on xaml, so I need my own syntax to let WPF control get my string.
  2. I use IIndexableGetter to provide indexer of let STR[InsertKeyHere] to get my custom string syntax. Also PIC[Filepath] would also display the picture.
  3. You may see   ICommand MyCommand  . That is the implementation about ICommand and it will set you free from event. If 

 public class CncViewModel : INotifyPropertyChanged
 {
  public CncViewModel( RSource source )
  {
   m_R = source;
   m_Pic = new MockPicture();
   m_Str = new MockStringLookup();

   // Using a timer to update value
   tmr = new System.Timers.Timer();
   tmr.AutoReset = true;
   tmr.Interval = 300;
   tmr.Elapsed += M_Source_TimeTicked;
   tmr.Enabled = true;

  }
  Timer tmr;
  private void M_Source_TimeTicked( object sender, EventArgs e )
  {
   firePropChanged( "R" );
   firePropChanged( "STR" );
  }
  void firePropChanged( string szProp )
  {
   if( PropertyChanged == null ) {
    return;
   }
   PropertyChanged( this, new PropertyChangedEventArgs( szProp ) );
  }
  public IIndexableGetter<int> R
  {
   get
   {
    return m_R;
   }
  }
  public IIndexableGetter<string> STR
  {
   get
   {
    return m_Str;
   }
  }
  public IIndexableGetter<BitmapImage> PIC
  {
   get
   {
    return m_Pic;
   }
  }

  public ICommand MyCommand
  {
   get
   {
    return new RelayCommand( ParseSyntecCmd );
   }
  }

  public void ParseSyntecCmd( string[] arr )
  {
   Console.WriteLine( "Cmd fired" );
   if( arr == null ) {
    Console.WriteLine( "Null" );
    return;
   }
   foreach( var szStr in arr ) {
    // Implement string command here
    Console.WriteLine( "Prm: " + szStr );
   }
  }

  // private
  IIndexableGetter<BitmapImage> m_Pic;
  IIndexableGetter<int> m_R;
  IIndexableGetter<string> m_Str;
  public event PropertyChangedEventHandler PropertyChanged;
 }


 public class MockStringLookup : IIndexableGetter<string>
 {
  public string this[ string szIndex ]
  {
   get
   {
    // Implement your own custom string or multilanguage mechanism.
    string[] szSplit = szIndex.Split( ':' );
    if( szSplit.Length == 0 ) {
     return string.Empty;
    }
    return szSplit[ szSplit.Length - 1 ];
   }
  s}
 }sdfsd




    The xaml of a button named "CycleStart". ( It's not really called CycleStart in loose xaml, because loose xaml don't have x syntax. ).

  1. Button.Command show you how to bind multiply self defined syntax in Command
    1. If there is only one parameter, simply use  CommandParameter="SomeSyntax".
    2. Example Button.CommandParameter below shows how to define an array of string in xaml.
  2. Button.Style is a self explanatory demo. If R[6000] value is 1, background is pink. If R[6000] is 0, background is white.

        <Button
            Grid.Column="2"
            x:Name="CycleStart" 
            Margin="22,10,105,0" VerticalAlignment="Top" Height="49"
            Command="{Binding MyCommand}">
            <Button.CommandParameter>
                <x:Array Type="{x:Type s:String}">
                    <s:String>MySyntax_Inv R6000.0</s:String>
                    <s:String>MySyntax_R6001=2</s:String>
                </x:Array>
            </Button.CommandParameter>
            <Button.Style>
                <Style TargetType="{x:Type Button}">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding R[6000]}"
                            Value="1">
                            <Setter Property="Background" Value="Pink" />
                        </DataTrigger>
                        <DataTrigger Binding="{Binding R[6000]}"
                            Value="0">
                            <Setter Property="Background" Value="White" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Button.Style>
            <Image x:Name="image" Height="43" Width="43" Source="{Binding PIC[Images\\play.png]}"/>
        </Button>

Simulate runtime display in WPF designer

Example here ( same as above one, just in case you're jumping into here )

   You might find some of display (eg: content strings) in designer is different from runtime. Rather than always run your program on runtime to see the change, all you need is a mocked ViewModel to display in designer. Also it will allow user to use intellisense in xaml which is a convenient side-effect of mocking ViewModel. Here are some notes:

  1. Implement your own MockDesignViewModel.
  2. xmlns:d xaml namespace aliasing as "d" in xaml. In this case, d is for blend to be shown in designtime.
  3. mc:Ignorable="d"  means ignore xmlns:d it on runtime.
  4. d:DataContext ...etc means it will create an instance of MockDesignViewModel on designtime.
  5. If you have troubles in getting design view right, How to debug in wpf design view would help a lot.


<--! Example of Usercontrol using a design instance >
<UserControl x:Class="WpfCustomControl.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
             xmlns:s="clr-namespace:System;assembly=mscorlib"
             xmlns:local="clr-namespace:WpfCustomControl"
             mc:Ignorable="d" 
             d:DesignHeight="800" d:DesignWidth="600"
             d:DataContext="{d:DesignInstance Type=local:MockDesignViewModel, IsDesignTimeCreatable=True}"
             >
</UserControl>


// Exmaple of a mock design view model.
 public class MockDesignViewModel
 {
  public MockDesignViewModel()
  {
   m_Pic = new MockPicture();
   m_R = new MockRegister();
   m_Str = new MockStringLookup();
  }

  public IIndexableGetter<BitmapImage> PIC
  {
   get
   {
    return m_Pic;
   }
  }

  public IIndexableGetter<int> R
  {
   get
   {
    return m_R;
   }
  }

  public IIndexableGetter<string> STR
  {
   get
   {
    return m_Str;
   }
  }

  IIndexableGetter<BitmapImage> m_Pic;
  IIndexableGetter<int> m_R;
  IIndexableGetter<string> m_Str;
 }

 public class MockStringLookup : IIndexableGetter<string>
 {
  public string this[ string szIndex ]
  {
   get
   {
    string[] szSplit = szIndex.Split( ':' );
    if( szSplit.Length == 0 ) {
     return string.Empty;
    }
    return szSplit[ szSplit.Length - 1 ];
   }
  }
 }

 public class MockRegister : IIndexableGetter<int>
 {
  public int this[ string szIndex ]
  {
   get
   {
    int nIndex = 0;
    bool bParse = Int32.TryParse( szIndex, out nIndex );
    return nIndex;
   }
  }
 }

 public class MockPicture : IIndexableGetter<BitmapImage>
 {
  public MockPicture()
  {
   // This is where solution is in design time
   string szWorkingDir = Environment.CurrentDirectory;

   // Get out of 3 layer folder
   m_szPictureDir = szWorkingDir;
  }

  public BitmapImage this[ string szIndex ]
  {
   get
   {
    string szCombinedpath = m_szPictureDir + "\\" + szIndex;
    string szActualPath = Path.GetFullPath( szCombinedpath );
    var uri = new Uri( szActualPath );
    BitmapImage bmp = new BitmapImage( uri );
    return bmp;
   }
  }
  // Private
  string m_szPictureDir;
 }


References:
  1. Basic knowledge of WPF
    1. Understanding the change in mindset when switching from WinForms to WPF
    2. What is this "DataContext" you speak of?
    3. A Simple MVVM Example
  2. WPF Hosting
    1. Walkthrough: Hosting a WPF Composite Control in Windows Forms.
  3. Data binding
    1. Data Binding Overview
  4. ICommand
    1. ICommand MVVM implementation
  5. DesignTime settings 
    1. How to debug in wpf design view

If there is any inaccurate description, that would be my fault. Please feel free to let me know what is wrong.

Sunday, December 4, 2016

How does a monkey to start Dota2 Modding

My goal is to create an infinite bouncing Luna moon glaives.
1. Just to change mechanisms.
2. No FX required.

Progress:


All I have to do:
1. Get mod working
 --  Myll's getting started really helped.
https://moddota.com/forums/discussion/142/getting-started-with-dota-2-modding

2. Get to know how to customize an ability ( Data driven ability,  Key value and Lua script )
-- Noya's Beginner's guide is cool.
https://moddota.com/forums/discussion/135/beginners-guide-to-dota-scripting

3. Oh my god.. I can't just change a parameter to let moon glaives bouncing 666 times..
    Shall I reinvent the wheel?
-- All works had been done by MNoya's Sepll Library! Thank to open source!
https://github.com/Pizzalol/SpellLibrary

4. Umm.. this wheel doesn't work.
-- Seems after dota2 modkit merged all document, everything goes wrong..
-- Taking time to check it out later.