مطالب
OpenCVSharp #5
استفاده از پنجره‌ی native خود OpenCV، روش مرسومی است در زبان‌های مختلف برنامه نویسی که از OpenCV استفاده می‌کنند و این پنجره مستقل است از سکوی کاری مورد استفاده. اما شاید در دات نت علاقمند باشید که نتیجه‌ی عملیات را در یک picture box استاندارد نمایش دهید. در ادامه، تبدیل تصاویر OpenCV را به فرمت دات نت، در دو قالب برنامه‌های WinForms و همچنین WPF، بررسی خواهیم کرد.


استفاده از OpenCVSharp در برنامه‌های WinForms به کمک PictureBoxIpl

یکی از اسمبلی‌های کتابخانه‌ی OpenCVSharp را که در پوشه‌ی bin برنامه می‌توان مشاهده کرد، OpenCvSharp.UserInterface.dll نام دارد. این اسمبلی حاوی یک picture box جدید به نام PictureBoxIpl است که می‌تواند تصاویری را با فرمت IplImage، دریافت کند.


می‌توانید این picture box ویژه را از طریق منوی ToolBox -> Choose items و سپس صفحه‌ی دیالوگ فوق، به نوار ابزار WinForms اضافه کرده و از آن استفاده کنید و یا می‌توان با کدنویسی نیز به آن دسترسی یافت:
using (var iplImage = new IplImage(@"..\..\Images\Penguin.png", LoadMode.Color))
{
    Cv.Dilate(iplImage, iplImage);
 
    var pictureBoxIpl = new OpenCvSharp.UserInterface.PictureBoxIpl
    {
        ImageIpl = iplImage,
        AutoSize = true
    };
    flowLayoutPanel1.Controls.Add(pictureBoxIpl); 
}
در اینجا تصویر مورد نظر را توسط کلاس IplImage بارگذاری کرده و سپس برای نمونه فیلتر Dilate را به آن اعمال کرده‌ایم. سپس وهله‌ی جدیدی از کنترل PictureBoxIpl ایجاد و خاصیت ImageIpl آن، به تصویر بارگذاری شده، تنظیم و در آخر این picture box با کدنویسی به صفحه اضافه شده‌است.

یک نکته
هر نوع تغییری به iplImage پس از انتساب آن به خاصیت ImageIpl، نمایش داده نخواهد شد. برای به حداقل رساندن سربار ایجاد اشیاء جدید (خصوصا برای نمایش اطلاعات رسیده‌ی از دوربین یا WebCam)، از متد RefreshIplImage استفاده کنید. این متد بجای ایجاد یک شیء جدید، تنها ناحیه‌ی موجود را مجددا ترسیم خواهد کرد و بسیار سریع است:
 pictureBoxIpl.RefreshIplImage(iplImage);


استفاده از OpenCVSharp در برنامه‌های WinForms به کمک PictureBox

اگر نخواهید از کنترل جدید PictureBoxIpl استفاده کنید، می‌توان از همان Picture box استاندارد WinForms نیز کمک گرفت:
Bitmap bitmap;
using (var iplImage = new IplImage(@"..\..\Images\Penguin.png", LoadMode.Color))
{
    bitmap = iplImage.ToBitmap(); // BitmapConverter.ToBitmap()
}
 
var pictureBox = new PictureBox
{
    Image = bitmap,
    ClientSize = bitmap.Size
}; 
 
flowLayoutPanel1.Controls.Add(pictureBox);
تنها نکته‌ای که در اینجا جدید است، استفاده از متد الحاقی ToBitmap می‌باشد که در کلاس BitmapConverter کتابخانه‌ی OpenCVSharp تعریف شده‌است. به این ترتیب تصویر با فرمت OpenCV، به یک Bitmap دات نتی تبدیل می‌شود. اکنون می‌توان این بیت‌مپ را برای مثال به یک Picture box استاندارد انتساب داد و یا حتی متد Save آن‌را فراخوانی کرد و آن‌را بر روی دیسک سخت، ذخیره نمود.

یک نکته
در اینجا نیز برای به حداقل رسانی به روز رسانی‌های بعدی picture box بهتر است از متد ToBitmap به شکل زیر کمک گرفت:
 iplImage.ToBitmap(dst: (Bitmap)pictureBox.Image);
به این ترتیب سربار وهله سازی یک شیء جدید Bitmap حذف خواهد شد و صرفا ناحیه‌ی نمایشی مجددا ترسیم می‌شود.





استفاده از OpenCVSharp در برنامه‌های WPF

در WPF می‌توان با استفاده از متد الحاقی ToWriteableBitmap کلاس BitmapConverter، فرمت IplImage را به منبع تصویر یک کنترل تصویر استاندارد، تبدیل کرد:
using System.Windows.Media;
using OpenCvSharp;
using OpenCvSharp.Extensions;
 
namespace OpenCVSharpSample05Wpf
{
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();
            loadImage();
        }
 
        private void loadImage()
        {
            using (var iplImage = new IplImage(@"..\..\Images\Penguin.png", LoadMode.Color))
            {
                Cv.Dilate(iplImage, iplImage);
 
                Image1.Source = iplImage.ToWriteableBitmap(PixelFormats.Bgr24);
            }
        }
    }
}

کدهای کامل WPF و WinForms این مطلب برای دریافت.
مسیرراه‌ها
WPF
          مطالب
          بستن یک پنجره از طریق ViewModel با استفاده از خصوصیت های پیوست شده هنگام استفاده از الگوی MVVM
           در نظر بگیرید که یک پروژه WPF را با الگوی MVVM پیاده سازی کرده اید و نیاز پیدا می‌کنید تا یک پنجره را از طریق کد ببندید. از آنجایی که به کنترل Window درون ViewModel دسترسی ندارید، نمی‌توانید از متد Close آن برای اینکار استفاده کنید. راه‌های مختلفی برای اینکار وجود دارند، مثلا اگر از MVVM Light Toolkit استفاده می‌کنید با ارسال یک Message و نوشتن یک تکه کد در CodeBehind پنجره می‌توانید اینکار را انجام بدهید.
          اما برای اینکار یک راه حل ساده‌تری بدون نیاز به نوشتن کد در CodeBehind و استفاده از Toolkit خاصی وجود دارد و آن استفاده ازخاصیت‌های پیوست شده یا Attached Properties است. برای اینکار یک خاصیت از نوع Boolean مانند زیر تعریف می‌کنیم و آن را به پنجره ای که می‌خواهیم Colse شود پیوست می‌کنیم.
          namespace TestProject.XamlServices
          {
              public class CloseBehavior
              {
                  public static readonly DependencyProperty CloseProperty = 
          DependencyProperty.RegisterAttached("Close", typeof(bool), typeof(CloseBehavior), new UIPropertyMetadata(false, OnClose));
          
                  private static void OnClose(DependencyObject sender, DependencyPropertyChangedEventArgs e)
                  {
                      if (!(e.NewValue is bool) || !((bool) e.NewValue)) return;
                      var win = GetWindow(sender);
                      if (win != null)
                          win.Close();
                  }
          
                  private static Window GetWindow(DependencyObject sender)
                  {
                      Window w = null;
                      if (sender is Window)
                          w = (Window)sender;
                      return w ?? (w = Window.GetWindow(sender));
                  }
          
                  public static bool GetClose(Window target)
                  {
                      return (bool)target.GetValue(CloseProperty);
                  }
          
                  public static void SetClose(DependencyObject target, bool value)
                  {
                      target.SetValue(CloseProperty, value);
                  }
              }
          } 
          در تکه کد بالا یک خصوصیت از نوع  Boolean  ایجاد کردیم که می‌تواند به هر پنجره ای که قرار است از طریق کد بسته شود، پیوست شود. خصوصیت‌های پیوست شده یک  Callback مربوط به تغییر مقدار دارند که یک  متداستاتیک است و مقدار جدید، از طریق  EventArg و   شیءایی که این خاصیت به آن پیوست شده نیز بعنوان Source  به آن  ارسال می‌شود. هر وقت مقدار خصوصیت، تغییر کند این متد فراخوانی می‌گردد. در کد بالا متد OnClose ایجاد شده است و زمانی که مقدار این خصوصیت برابر true می‌شود پنجره close خواهد شد.
          برای استفاده از این خصوصیت و اتصال آن باید یک خصوصیت از نوع Boolean  نیز در ViewModel مربوط به Window ایجاد کنید:  
           private bool _isClose;
           public bool IsClose
             {
                get { return _isClose; }
                set
                {
                 _isClose = value;
                OnClosed();
                RaisePropertyChanged("IsClose");
                }
          }
          و آن را به صورت زیر Bind کنید:  
          <Window x:Class="TestProject.TestView"
                  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                  xmlns:xamlServices="clr-namespace:TestProject.XamlServices;assembly=TestProject.XamlServices"
                 xamlServices:CloseBehavior.Close="{Binding IsClose}">
                 ...
          </Window>
          پس از انجام اتصالات فوق، کافیست   هر جایی از ViewModel که نیاز است پنجره بسته شود،مقدار این خصوصیت برابر False بشود.
          مطالب
          مدیریت همزمانی و طول عمر توالی‌های Reactive extensions در یک برنامه‌ی دسکتاپ
          پس از معرفی و مشاهده‌ی نحوه‌ی ایجاد توالی‌ها در Rx، بهتر است با نمونه‌ای از نحوه‌ی استفاده از آن در یک برنامه‌ی WPF آشنا شویم.
          بنابراین ابتدا دو بسته‌ی Rx-Main و Rx-WPF را توسط نیوگت، به یک برنامه‌ی جدید WPF اضافه کنید:
           PM> Install-Package Rx-Main
          PM> Install-Package Rx-WPF
          فرض کنید قصد داریم محتوای یک فایل حجیم را به نحو ذیل خوانده و توسط Rx نمایش دهیم.
                  private static IEnumerable<string> readFile(string filename)
                  {
                      using (TextReader reader = File.OpenText(filename))
                      {
                          string line;
                          while ((line = reader.ReadLine()) != null)
                          {
                              Thread.Sleep(100);
                              yield return line;
                          }
                      }
                  }
          در اینجا برای ایجاد یک توالی IEnumerable ، از yield return استفاده شده‌است. همچنین Thread.Sleep آن جهت بررسی قفل شدن رابط کاربری در حین خواندن فایل به عمد قرار گرفته است.
          UI برنامه نیز به نحو ذیل است:
          <Window x:Class="WpfApplicationRxTests.MainWindow"
                  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                  Title="MainWindow" Height="450" Width="525">
              <Grid>
                  <Grid.RowDefinitions>
                      <RowDefinition Height="Auto" />
                      <RowDefinition  Height="*" />
                      <RowDefinition Height="Auto" />
                  </Grid.RowDefinitions>
                  <Button Grid.Row="0" Name="btnGenerateSequence" Click="btnGenerateSequence_Click">Generate sequence</Button>
                  <ListBox Grid.Row="1" Name="lstNumbers"  />
                  <Button Grid.Row="2" IsEnabled="False" Name="btnStop" Click="btnStop_Click">Stop</Button>
              </Grid>
          </Window>
          با این کدها
          using System;
          using System.Collections.Generic;
          using System.Collections.ObjectModel;
          using System.IO;
          using System.Reactive.Concurrency;
          using System.Reactive.Linq;
          using System.Threading;
          using System.Windows;
          
          namespace WpfApplicationRxTests
          {
              public partial class MainWindow
              {
                  public MainWindow()
                  {
                      InitializeComponent();
                  }
          
                  private static IEnumerable<string> readFile(string filename)
                  {
                      using (TextReader reader = File.OpenText(filename))
                      {
                          string line;
                          while ((line = reader.ReadLine()) != null)
                          {
                              Thread.Sleep(100);
                              yield return line;
                          }
                      }
                  }
          
                  private IDisposable _subscribe;
                  private void btnGenerateSequence_Click(object sender, RoutedEventArgs e)
                  {
                      btnGenerateSequence.IsEnabled = false;
                      btnStop.IsEnabled = true;
          
                      var items = new ObservableCollection<string>();
                      lstNumbers.ItemsSource = items;
                      _subscribe = readFile("test.txt").ToObservable()
                                         .SubscribeOn(ThreadPoolScheduler.Instance)
                                         .ObserveOn(DispatcherScheduler.Current)
                                         .Finally(finallyAction: () =>
                                         {
                                             btnGenerateSequence.IsEnabled = true;
                                             btnStop.IsEnabled = false;
                                         })
                                         .Subscribe(onNext: line =>
                                         {
                                             items.Add(line);
                                         },
                                         onError: ex => { },
                                         onCompleted: () =>
                                         {
                                             //lstNumbers.ItemsSource = items;
                                         });
                  }
          
                  private void btnStop_Click(object sender, RoutedEventArgs e)
                  {
                      _subscribe.Dispose();
                  }
              }
          }

          توضیحات

          حاصل متد readFile را که یک توالی معمولی IEnumerable را ایجاد می‌کند، توسط فراخوانی متد ToObservable، تبدیل به یک خروجی IObservable کرده‌ایم تا بتوانیم هربار که سطری از فایل مدنظر خوانده می‌شود، نسبت به آن واکنش نشان دهیم.
          متد SubscribeOn مشخص می‌کند که این توالی Observable باید بر روی چه تردی اجرا شود. در اینجا از ThreadPoolScheduler.Instance استفاده شده‌است تا در حین خواندن فایل، رابط کاربری در حالت هنگ به نظر نرسد و ترد جاری (ترد اصلی برنامه) به صورت خودکار آزاد گردد.
          از متد ObserveOn با پارامتر DispatcherScheduler.Current استفاده کرده‌ایم، تا نتیجه‌ی واکنش‌های به خوانده شدن سطرهای یک فایل مفروض، در ترد اصلی برنامه صورت گیرد. در غیر اینصورت امکان کار کردن با عناصر رابط کاربری در یک ترد دیگر وجود نخواهد داشت و برنامه کرش می‌کند.
          در قسمت‌های قبل، صرفا متد Subscribe را مشاهده کرده بودید. در اینجا از متد Finally نیز استفاده شده‌است. علت اینجا است که اگر در حین خواندن فایل خطایی رخ دهد، قسمت onError متد Subscribe اجرا شده و دیگر به پارامتر onCompleted آن نخواهیم رسید. اما متد Finally آن همیشه در پایان عملیات اجرا می‌شود.
          خروجی حاصل از متد Subscribe، از نوع IDisposable است. Rx به صورت خودکار پس از پردازش آخرین عنصر توالی، این شیء را Dispose می‌کند. اینجا است که callback متد Finally یاد شده فراخوانی خواهد شد. اما اگر در حین خواندن یک فایل طولانی، کاربر علاقمند باشد تا عملیات را متوقف کند، تنها کافی است که به صورت صریح، این شیء را Dispose نماید. به همین جهت است که مشاهده می‌کنید، این خروجی به صورت یک فیلد تعریف شده‌است تا در متد Stop بتوانیم آن‌را در صورت نیاز Dispose کنیم.


          مثال فوق را از اینجا نیز می‌توانید دریافت کنید:
          WpfApplicationRxTests.zip
            
          مطالب
          MEF و الگوی Singleton

          در مورد معرفی مقدماتی MEF می‌توانید به این مطلب مراجعه کنید و در مورد الگوی Singleton به اینجا.


          کاربردهای الگوی Singleton عموما به شرح زیر هستند:
          1) فراهم آوردن دسترسی ساده و عمومی به DAL (لایه دسترسی به داده‌ها)
          2) دسترسی عمومی به امکانات ثبت وقایع سیستم در برنامه logging -
          3) دسترسی عمومی به تنظیمات برنامه
          و موارد مشابهی از این دست به صورتیکه تنها یک روش دسترسی به این اطلاعات وجود داشته باشد و تنها یک وهله از این شیء در حافظه قرار گیرد.

          با استفاده از امکانات MEF دیگر نیازی به نوشتن کدهای ویژه تولید کلاس‌های Singleton نمی‌باشد زیرا این چارچوب کاری دو نوع روش وهله سازی از اشیاء (PartCreationPolicy) را پشتیبانی می‌کند: Shared و NonShared . حالت Shared دقیقا همان نام دیگر الگوی Singleton است. البته لازم به ذکر است که حالت Shared ، حالت پیش فرض تولید وهله‌ها بوده و نیازی به ذکر صریح آن همانند ویژگی زیر نیست:
          [PartCreationPolicy(CreationPolicy.Shared)]

          مثال:
          فرض کنید قرار است از کلاس زیر تنها یک وهله بین صفحات یک برنامه‌ی Silverlight توزیع شود. با استفاده از ویژگی‌ Export به MEF اعلام کرده‌ایم که قرار است سرویسی را ارائه دهیم :

          using System;
          using System.ComponentModel.Composition;

          namespace SlMefTest
          {
          [Export]
          public class WebServiceData
          {
          public int Result { set; get; }

          public WebServiceData()
          {
          var rnd = new Random();
          Result = rnd.Next();
          }
          }

          }
          اکنون برای اثبات اینکه تنها یک وهله از این کلاس در اختیار صفحات مختلف قرار خواهد گرفت، یک User control جدید را به همراه یک دکمه که مقدار Result را نمایش می‌دهد به برنامه اضافه خواهیم کرد. دکمه‌ی دیگری را نیز به همین منظور به صفحه‌ی اصلی برنامه اضافه می‌کنیم.
          کدهای صفحه اصلی برنامه (که از یک دکمه و یک Stack panel جهت نمایش محتوای یوزر کنترل تشکیل شده) به شرح بعد هستند:
          <UserControl x:Class="SlMefTest.MainPage"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
          mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
          <StackPanel>
          <Button Content="MainPageButton" Height="23"
          HorizontalAlignment="Left"
          Margin="10,10,0,0" Name="button1"
          VerticalAlignment="Top" Width="98" Click="button1_Click" />
          <StackPanel Name="panel1" Margin="5"/>
          </StackPanel>
          </UserControl>

          using System.ComponentModel.Composition;
          using System.Windows;

          namespace SlMefTest
          {
          public partial class MainPage
          {
          [Import]
          public WebServiceData Data { set; get; }

          public MainPage()
          {
          InitializeComponent();
          this.Loaded += mainPageLoaded;
          }

          void mainPageLoaded(object sender, RoutedEventArgs e)
          {
          CompositionInitializer.SatisfyImports(this);
          panel1.Children.Add(new SilverlightControl1());
          }

          private void button1_Click(object sender, RoutedEventArgs e)
          {
          MessageBox.Show(Data.Result.ToString());
          }
          }
          }
          با استفاده از ویژگی Import به MEF اعلام می‌کنیم که به اطلاعاتی از نوع شیء WebServiceData نیاز داریم و توسط متد CompositionInitializer.SatisfyImports کار وهله سازی و پیوند زدن export و import های همانند صورت می‌گیرد. سپس استفاده‌ی مستقیم از Data.Result مجاز بوده و مقدار آن null نخواهد بود.

          کدهای User control ساده اضافه شده به شرح زیر هستند:

          <UserControl x:Class="SlMefTest.SilverlightControl1"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
          mc:Ignorable="d"
          d:DesignHeight="300" d:DesignWidth="400">

          <Grid x:Name="LayoutRoot" Background="White">
          <Button Content="UserControlButton"
          Height="23"
          HorizontalAlignment="Left"
          Margin="10,10,0,0"
          Name="button1"
          VerticalAlignment="Top"
          Width="125"
          Click="button1_Click" />
          </Grid>
          </UserControl>

          using System.ComponentModel.Composition;
          using System.Windows;

          namespace SlMefTest
          {
          public partial class SilverlightControl1
          {
          [Import]
          public WebServiceData Data { set; get; }

          public SilverlightControl1()
          {
          InitializeComponent();
          this.Loaded += silverlightControl1Loaded;
          }

          void silverlightControl1Loaded(object sender, RoutedEventArgs e)
          {
          CompositionInitializer.SatisfyImports(this);
          }

          private void button1_Click(object sender, RoutedEventArgs e)
          {
          MessageBox.Show(Data.Result.ToString());
          }
          }
          }
          اکنون قبل از شروع برنامه یک break point را در سازنده‌ی کلاس WebServiceData قرار دهید. سپس برنامه را آغاز نمائید. تنها یکبار این سازنده فراخوانی خواهد شد (هر چند در دو کلاس کار Import اطلاعات WebServiceData صورت گرفته است). همچنین با کلیک بر روی دو دکمه‌ای که اکنون در صفحه‌ی اصلی برنامه ظاهر می‌شوند، فقط یک عدد مشابه نمایش داده می‌شود (با توجه به اینکه اطلاعات هر دکمه در یک وهله‌ی جداگانه قرار دارد؛ یکی متعلق است به صفحه‌ی اصلی و دیگری متعلق است به user control اضافه شده).

          مطالب
          مدیریت رخدادهای MouseLeftButtonDown و MouseLeftButtonUp در Silverlight

          نیاز بود تا بتوان رخدادهای MouseLeftButtonDown و MouseLeftButtonUp یک TextBox را در Silverlight مدیریت کرد. شاید عنوان کنید که خیلی ساده است! دو روال رخداد گردان مربوطه را اضافه کنید و سپس تعاریف آن‌ها را در کدهای XAML خود قید نمائید. اما واقعیت این است که کار نمی‌کند! نه؛ کار نمی‌کند! :)

          مشکل از کجاست؟ پاسخی که در MSDN در این مورد آمده است به صورت زیر می‌باشد:

          "Certain control classes (for example Button) provide control-specific handling for mouse events such as MouseLeftButtonDown. The control-specific handling typically involves handling the event at a class level rather than at the instance level, and marking the MouseLeftButtonDown event data's Handled value as true such that the event cannot be handled by instances of the control class, nor by other objects anywhere further along the event route. In the case of Button, the class design does this so that the Click event can be raised instead."


          به عبارتی رخداد Click زحمت کشیده و رخدادهای MouseLeftButtonDown و MouseLeftButtonUp را نیز handled معرفی می‌کند و دیگر روال رخدادگردان شما فراخوانی نخواهد شد (در WPF هم به همین صورت است) و موارد ذکر شده در visual tree یک TextBox منتشر نمی‌گردند.

          راه حل چیست؟

          حداقل دو راه حل وجود دارد:
          الف) یک کنترل TextBox سفارشی را از کنترل TextBox اصلی باید به ارث برد و رخدادهایی را که توسط رخداد Click به صورت handled معرفی شده‌اند، unhandled کرد:

          public class MyTextBox : TextBox
          {
          protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
          {
          base.OnMouseLeftButtonDown(e);
          e.Handled = false;
          }

          protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
          {
          base.OnMouseLeftButtonUp(e);
          e.Handled = false;
          }
          }

          ب) یا امکان گوش فرا دادن به رخدادهای handled نیز میسر است. فقط کافی است این گوش فرادهنده را توسط متد AddHandler که پارامتر آخر آن به true تنظیم شده است (رخدادهای handled نیز لحاظ شوند)، معرفی کرد:

          public MainPage()
          {
          InitializeComponent();
          txt1.AddHandler(FrameworkElement.MouseLeftButtonDownEvent,
          new MouseButtonEventHandler(txt1_MouseLeftButtonDown), true);
          }
          private void txt1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
          {

          //do something
          }

          مطالب
          MVVM و امکان استفاده از یک وهله از ViewModel جهت چند View مرتبط

          عموما هنگام طراحی یک View، خیلی زود به حجم انبوهی از کدهای XAML خواهیم رسید. در ادامه بررسی خواهیم کرد که چطور می‌توان یک View را به چندین View خرد کرد، بدون اینکه نیازی باشد تا از چندین ViewModel (یا همان code behind عاری از ارجاعات بصری سابق قرار گرفته در یک پروژه جدای دیگر) استفاده شود و تمام این View های خرد شده هم تنها از یک وهله از ViewModel ایی خاص استفاده کنند و با اطلاعاتی یکپارچه سروکار داشته باشند؛ یا در عمل یکپارچه کار کنند.
          این مشکل از جایی شروع می‌شود که مثلا خرد کردن یک user control به چند یوزر کنترل، یعنی کار کردن با چند وهله از اشیایی متفاوت. هر چند نهایتا تمام این‌ها قرار است در یک صفحه در کنار هم قرار گیرند اما در عمل از هم کاملا مجزا هستند و اگر به ازای هر کدام یکبار ViewModel را وهله سازی کنیم، به مشکل برخواهیم خورد؛ چون هر وهله نسبت به وهله‌ای دیگر ایزوله است. اگر در یکی Name مثلا Test بود در دیگری ممکن است مقدار پیش فرض نال را داشته باشد؛ چون با چند وهله از یک کلاس، در یک فرم نهایی سروکار خواهیم داشت.

          ابتدا Model و ViewModel ساده زیر را در نظر بگیرید:
          using System.ComponentModel;

          namespace SplittingViewsInMvvm.Models
          {
          public class GuiModel : INotifyPropertyChanged
          {
          string _name;
          public string Name
          {
          get { return _name; }
          set
          {
          _name = value;
          raisePropertyChanged("Name");
          }
          }

          string _lastName;
          public string LastName
          {
          get { return _lastName; }
          set
          {
          _lastName = value;
          raisePropertyChanged("LastName");
          }
          }

          #region INotifyPropertyChanged Members
          public event PropertyChangedEventHandler PropertyChanged;
          void raisePropertyChanged(string propertyName)
          {
          var handler = PropertyChanged;
          if (handler == null) return;
          handler(this, new PropertyChangedEventArgs(propertyName));
          }
          #endregion
          }
          }

          using SplittingViewsInMvvm.Models;

          namespace SplittingViewsInMvvm.ViewModels
          {
          public class MainViewModel
          {
          public GuiModel GuiModelData { set; get; }

          public MainViewModel()
          {
          GuiModelData = new GuiModel();
          GuiModelData.Name = "Name";
          GuiModelData.LastName = "LastName";
          }
          }
          }

          سپس View زیر هم از این اطلاعات استفاده خواهد کرد:

          <UserControl x:Class="SplittingViewsInMvvm.Views.Main"
          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"
          mc:Ignorable="d"
          xmlns:VM="clr-namespace:SplittingViewsInMvvm.ViewModels"
          d:DesignHeight="300" d:DesignWidth="300">
          <UserControl.Resources>
          <VM:MainViewModel x:Key="vmMainViewModel" />
          </UserControl.Resources>
          <StackPanel DataContext="{Binding Source={StaticResource vmMainViewModel}}">
          <GroupBox Margin="2" Header="Group 1">
          <TextBlock Text="{Binding GuiModelData.Name}" />
          </GroupBox>
          <GroupBox Margin="2" Header="Group 2">
          <TextBlock Text="{Binding GuiModelData.LastName}" />
          </GroupBox>
          </StackPanel>
          </UserControl>

          اکنون فرض کنید که می‌خواهیم Group 1 و Group 2 را جهت مدیریت ساده‌تر View اصلی در دو user control مجزا قرار دهیم؛ مثلا:

          <UserControl x:Class="SplittingViewsInMvvm.Views.Group1"
          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"
          mc:Ignorable="d"
          d:DesignHeight="300" d:DesignWidth="300">
          <Grid>
          <GroupBox Margin="2" Header="Group 1">
          <TextBlock Text="{Binding GuiModelData.Name}" />
          </GroupBox>
          </Grid>
          </UserControl>
          و
          <UserControl x:Class="SplittingViewsInMvvm.Views.Group2"
          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"
          mc:Ignorable="d"
          d:DesignHeight="300" d:DesignWidth="300">
          <Grid>
          <GroupBox Margin="2" Header="Group 2">
          <TextBlock Text="{Binding GuiModelData.LastName}" />
          </GroupBox>
          </Grid>
          </UserControl>

          اکنون اگر این دو را مجددا در همان View اصلی ساده شده قبلی قرار دهیم (بدون اینکه در هر user control به صورت جداگانه data context را تنظیم کنیم):
          <UserControl x:Class="SplittingViewsInMvvm.Views.Main"
          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"
          mc:Ignorable="d"
          xmlns:V="clr-namespace:SplittingViewsInMvvm.Views"
          xmlns:VM="clr-namespace:SplittingViewsInMvvm.ViewModels"
          d:DesignHeight="300" d:DesignWidth="300">
          <UserControl.Resources>
          <VM:MainViewModel x:Key="vmMainViewModel" />
          </UserControl.Resources>
          <StackPanel DataContext="{Binding Source={StaticResource vmMainViewModel}}">
          <V:Group1 />
          <V:Group2 />
          </StackPanel>
          </UserControl>

          باز هم .... برنامه همانند سابق کار خواهد کرد و ViewModel وهله سازی شده در user control فوق به صورت یکسانی در اختیار هر دو View اضافه شده قرار می‌گیرد و نهایتا یک View یکپارچه را در زمان اجرا می‌توان مورد استفاده قرار داد. علت هم بر می‌گردد به مقدار دهی خودکار DataContext هر View اضافه شده به بالاترین DataContext موجود در Visual tree که ذکر آن الزامی نیست:

          <UserControl x:Class="SplittingViewsInMvvm.Views.Main"
          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"
          mc:Ignorable="d"
          xmlns:V="clr-namespace:SplittingViewsInMvvm.Views"
          xmlns:VM="clr-namespace:SplittingViewsInMvvm.ViewModels"
          d:DesignHeight="300" d:DesignWidth="300">
          <UserControl.Resources>
          <VM:MainViewModel x:Key="vmMainViewModel" />
          </UserControl.Resources>
          <StackPanel DataContext="{Binding Source={StaticResource vmMainViewModel}}">
          <V:Group1 DataContext="{Binding}" />
          <V:Group2 DataContext="{Binding}"/>
          </StackPanel>
          </UserControl>


          بنابراین به صورت خلاصه زمانیکه از MVVM استفاده ‌می‌کنید لازم نیست کار خاصی را جهت خرد کردن یک View به چند Sub View انجام دهید! فقط این‌ها را در چند User control جدا کنید و بعد مجددا به کمک فضای نامی که تعریف خواهد (مثلا V در اینجا) در همان View اصلی تعریف کنید. بدون هیچ تغییر خاصی باز هم برنامه همانند سابق کار خواهد کرد.


          مطالب
          بارگذاری یک یوزرکنترل با استفاده از جی‌کوئری

          مزیت استفاده از یوزر کنترل‌ها، ماژولار کردن برنامه است. برای مثال اگر صفحه جاری شما قرار است از چهار قسمت اخبار، منوی پویا ، سخن روز و آمار کاربران تشکیل شود، می‌توان هر کدام را توسط یک یوزر کنترل پیاده سازی کرده و سپس صفحه اصلی را از کنار هم قرار دادن این یوزر کنترل‌ها تهیه نمود.
          با این توضیحات اکنون می‌خواهیم یک یوزکنترل ASP.Net را توسط jQuery Ajax بارگذاری کرده و نمایش دهیم. حداقل دو مورد کاربرد را می‌توان برای آن متصور شد:
          الف) در اولین باری که یک صفحه در حال بارگذاری است، قسمت‌های مختلف آن‌را بتوان از یوزر کنترل‌های مختلف خواند و تا زمان بارگذاری کامل هر کدام، یک عبارت لطفا منتظر بمانید را نمایش داد. نمونه‌ی آن‌را شاید در بعضی از CMS های جدید دیده باشید. صفحه به سرعت بارگذاری می‌شود. در حالیکه مشغول مرور صفحه جاری هستید، قسمت‌های مختلف صفحه پدیدار می‌شوند.
          ب) بارگذاری یک قسمت دلخواه صفحه بر اساس درخواست کاربر. مثلا کلیک بر روی یک دکمه و امثال آن.

          روش کلی کار:
          1) تهیه یک متد وب سرویس که یوزر کنترل را بر روی سرور اجرا کرده و حاصل را تبدیل به یک رشته کند.
          2) استفاده از متد Ajax جی‌کوئری برای فراخوانی این متد وب سرویس و افزودن رشته دریافت شده به صفحه.
          بدیهی است زمانیکه متد Ajax فراخوانی می‌شود می‌توان عبارت یا تصویر منتظر بمانید را نمایش داد و پس از پایان کار این متد، عبارت (یا تصویر) را مخفی نمود.

          پیاده سازی:
          قسمت تبدیل یک یوزر کنترل به رشته را قبلا در مقاله "تهیه قالب برای ایمیل‌های ارسالی یک برنامه ASP.Net" مشاهده کرده‌اید. در این‌جا برای استفاده از این متد در یک وب سرویس نیاز به کمی تغییر وجود داشت (KeyValuePair ها درست سریالایز نمی‌شوند) که نتیجه نهایی به صورت زیر است. یک فایل Ajax.asmx را به برنامه اضافه کرده و سپس در صفحه Ajax.asmx.cs کد آن به صورت زیر می‌تواند باشد:

          using System;
          using System.Collections.Generic;
          using System.IO;
          using System.Reflection;
          using System.Text.RegularExpressions;
          using System.Web;
          using System.Web.Script.Services;
          using System.Web.Services;
          using System.Web.UI;
          using System.Web.UI.HtmlControls;

          namespace AjaxTest
          {
          public class KeyVal
          {
          public string Key { set; get; }
          public object Value { set; get; }
          }

          /// <summary>
          /// Summary description for Ajax
          /// </summary>
          [ScriptService]
          [WebService(Namespace = "http://tempuri.org/")]
          [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
          [System.ComponentModel.ToolboxItem(false)]
          public class Ajax : WebService
          {
          /// <summary>
          /// Removes Form tags using Regular Expression
          /// </summary>
          private static string cleanHtml(string html)
          {
          return Regex.Replace(html, @"<[/]?(form)[^>]*?>", string.Empty, RegexOptions.IgnoreCase);
          }

          /// <summary>
          /// تبدیل یک یوزر کنترل به معادل اچ تی ام ال آن
          /// </summary>
          /// <param name="path">مسیر یوزر کنترل</param>
          /// <param name="properties">لیست خواص به همراه مقادیر مورد نظر</param>
          /// <returns></returns>
          /// <exception cref="NotImplementedException"><c>NotImplementedException</c>.</exception>
          [WebMethod(EnableSession = true)]
          [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
          public string RenderUserControl(string path,
          List<KeyVal> properties)
          {
          Page pageHolder = new Page();

          UserControl viewControl =
          (UserControl)pageHolder.LoadControl(path);

          viewControl.EnableViewState = false;

          Type viewControlType = viewControl.GetType();

          if (properties != null)
          foreach (var pair in properties)
          {
          if (pair.Key != null)
          {
          PropertyInfo property =
          viewControlType.GetProperty(pair.Key);

          if (property != null)
          {
          if (pair.Value != null) property.SetValue(viewControl, pair.Value, null);
          }
          else
          {
          throw new NotImplementedException(string.Format(
          "UserControl: {0} does not have a public {1} property.",
          path, pair.Key));
          }
          }
          }

          //Form control is mandatory on page control to process User Controls
          HtmlForm form = new HtmlForm();

          //Add user control to the form
          form.Controls.Add(viewControl);

          //Add form to the page
          pageHolder.Controls.Add(form);

          //Write the control Html to text writer
          StringWriter textWriter = new StringWriter();

          //execute page on server
          HttpContext.Current.Server.Execute(pageHolder, textWriter, false);

          // Clean up code and return html
          return cleanHtml(textWriter.ToString());
          }
          }
          }
          تا این‌جا متد وب سرویسی را داریم که می‌تواند مسیر یک یوزر کنترل را به همراه خواص عمومی آن‌را دریافت کرده و سپس یوزر کنترل را رندر نموده و حاصل را به صورت HTML به شما تحویل دهد. با استفاده از reflection خواص عمومی یوزر کنترل یافت شده و مقادیر لازم به آن‌ها پاس می‌شوند.

          چند نکته:
          الف) وب کانفیگ برنامه ASP.Net شما اگر با VS 2008 ایجاد شده باشد مداخل لازم را برای استفاده از این وب سرویس توسط jQuery Ajax دارد در غیر اینصورت موفق به استفاده از آن نخواهید شد.
          ب) هنگام بازگرداندن این اطلاعات با فرمت json = ResponseFormat.Json جهت استفاده در jQuery Ajax ، گاهی از اوقات بسته به حجم بازگردانده شده ممکن است خطایی حاصل شده و عملیات متوقف شد. این طول پیش فرض را (maxJsonLength) در وب کانفیگ به صورت زیر تنظیم کنید تا مشکل حل شود:

          <system.web.extensions>
          <scripting>
          <webServices>
          <jsonSerialization maxJsonLength="10000000"></jsonSerialization>
          </webServices>
          </scripting>
          </system.web.extensions>

          برای پیاده سازی قسمت Ajax آن برای اینکه کار کمی تمیزتر و با قابلیت استفاده مجدد شود یک پلاگین تهیه شده (فایلی با نام jquery.advloaduc.js) که سورس آن به صورت زیر است:

          $.fn.advloaduc = function(options) {
          var defaults = {
          webServiceName: 'Ajax.asmx', //نام فایل وب سرویس ما
          renderUCMethod: 'RenderUserControl', //متد وب سرویس
          ucMethodJsonParams: '{path:\'\'}',//پارامترهایی که قرار است پاس شوند
          completeHandler: null //پس از پایان کار وب سرویس این متد جاوا اسکریپتی فراخوانی می‌شود
          };
          var options = $.extend(defaults, options);

          return this.each(function() {
          var obj = $(this);
          obj.prepend("<div align='center'> لطفا اندکی تامل بفرمائید... <img src=\"images/loading.gif\"/></div>");

          $.ajax({
          type: "POST",
          url: options.webServiceName + "/" + options.renderUCMethod,
          data: options.ucMethodJsonParams,
          contentType: "application/json; charset=utf-8",
          dataType: "json",
          success:
          function(msg) {
          obj.html(msg.d);

          // if specified make callback and pass element
          if (options.completeHandler)
          options.completeHandler(this);
          },
          error:
          function(XMLHttpRequest, textStatus, errorThrown) {
          obj.html("امکان اتصال به سرور در این لحظه مقدور نیست. لطفا مجددا سعی کنید.");
          }
          });
          });
          };
          برای اینکه با کلیات این روش آشنا شوید می‌توان به مقاله "بررسی وجود نام کاربر با استفاده از jQuery Ajax در ASP.Net" مراجعه نمود که از ذکر مجدد آن‌ها خودداری می‌شود. همچنین در مورد نوشتن یک پلاگین جی‌کوئری در مقاله "افزونه جملات قصار jQuery" توضیحاتی داده شده است.
          عمده کاری که در این پلاگین صورت می‌گیرد فراخوانی متد Ajax جی‌کوئری است. سپس به متد وب سرویس ما (که در اینجا نام آن به صورت پارامتر نیز قابل دریافت است)، پارامترهای لازم پاس شده و سپس نتیجه حاصل به یک شیء در صفحه اضافه می‌شود.
          completeHandler آن اختیاری است و پس از پایان کار متد اجکس فراخوانی می‌شود. در صورتیکه به آن نیازی نداشتید یا مقدار آن را null قرار دهید یا اصلا آن‌را ذکر نکنید.

          مثالی در مورد استفاده از این وب سرویس و همچنین پلاگین جی‌کوئری نوشته شده:

          الف) یوزر کنترل ساده زیر را به پروژه اضافه کنید:

          <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="part1.ascx.cs" Inherits="TestJQueryAjax.part1" %>
          <asp:Label runat="server" ID="lblData" ></asp:Label>
          بدیهی است یک یوزر کنترل می‌تواند به اندازه یک صفحه کامل پیچیده باشد به همراه انواع و اقسام ارتباطات با دیتابیس و غیره.

          سپس کد آن‌را به صورت زیر تغییر دهید:

          using System;
          using System.Threading;

          namespace TestJQueryAjax
          {
          public partial class part1 : System.Web.UI.UserControl
          {
          public string Text1 { set; get; }
          public string Text2 { set; get; }

          protected void Page_Load(object sender, EventArgs e)
          {
          Thread.Sleep(3000);
          if (!string.IsNullOrEmpty(Text1) && !string.IsNullOrEmpty(Text2))
          lblData.Text = Text1 + "<br/>" + Text2;
          }
          }
          }
          این یوزر کنترل دو خاصیت عمومی دارد که توسط وب سرویس مقدار دهی خواهد شد و نهایتا حاصل نهایی را در یک لیبل در دو سطر نمایش می‌دهد.
          عمدا یک sleep سه ثانیه‌ای در اینجا در نظر گرفته شده تا اثر آن‌را بهتر بتوان مشاهده کرد.

          ب) اکنون کد مربوط به صفحه‌ای که قرار است این یوزر کنترل را به صورت غیرهمزمان بارگذاری کند به صورت زیر خواهد بود (مهم‌ترین قسمت آن نحوه تشکیل پارامترها و مقدار دهی خواص یوزر کنترل است):

          <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="TestJQueryAjax._Default" %>

          <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
          <html xmlns="http://www.w3.org/1999/xhtml">
          <head runat="server">
          <title></title>

          <script src="js/jquery.js" type="text/javascript"></script>
          <script src="js/jquery.advloaduc.js" type="text/javascript"></script>
          <script src="js/json2.js" type="text/javascript"></script>

          <script type="text/javascript">
          function showAlert() {
          alert('finished!');
          }

          //تشکیل پارامترهای متد وب سرویس جهت ارسال به آن
          var fileName = 'part1.ascx';
          var props = [{ 'Key': 'Text1', 'Value': 'سطر یک' }, { 'Key': 'Text2', 'Value': 'سطر 2'}];
          var jsonText = JSON.stringify({ path: fileName, properties: props });

          $(document).ready(function() {
          $("#loadMyUc").advloaduc({
          webServiceName: 'Ajax.asmx',
          renderUCMethod: 'RenderUserControl',
          ucMethodJsonParams: jsonText,
          completeHandler: showAlert
          });
          });

          </script>

          </head>
          <body>
          <form id="form1" runat="server">
          <div id="loadMyUc">
          </div>
          </form>
          </body>

          </html>
          نکته:
          برای ارسال صحیح و امن اطلاعات json به سرور، از اسکریپت استاندارد json2.js استفاده شد.

          ابزار دیباگ:
          بهترین ابزار برای دیباگ این نوع اسکریپت‌ها استفاده از افزونه فایرباگ فایرفاکس است. برای مثال مطابق تصویر زیر، یوزر کنترلی فراخوانی شده است که در سرور وجود ندارد:


          دریافت مثال فوق


          مطالب
          مشکل چشمک زدن (Flicker) در کنترل ListView

          با هر بار اضافه کردن یک سطر به ListView ، تمام ناحیه پس زمینه کنترل به روز شده و مشکل چشمک زدن (Flicker) آزار دهنده‌ای را پدید می‌آورد. راه حل‌های زیادی برای رفع این مشکل وجود دارد. برای مثال استفاده از متدهای BeginUpdate و EndUpdate قبل و پس از افزودن تعداد زیادی رکورد به یک ListView . اما اگر این کنترل توسط چند ترد در حال به روز رسانی باشد و هربار هم تعداد آیتم‌های اضافه شده آنچنان زیاد نباشد، این روش اثری نداشته و باز هم مشکل flickering وجود خواهد داشت.
          رفع این مشکل راه حل بسیار ساده‌ای دارد که به شرح زیر است:

          یک user control جدید ایجاد کنید، آن‌را از ListView به ارث برده و سپس سطر زیر را به constructor آن اضافه کنید:

          this.DoubleBuffered = true;

          اکنون از این ListView سفارشی بجایlistView استاندارد استفاده کنید، مشکل برطرف می‌شود!

          public partial class CustomListView : ListView
          {
          public CustomListView()
          {
          this.DoubleBuffered = true;
          }
          }
          شبیه به همین مورد را جهت کنترل ListBox نیز می‌توان پیاده سازی کرد

          مطالب
          آموزش ایجاد برنامه های چند زبانه در WPF
          با گسترش استفاده از کامپیوتر در بسیاری از امور روزمره انسان‌ها سازگار بودن برنامه‌ها با سلیقه کاربران به یکی از نیاز‌های اصلی برنامه‌های کامپیوتری تبدیل شده است. بدون شک زبان و فرهنگ یکی از مهم‌ترین عوامل در ایجاد ارتباط نزدیک بین برنامه و کاربر به شمار می‌رود و نقشی غیر قابل انکار در میزان موفقیت یک برنامه به عهده دارد. از این رو در این نوشته تلاش بر آن است تا یکی از ساده‌ترین و در عین حال کارا‌ترین راه‌های ممکن برای ایجاد برنامه‌های چند زبانه با استفاده از تکنولوژی WPF آموزش داده شود.

          مروری بر روش‌های موجود
          همواره روش‌های مختلفی برای پیاده سازی یک ایده در دنیای نرم افزار وجود دارد که هر روش را می‌توان بر حسب نیاز مورد استفاده قرار داد. در برنامه‌های مبتنی بر WPF معمولا از دو روش عمده برای این منظور استفاده می‌شود:

          1-استفاده از فایل‌های resx
          در این روش که برای Win App نیز استفاده می‌شود، اطلاعات مورد نیاز برای هر زبان به شکل جدول هایی دارای کلید و مقدار در داخل یک فایل .resx نگهداری می‌شود و در زمان اجرای برنامه بر اساس انتخاب کاربر اطلاعات زبان مورد نظر از داخل فایل  resx خوانده شده و نمایش داده می‌شود. یکی از ضعف هایی که این روش در عین ساده بودن دارد این است که همه اطلاعات مورد نیاز داخل assembly اصلی برنامه قرار می‌گیرد و امکان افزودن زبان‌های جدید بدون تغییر دادن برنامه اصلی ممکن نخواهد بود.

          2-استفاده از فایل‌های csv که به فایل‌های dll تبدیل می‌شوند
          در این روش با استفاده از ابزار‌های موجود در کامپایلر WPF برای هر کنترل یک property به نام Uid ایجاد شده و مقدار دهی می‌شود. سپس با ابزار دیگری ( که جزو ابزار‌های کامپایلر محسوب نمی‌شود ) از فایل csproj پروژه یک خروجی اکسل با فرمت csv ایجاد می‌شود که شامل Uid‌های کنترل‌ها و مقادیر آن‌ها است. پس از ترجمه متون مورد نظر به زبان مقصد با کمک ابزار دیگری فایل اکسل مورد نظر به یک net assembly تبدیل می‌شود و داخل پوشه ای با نام culture استاندارد ذخیره می‌شود. ( مثلا برای زبان فارسی نام پوشه fa-IR خواهد بود ). زمانی که برنامه اجرا می‌شود بر اساس culture ای که در سیستم عامل انتخاب شده است و در صورتی که برای آن culture فایل dll ای موجود باشد، زبان مربوط به آن culture را load خواهد کرد. با وجود این که این روش مشکل روش قبلی را ندارد و بیشتر با ویژگی‌های WPF سازگار است اما پروسه ای طولانی برای انجام کار‌ها دارد و به ازای هر تغییری باید کل مراحل هر بار تکرار شوند. همچنین مشکلاتی در نمایش برخی زبان‌ها ( از جمله فارسی ) در این روش مشاهده شده است.

          روش سوم!
          روش سوم اما کاملا بر پایه WPF و در اصطلاح WPF-Native می‌باشد. ایده از آنجا ناشی شده است که برای ایجاد skin در برنامه‌های WPF استفاده می‌شود. در ایجاد برنامه‌های Skin-Based به این شیوه عمل می‌شود که skin‌های مورد نظر به صورت style هایی در داخل ResourceDictionary ‌ها قرار می‌گیرند. سپس آن ResourceDictionary به شکل dll کامپایل می‌شود. در برنامه اصلی نیز همه کنترل‌ها style هایشان را به شکل dynamic resource از داخل یک ResourceDictionary مشخص شده load می‌کنند. حال کافی است برای تغییر skin فعلی، ResourceDictionary  مورد نظر از dll مشخص load شود و ResourceDictionary ای که در حال حاضر در برنامه از آن استفاده می‌شود با ResourceDictionary ای که load شده جایگزین شود. کنترل‌ها مقادیر جدید را از ResourceDictionary جدید به شکل کاملا خودکار دریافت خواهند کرد.
          به سادگی می‌توان از این روش برای تغییر زبان برنامه نیز استفاده کرد با این تفاوت که این بار، به جای Style ها، String‌های زبان‌های مختلف را درون resource‌ها نگهداری خواهیم کرد.

          یک مثال ساده
          در این قسمت نحوه پیاده سازی این روش با ایجاد یک نمونه برنامه ساده که دارای دو زبان انگلیسی و فارسی خواهد بود آموزش داده می‌شود.
          ابتدا یک پروژه WPF Application در Visual Studio 2010 ایجاد کنید. در MainWindow سه کنترل Button قرار دهید و یک ComboBox که قرار است زبان‌های موجود را نمایش دهد و با انتخاب یک زبان، نوشته‌های درون Button‌ها متناسب با آن تغییر خواهند کرد.

          توجه داشته باشید که برای Button‌ها نباید به صورت مستقیم مقداری به Content شان داده شود. زیرا مقدار مورد نظر از داخل ResourceDictionary که خواهیم ساخت به شکل dynamic گرفته خواهد شد. پس در این مرحله یک ResourceDictionary به پروژه اضافه کرده و در آن resource هایی به شکل string ایجاد می‌کنیم. هر resource دارای یک Key می‌باشد که بر اساس آن، Button مورد نظر، مقدار آن Resource را load خواهد کرد. فایل ResourceDictionary را
          Culture_en-US.xaml نامگذاری کنید و مقادیر مورد نظر را به آن اضافه نمایید.  

          <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                              xmlns:system="clr-namespace:System;assembly=mscorlib">
              <system:String x:Key="button1">Hello!</system:String>
              <system:String x:Key="button2">How Are You?</system:String>
              <system:String x:Key="button3">Are You OK?</system:String>
           
          </ResourceDictionary>

          دقت کنید که namespace ای که کلاس string در آن قرار دارد به فایل xaml اضافه شده است و پیشوند system به آن نسبت داده شده است.

          با افزودن یک ResourceDictionary به پروژه، آن ResourceDictionary به MergedDictionary کلاس App اضافه می‌شود. بنابراین فایل App.xaml به شکل زیر خواهد بود:

          <Application x:Class="BeRMOoDA.WPF.LocalizationSample.App"
                       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                       StartupUri="MainWindow.xaml">
              <Application.Resources>
           
                  <ResourceDictionary>
                      <ResourceDictionary.MergedDictionaries>
                          <ResourceDictionary Source="Culture_en-US.xaml"/>
                      </ResourceDictionary.MergedDictionaries>
                  </ResourceDictionary>
           
              </Application.Resources>
          </Application>

          برای اینکه بتوانیم محتوای Button‌های موجود را به صورت داینامیک و در زمان اجرای برنامه، از داخل Resource‌ها بگیریم، از DynamicResource استفاده می‌کنیم.

          <Button Content="{DynamicResource ResourceKey=button1}" />
          <Button Content="{DynamicResource ResourceKey=button2}" />
          <Button Content="{DynamicResource ResourceKey=button3}" />

          بسیار خوب! اکنون باید شروع به ایجاد یک ResourceDictionary برای زبان فارسی کنیم و آن را به صورت یک فایل dll کامپایل نماییم.
          برای این کار یک پروژه جدید در قسمت WPF از نوع User control ایجاد می‌کنیم و نام آن را Culture_fa-IR_Farsi قرار می‌دهیم. لطفا شیوه نامگذاری را رعایت کنید چرا که در ادامه به آن نیاز خواهیم داشت.
          پس از ایجاد پروژه فایل UserControl1.xaml را از پروژه حذف کنید و یک ResourceDictionary با نام Culture_fa-IR.xaml اضافه کنید. محتوای آن را پاک کنید و محتوای فایل Culture_en-US.xaml را از پروژه قبلی به صورت کامل در فایل جدید کپی کنید. دو فایل باید ساختار کاملا یکسانی از نظر key برای Resource‌های موجود داشته باشند. حالا زمان ترجمه فرا رسیده است! رشته‌های دلخواه را ترجمه کنید و پروژه را build نمایید. 
          پس از ترجمه فایل Culture_fa-IR.xaml به شکل زیر خواهد بود:

          <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                              xmlns:system="clr-namespace:System;assembly=mscorlib">
              <ResourceDictionary.MergedDictionaries>
                  <ResourceDictionary Source="Culture_fa-IR_Farsi.xaml"/>
              </ResourceDictionary.MergedDictionaries>
              <system:String x:Key="button1">سلام!</system:String>
              <system:String x:Key="button2">حالت چطوره؟</system:String>
              <system:String x:Key="button3">خوبی؟</system:String>
          </ResourceDictionary>
          خروجی این پروژه یک فایل با نام Culture_fa-IR_Farsi.dll خواهد بود که حاوی یک ResourceDictionary برای زبان فارسی می‌باشد.

          در ادامه میخواهیم راهکاری ارئه دهیم تا بتوان فایل‌های dll مربوط به زبان‌ها را در زمان اجرای برنامه اصلی، load کرده و نام زبان‌ها را در داخل ComboBox ای که داریم نشان دهیم. سپس با انتخاب هر زبان در ComboBox، محتوای Button‌ها بر اساس زبان انتخاب شده تغییر کند.
          برای سهولت کار، نام فایل‌ها را به گونه ای انتخاب کردیم که بتوانیم ساده‌تر به این هدف برسیم. نام هر فایل از سه بخش تشکیل شده است:
          Culture_[standard culture notation]_[display name for this culture].dll
          یعنی اگر فایل Culture_fa-IR_Farsi.dll را در نظر بگیریم، Culture نشان دهنده این است که این فایل مربوط به یک culture می‌باشد. fa-IR نمایش استاندارد culture برای کشور ایران و زبان فارسی است و Farsi هم مقداری است که می‌خواهیم در ComboBox برای این زبان نمایش داده شود.
          پوشه ای با نام Languages در کنار فایل اجرایی برنامه اصلی ایجاد کنید و فایل Culture_fa-IR_Farsi.dll را درون آن کپی کنید. تصمیم داریم همه dll‌های مربوط به زبان‌ها را داخل این پوشه قرار دهیم تا مدیریت آن‌ها ساده‌تر شود. 
          برای مدیریت بهتر فایل‌های مربوط به زبان‌ها یک کلاس با نام CultureAssemblyModel خواهیم ساخت که هر instance از آن نشانگر یک فایل زبان خواهد بود. یک کلاس با این نام به پروژه اضافه کنید و property‌های زیر را در آن تعریف نمایید:

          public class CultureAssemblyModel
              {
                  //the text will be displayed to user as language name (like Farsi)
                  public string DisplayText { get; set; }
                  //name of .dll file (like Culture_fa-IR_Farsi.dll)
                  public string Name { get; set; }
                  //standar notation of this culture (like fa-IR)
                  public string Culture { get; set; }
                  //name of resource dictionary file inside the loaded .dll (like Culture_fa-IR.xaml)
                  public string XamlFileName { get; set; }
              }
          اکنون باید لیست culture‌های موجود را از داخل پوشه languages خوانده و نام آنها را در ComboBox نمایش دهیم.
          برای خواندن لیست culture‌های موجود، لیستی از CultureAssmeblyModel‌ها ایجاد کرده و با استفاده از متد LoadCultureAssmeblies، آن را پر می‌کنیم.

          //will keep information about loaded assemblies
          public List<CultureAssemblyModel> CultureAssemblies { get; set; }
           
          //loads assmeblies in languages folder and adds their info to list
           void LoadCultureAssemblies()
           {
                //we should be sure that list is empty before adding info (do u want to add some cultures more than one? of course u dont!)
                CultureAssemblies.Clear();
                //creating a directory represents applications directory\languages
                DirectoryInfo dir = new DirectoryInfo(System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\languages");
                //getting all .dll files in the language folder and its sub dirs. (who knows? maybe someone keeps each culture file in a seperate folder!)
                var assemblies = dir.GetFiles("*.dll", SearchOption.AllDirectories);
                //for each found .dll we will create a model and set its properties and then add to list  for (int i = 0; i < assemblies.Count(); i++)
                {
          string name = assemblies[i].Name;
            CultureAssemblyModel model = new CultureAssemblyModel() { DisplayText = name.Split('.', '_')[2], Culture = name.Split('.', '_')[1], Name = name  , XamlFileName =name.Substring(0, name.LastIndexOf(".")) + ".xaml" }; CultureAssemblies.Add(model); } }
          پس از دریافت اطلاعات culture‌های موجود، زمان نمایش آن‌ها در ComboBox است. این کار بسیار ساده است، تنها کافی است ItemsSource آن را با لیستی از CultureAssmeblyModel‌ها که ساختیم، مقدار دهی کنیم.

          comboboxLanguages.ItemsSource = CultureAssemblies;
          البته لازم به ذکر است که برای نمایش فقط نام هر CultureAssemblyModel در ComboBox، باید ItemTemplate مناسبی برای ComboBox ایجاد کنیم. در مثال ما ItemTemplate به شکل زیر خواهد بود:

          <ComboBox HorizontalAlignment="Left" Margin="10" VerticalAlignment="Top" MinWidth="100" Name="comboboxLanguages">
                      <ComboBox.ItemTemplate>
                          <DataTemplate>
                              <Label Content="{Binding DisplayText}"/>
                          </DataTemplate>
                      </ComboBox.ItemTemplate>
          </ComboBox>
          توجه داشته باشید که با وجود اینکه فقط نام را در ComboBox نشان می‌دهیم، اما باز هم هر آیتم از ComboBox یک instance از نوع CultureAssemblyModel می‌باشد.

          در مرحله بعد، قرار است متدی بنویسیم که اطلاعات زبان انتخاب شده را گرفته و با جابجایی ResourceDictionary ها، زبان برنامه را تغییر دهیم.
          متدی با نام LoadCulture در کلاس App ایجاد می‌کنیم که یک CultureAssemblyModel به عنوان ورودی دریافت کرده و ResourceDictionary داخل آن را load می‌کند و آن را با ResourceDictionary فعلی موجود در App.xaml جابجا می‌نماید.
          با این کار، Button هایی که قبلا مقدار Content خود را از Resource‌های موجود دریافت می‌کردند، اکنون از Resource‌های جابجا شده خواهند گرفت و به این ترتیب زبان انتخاب شده بر روی برنامه اعمال می‌شود.

          //loads selected culture
           public void LoadCulture(CultureAssemblyModel culture)
           {
               //creating a FileInfo object represents .dll file of selected cultur
               FileInfo assemblyFile = new FileInfo("languages\\" + culture.Name);
               //loading .dll into memory as a .net assembly
               var assembly = Assembly.LoadFile(assemblyFile.FullName);
               //getting .dll file name
               var assemblyName = assemblyFile.Name.Substring(0, assemblyFile.Name.LastIndexOf("."));
               //creating string represents structure of a pack uri (something like this: /{myassemblyname;component/myresourcefile.xaml}
               string packUri = string.Format(@"/{0};component/{1}", assemblyName, culture.XamlFileName);
               //creating a pack uri
               Uri uri = new Uri(packUri, UriKind.Relative);
               //now we have created a pack uri that represents a resource object in loaded assembly
               //and its time to load that as a resource dictionary (do u remember that we had resource dictionary in culture assemblies? don't u?)
               var dic = Application.LoadComponent(uri) as ResourceDictionary;
               dic.Source = uri;
               //here we will remove current merged dictionaries in our resource dictionary and add recently-loaded resource dictionary as e merged dictionary
               var mergedDics = this.Resources.MergedDictionaries;
               if (mergedDics.Count > 0)
                    mergedDics.Clear();
               mergedDics.Add(dic);
           }
          برای ارسال زبان انتخاب شده به این متد، باید رویداد SelectionChanged را برای ComboBox مدیریت کنیم:

          void comboboxLanguages_SelectionChanged(object sender, SelectionChangedEventArgs e)
           {
               var selectedCulture = (CultureAssemblyModel)comboboxLanguages.SelectedItem;
               App app = Application.Current as App;
               app.LoadCulture(selectedCulture);
           }

          کار انجام شد!
          از مزیت‌های این روش می‌توان به WPF-Native بودن، سادگی در پیاده سازی، قابلیت load کردن هر زبان جدیدی در زمان اجرا بدون نیاز به کوچک‌ترین تغییر در برنامه اصلی و همچنین پشتیبانی کامل از نمایش زبان‌های مختلف از جمله فارسی اشاره کرد.