‏نمایش پست‌ها با برچسب WPF. نمایش همه پست‌ها
‏نمایش پست‌ها با برچسب WPF. نمایش همه پست‌ها

۱۳۹۰/۱۱/۲۶

تعيين Fallback font براي قلم‌هاي فارسي در WPF


اكثر قلم‌هاي فارسي، فاقد تعاريف مرتبط با حروف انگليسي هستند. البته عموم كاربران متوجه اين امر نمي‌شوند چون ويندوز دو مفهوم Font Fallback و Font Linking را جهت پوشش glyph هاي تعريف نشده، در پشت صحنه اعمال خواهد كرد. جزئيات بيشتر در اينجا: (^ و ^)

به صورت خلاصه كار Font Fallback در ويندوز جايگزيني خودكار قلم مورد استفاده است؛ تحت شرايط زير:
- فونت تعريف شده در برنامه، در سيستم كاربر وجود نداشته باشد.
- تعاريف Glyphهاي بكارگرفته شده در متن جاري، در قلم انتخابي وجود نداشته باشند.

در WPF اين مساله كاملا قابل كنترل است. قلمي كه به صورت خودكار به عنوان جايگزين مطرح مي‌شود در قلمي به نام "Global User Interface" تعريف شده است. تعاريف اين قلم تركيبي هم در فايلي به نام GlobalUserInterface.CompositeFont در پوشه فونت‌هاي سيستم موجود است (براي مثال، مسير c:\windows\fonts حاوي اين فايل متني است).
اگر اين فايل XML را با يك اديتور متني باز كنيد، مشاهده خواهيد كرد كه بازه‌هاي مختلف كاراكترهاي يونيكد، به فونت‌هاي پيش فرضي نگاشت شده‌اند. بنابراين اگر اين سؤال وجود دارد كه در متن مخلوط فارسي و انگليسي من، فونت پيش فرض حروف انگليسي از كجا تامين و مشخص مي‌شود، پاسخ را در اين فايل مي‌توانيد مشاهده كنيد.

روش ديگري هم براي تعيين Fallback font در WPF وجود دارد. يك مثال:

<Window x:Class="WpfFontTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBlock 
            Text="نمايش مخلوطي از متن فارسي و متن English با هم"
            Margin="7"
            FontFamily="Fonts/BNazanin.ttf#B Nazanin, Comic Sans Ms"
            FontSize="25"
            FlowDirection="RightToLeft"
            VerticalAlignment="Top" HorizontalAlignment="Center"  />
    </Grid>
</Window>

در اين مثال فونت B Nazanin در برنامه قرار داده شده است (embedded font). همچنين در كنار آن پس از علامت كاما، Fallback font مشخص است. به اين معنا كه تاجايي كه ميسر است لطفا از فونت B Nazanin براي نمايش متن مورد نظر استفاده شود؛ اگر نشد از قلم Comic Sans Ms استفاده گردد. قلم B Nazanin حاوي تعاريف حروف انگليسي نيست. بنابراين WPF جهت نمايش آن‌ها از فونت دوم معرفي شده كمك مي‌گيرد. توضيحات بيشتر در اينجا: (^)

۱۳۹۰/۱۰/۰۴

MVVM و الگوي ViewModel Locator


اگر ViewModel را همان فايل code behind عاري از ارجاعاتي به اشياء بصري بدانيم، يك تفاوت مهم را علاوه بر مورد ذكر شده نسبت به Code behind متداول خواهد داشت: وهله سازي آن بايد دستي انجام شود و خودكار نيست.
اگر به ابتداي كلاس‌هاي code behind‌ دقت كنيد هميشه واژه‌ي partial قابل رويت است، به اين معنا كه اين كلاس در حقيقت جزئي از همان كلاس متناظر با XAML ايي است كه مشاهده مي‌كنيد؛ يا به عبارتي با آن يكي است. فقط جهت زيبايي يا مديريت بهتر، در دو كلاس قرار گرفته‌اند اما واژه كليدي partial اين‌ها را نهايتا به صورت يكسان و يكپارچه‌اي به كامپايلر معرفي خواهد كرد. بنابراين وهله سازي code behind هم خودكار خواهد بود و به محض نمايش رابط كاربري،‌ فايل code behind آن هم وهله سازي مي‌شود؛ چون اساسا و در پشت صحنه، از ديدگاه كامپايلر تفاوتي بين اين دو وجود ندارد.

اكنون سؤال اينجا است كه آيا مي‌توان با ViewModel ها هم همين وهله سازي خودكار را به محض نمايش يك View متناظر، پياده سازي كرد؟
البته صحيح آن اين است كه عنوان شود ViewModel متناظر با يك View و نه برعكس. چون روابط در الگوي MVVM از View به ViewModel به Model است و نه حالت عكس؛ مدل نمي‌داند كه ViewModel ايي وجود دارد. ViewModel هم از وجود View ها در برنامه بي‌خبر است و اين «بي‌خبري‌ها» اساس الگوهايي مانند MVC ، MVVM ، MVP‌ و غيره هستند. به همين جهت شاعر در وصف ViewModel فرموده‌اند كه:

اي در درون برنامه‌ام و View از تو بي خبر_________وز تو برنامه‌ام پر است و برنامه از تو بي خبر :)

پاسخ:
بله. براي اين منظور الگوي ديگري به نام ViewModel Locator طراحي شده است؛ روش‌هاي زيادي براي پياده سازي اين الگو وجود دارند كه ساده‌ترين آن‌ها مورد زير است:
فرض كنيد ViewModel ساده زير را قصد داريم به كمك الگوي ViewModel Locator به View ايي تزريق كنيم:

namespace WpfViewModelLocator.ViewModels
{
    public class MainWindowViewModel
    {
        public string SomeText { set; get; }
        public MainWindowViewModel()
        {
            SomeText = "Data ...";
        }
    }
}

براي اين منظور ابتدا كلاس ViewModelLocatorBase زير را تدارك خواهيم ديد:

using WpfViewModelLocator.ViewModels;

namespace WpfViewModelLocator.ViewModelLocator
{
    public class ViewModelLocatorBase
    {
        public MainWindowViewModel MainWindowVm
        {
            get { return new MainWindowViewModel(); }
        }
    }
}

در اينجا يك وهله از كلاس MainWindowViewModel توسط خاصيتي به نام MainWindowVm در دسترس قرار خواهد گرفت. براي اينكه بتوان اين كلاس را در تمام Viewهاي برنامه قابل دسترسي كنيم، آن‌را در App.Xaml تعريف خواهيم كرد:

<Application x:Class="WpfViewModelLocator.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:vml="clr-namespace:WpfViewModelLocator.ViewModelLocator"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <vml:ViewModelLocatorBase x:Key="ViewModelLocatorBase" />
    </Application.Resources>
</Application>

اكنون فقط كافي است در View خود DataContext را به نحو زير مقدار دهي كنيم تا در زمان اجرا به صورت خودكار بتوان به خاصيت MainWindowVm ياد شده دسترسي يافت:

<Window x:Class="WpfViewModelLocator.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
        Title="MainWindow" Height="350" Width="525">
    <Grid DataContext="{Binding Path=MainWindowVm, Source={StaticResource ViewModelLocatorBase}}">
        <TextBlock Text="{Binding SomeText}" VerticalAlignment="Top" Margin="5" />
    </Grid>
</Window>

در مورد ViewModel ها و Viewهاي ديگر هم به همين ترتيب خواهد بود. يك وهله از آن‌ها به كلاس ViewModelLocatorBase اضافه مي‌شود. سپس Binding Path مرتبط به DataContext به نام خاصيتي كه در كلاس ViewModelLocatorBase مشخص خواهيم كرد، Bind خواهد شد.

روش دوم:
اگر در اينجا بخواهيم Path را حذف كنيم و فقط دسترسي عمومي به ViewModelLocatorBase را ذكر كنيم، بايد يك Converter نوشت (چون به اين ترتيب مي‌توان به اطلاعات Binding در متد Convert دسترسي يافت). سپس يك قرار داد را هم تعريف خواهيم كرد؛ به اين صورت كه ما در Converter به نام View دسترسي پيدا مي‌كنيم (از طريق ريفلكشن). سپس نام viewModel ايي را كه بايد به دنبال آن گشت مثلا ViewName به علاوه كلمه ViewModel در نظر خواهيم گرفت. در حقيقت يك نوع Convection over configuration است:

using System;
using System.Globalization;
using System.Linq;
using System.Windows.Data;

namespace WpfViewModelLocator.ViewModelLocator
{
    public class ViewModelLocatorBaseConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            //مقدار در اينجا همان مشخصات ويوو است
            if (value == null) return null;
            string viewTypeName = value.GetType().Name;

            //قرار داد ما است
            //ViewModel Name = ViewName + "ViewModel"
            string viewModelName = string.Concat(viewTypeName, "ViewModel");

            //يافتن اسمبلي كه حاوي ويوو مدل ما است
            var asms = AppDomain.CurrentDomain.GetAssemblies();
            var viewModelAsmName = "WpfViewModelLocator"; //نام پروژه مرتبط
            var viewModelAsm = asms.Where(x => x.FullName.Contains(viewModelAsmName)).First();

            //يافتن اين كلاس ويوو مدل مرتبط
            var viewModelType = viewModelAsm.GetTypes().Where(x => x.FullName.Contains(viewModelName)).FirstOrDefault();
            if (viewModelType == null)
                throw new InvalidOperationException(string.Format("Could not find view model '{0}'", viewModelName));

            //وهله سازي خودكار آن
            return Activator.CreateInstance(viewModelType);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

كار اين تبديلگر بسيار ساده و واضح است. Value‌ دريافتي، وهله‌اي از view است. پس به اين ترتيب مي‌توان نام آن‌را يافت. سپس قرارداد ويژه خودمان را اعمال مي‌كنيم به اين ترتيب كه ViewModel Name = ViewName + "ViewModel" و سپس به دنبال اسمبلي كه حاوي اين نام است خواهيم گشت. آن‌را يافته، كلاس مرتبط را در آن پيدا مي‌كنيم و در آخر، به صورت خودكار آن‌را وهله سازي خواهيم كرد.
اينبار تعريف عمومي اين Conveter در فايل App.Xaml به صورت زير خواهد بود:

<Application x:Class="WpfViewModelLocator.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:vml="clr-namespace:WpfViewModelLocator.ViewModelLocator"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <vml:ViewModelLocatorBaseConverter x:Key="ViewModelLocatorBaseConverter" />
    </Application.Resources>
</Application>

و استفاده‌ي آن در تمام View هاي برنامه به شكل زير مي‌باشد (بدون نياز به ذكر هيچ نام خاصي و بدون نياز به كلاس ViewModelLocatorBase ياد شده در ابتداي مطلب):

<Window x:Class="WpfViewModelLocator.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"                
        DataContext="{Binding RelativeSource={RelativeSource Self}, 
                              Converter={StaticResource ViewModelLocatorBaseConverter}}"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBlock Text="{Binding SomeText}" VerticalAlignment="Top" Margin="5" />
    </Grid>
</Window>


۱۳۹۰/۰۹/۱۹

تكميل كلاس DelegateCommand


مدت‌ها از كلاس DelegateCommand معرفي شده در اين آدرس استفاده مي‌كردم. اين كلاس يك مشكل جزئي دارد و آن هم عدم بررسي مجدد قسمت canExecute به صورت خودكار هست.

خلاصه‌اي براي كساني كه بار اول هست با اين مباحث برخورد مي‌كنند؛ يا MVVM به زبان بسيار ساده:

در برنامه نويسي متداول سيستم مايكروسافتي، در هر سيستمي كه ايجاد كرده و در هر فناوري كه ارائه داده از زمان VB6 تا امروز، شما روي يك دكمه مثلا دوبار كليك مي‌كنيد و در فايل اصطلاحا code behind اين فرم و در روال رخدادگردان آن شروع به كد نويسي خواهيد كرد. اين مورد تقريبا در همه جا صادق است؛ از WinForms تا WPF تا Silverlight تا حتي ASP.NET Webforms . به عمد هم اين طراحي صورت گرفته تا برنامه نويس‌ها در اين محيط‌ها زياد احساس غريبي نكنند. اما اين روش يك مشكل مهم دارد و آن هم «توهم» جداسازي رابط كاربر از كدهاي برنامه است. به ظاهر يك فايل فرم وجود دارد و يك فايل جداي code behind ؛ اما در عمل هر دوي اين‌ها يك partial class يا به عبارتي «يك كلاس» بيشتر نيستند. «فكر مي‌كنيم» كه از هم جدا شدند اما واقعا يكي هستند. شما در code behind صفحه به صورت مستقيم با عناصر رابط كاربري سروكار داريد و كدهاي شما به اين عناصر گره خورده‌اند.
شايد بپرسيد كه چه اهميتي دارد؟
مشكل اول: امكان نوشتن آزمون‌ها واحد براي اين متدها وجود ندارد يا بسيار سخت است. اين متدها فقط با وجود فرم و رابط كاربري متناظر با آن‌ها هست كه معنا پيدا مي‌كنند و تك تك عناصر آن‌ها وهله سازي مي‌شوند.
مشكل دوم: كد نوشته فقط براي همين فرم جاري آن قابل استفاده است؛ چون به صورت صريح به عناصر موجود در فرم اشاره مي‌كند. نمي‌تونيد اين فايل code behind رو برداريد بدون هيچ تغييري براي فرم ديگري استفاده كنيد.
مشكل سوم: نمي‌تونيد طراحي فرم رو بديد به يك نفر، كد نويسي اون رو به شخصي ديگر. چون ايندو لازم و ملزوم يكديگرند.

اين سيستم كد نويسي دهه 90 است.
چند سالي است كه طراحان سعي كرده‌اند اين سيستم رو دور بزنند و روش‌هايي رو ارائه بدن كه در آن‌ها فرم‌هاي برنامه و فايل‌هاي پياده سازي كننده‌ي منطق آن هيچگونه ارتباط مستقيمي باهم نداشته باشند؛ به هم گره نخورده باشند؛ ارجاعي به هيچيك از عناصر بصري فرم را در خود نداشته باشند. به همين دليل ASP.NET MVC به وجود آمده و در همان سال‌ها مثلا MVVM .

سؤال:
الان كه رابط كاربري از فايل پياده سازي كننده منطق آن جدا شده و ديگر Code behind هم نيست (همان partial class هاي متداول)، اين فايل‌ها چطور متوجه مي‌شوند كه مثلا روي يك فرم، شيءايي قرار گرفته؟ از كجا متوجه خواهند شد كه روي دكمه‌اي كليك شده؟ اين‌ها كه ارجاعي از فرم را در درون خود ندارند.
در الگوي MVVM اين سيم كشي توسط امكانات قوي Binding موجود در WPF ميسر مي‌شود. در ASP.NET MVC چيزي شبيه به آن به نام Model binder و همان مكانيزم‌هاي استاندارد HTTP اين كار رو مي‌كنه. در MVVM شما بجاي code behind خواهيد داشت ViewModel (اسم جديد آن). در ASP.NET MVC اين اسم شده Controller. بنابراين اگر اين اسامي رو شنيديد زياد تعجب نكنيد. اين‌ها همان Code behind قديمي هستند اما ... بدون داشتن ارجاعي از رابط كاربري در خود كه ... اطلاعات موجود در فرم به نحوي به آن‌ها Bind و ارسال مي‌شوند.
اين سيم كشي‌ها هم نامرئي هستند. يعني فايل ViewModel يا فايل Controller نمي‌دونند كه دقيقا از چه كنترلي در چه فرمي اين اطلاعات دريافت شده.
اين ايده هم جديد نيست. شايد بد نباشه به دوران طلايي Win32 برگرديم. همان توابع معروف PostMessage و SendMessage را به خاطر داريد؟ شما در يك ترد مي‌تونيد با مثلا PostMessage شيءايي رو به يك فرم كه در حال گوش فرا دادن به تغييرات است ارسال كنيد (اين سيم كشي هم نامرئي است). بنابراين پياده سازي اين الگوها حتي در Win32 و كليه فريم ورك‌هاي ساخته شده بر پايه آن‌ها مانند VCL ، VB6 ، WinForms و غيره ... «از روز اول» وجود داشته و مي‌تونستند بعد از 10 سال نيان بگن كه اون روش‌هاي RAD ايي رو كه ما پيشنهاد داديم، مي‌شد خيلي بهتر از همان ابتدا، طور ديگري پياده سازي بشه.

ادامه بحث!
اين سيم كشي يا اصطلاحا Binding ، در مورد رخدادها هم در WPF وجود داره و اينبار به نام Commands معرفي شده‌است. به اين معنا كه بجاي اينكه بنويسيد:
<Button  Click="btnClick_Event">Last</Button>

بنويسيد:
<Button Command="{Binding GoLast}">Last</Button>

حالا بايد مكانيزمي وجود داشته باشه تا اين پيغام رو به ViewModel برنامه برساند. اينكار با پياده سازي اينترفيس ICommand قابل انجام است كه معرفي يك كلاس عمومي از پياده سازي آن‌را در ابتداي بحث مشاهده نموديد.
در يك DelegateCommand،‌ توسط متد منتسب به executeAction، مشخص خواهيم كرد كه اگر اين سيم كشي برقرار شد (كه ما دقيقا نمي‌دانيم و نمي‌خواهيم كه بدانيم از كجا و كدام فرم دقيقا)، لطفا اين اعمال را انجام بده و توسط متد منتسب به canExecute به سيستم Binding خواهيم گفت كه آيا مجاز هستي اين اعمال را انجام دهي يا خير. اگر اين متد false برگرداند، مثلا دكمه ياد شده به صورت خودكار غيرفعال مي‌شود.
اما مشكل كلاس DelegateCommand ذكر شده هم دقيقا همينجا است. اين دكمه تا ابد غيرفعال خواهد ماند. در WPF كلاسي وجود دارد به نام CommandManager كه حاوي متدي استاتيكي است به نام InvalidateRequerySuggested. اگر اين متد به صورت دستي فراخواني شود، يكبار ديگر كليه متدهاي منتسب به تمام canExecute هاي تعريف شده، به صورت خودكار اجرا مي‌شوند و اينجا است كه مي‌توان دكمه‌اي را كه بايد مجددا بر اساس شرايط جاري تغيير وضعيت پيدا كند، فعال كرد. بنابراين فراخواني متد InvalidateRequerySuggested يك راه حل كلي رفع نقيصه‌ي ذكر شده است.
راه حل دومي هم براي حل اين مشكل وجود دارد. مي‌توان از رخدادگردان CommandManager.RequerySuggested استفاده كرد. روال منتسب به اين رخدادگردان هر زماني كه احساس كند تغييري در UI رخ داده، فراخواني مي‌شود. بنابراين پياده سازي بهبود يافته كلاس DelegateCommand به صورت زير خواهد بود:

using System;
using System.Windows.Input;

namespace MvvmHelpers
{
    // Ref.
    // - http://johnpapa.net/silverlight/5-simple-steps-to-commanding-in-silverlight/
    // - http://joshsmithonwpf.wordpress.com/2008/06/17/allowing-commandmanager-to-query-your-icommand-objects/
    public class DelegateCommand<T> : ICommand
    {
        readonly Func<T, bool> _canExecute;
        bool _canExecuteCache;
        readonly Action<T> _executeAction;

        public DelegateCommand(Action<T> executeAction, Func<T, bool> canExecute = null)
        {
            if (executeAction == null)
                throw new ArgumentNullException("executeAction");

            _executeAction = executeAction;
            _canExecute = canExecute;
        }

        public event EventHandler CanExecuteChanged
        {
            add { if (_canExecute != null) CommandManager.RequerySuggested += value; }
            remove { if (_canExecute != null) CommandManager.RequerySuggested -= value; }
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute((T)parameter);
        }

        public void Execute(object parameter)
        {
            _executeAction((T)parameter);
        }
    }
}

استفاده از آن هم در ViewModel ساده است. يكبار خاصيتي به اين نام تعريف مي‌شود. سپس در سازنده كلاس مقدار دهي شده و متدهاي متناظر آن تعريف خواهند شد:

public DelegateCommand<string> GoLast { set; get; }

//in ctor
GoLast = new DelegateCommand<string>(goLast, canGoLast);

private bool canGoLast(string data)
{
    //ex.
    return ListViewGuiData.CurrentPage != ListViewGuiData.TotalPage - 1;
}

private void goLast(string data)
{
  //do something
}

مزيت كلاس DelegateCommand جديد هم اين است كه مثلا متد canGoLast فوق، به صورت خودكار با به روز رساني UI ، فراخواني و تعيين اعتبار مجدد مي‌شود.


۱۳۹۰/۰۹/۰۸

استفاده از كنترل‌هاي Active-X در WPF


گاهي از اوقات شايد نياز شود تا از يك كنترل Active-X در WPF استفاده شود؛ مثلا هيچ نمايش دهنده‌ي PDF ايي را در ويندوز نمي‌توان يافت كه امكانات و كيفيت آن در حد Acrobat reader و Active-X آن باشد. يك روش استفاده از آن‌را به كمك كنترل WebBrowser در WPF پيشتر در اين سايت مطالعه كرده‌ايد. روش معرفي شده براي WinForm هم در WPF قابل استفاده است كه در ادامه شرح آ‌ن‌ خواهد آمد.

الف) بجاي اضافه كردن يك User control مخصوص WPF يك user control از نوع WinForms را به يك پروژه WPF اضافه كنيد.


سپس مراحل مشابهي را مانند حالت WinForms، بايد طي كرد:
ب) در VS.NET‌ از طريق منوي Tools گزينه‌ي Choose toolbox items ، برگه‌ي Com components را انتخاب كنيد.
ج) سپس گزينه‌ي Adobe PDF reader را انتخاب نمائيد و بر روي دكمه‌ي OK‌ كليك كنيد.


د) اكنون اين كنترل جديد را بر روي فرم user control قسمت الف برنامه قرار دهيد. به صورت خودكار COMReference هاي متناظر هم به پروژه اضافه مي‌شوند.
پس از اينكه كنترل بر روي فرم قرار گرفت بهتر است به خواص آن مراجعه كرده و خاصيت Dock آن‌را با Fill مقدار دهي كرد تا كنترل به صورت خودكار در هر اندازه‌اي كل ناحيه‌ي متناظر را پوشش دهد.


كد‌هاي مرتبط با نمايش فايل PDF اين كنترل هم به شرح زير است:

using System.Windows.Forms;

namespace WpfPdfViewer.Controls
{
    public partial class AcroReader : UserControl
    {
        public AcroReader(string fileName)
        {
            InitializeComponent();
            ShowPdf(fileName);
        }

        public void ShowPdf(string fileName)
        {
            if (string.IsNullOrWhiteSpace(fileName)) return;
            axAcroPDF1.LoadFile(fileName);
            axAcroPDF1.setShowToolbar(true);
            axAcroPDF1.Show();
        }
    }
}


خوب، ما تا اينجا يك كنترل Active-X را از طريق يك User controls مخصوص WinForms به پروژه‌ي WPF جاري اضافه كرده‌ايم. براي اينكه بتوانيم اين كنترل را درون مثلا يك User control از جنس WPF و XAML نمايش دهيم بايد از كنترل WindowsFormsHost استفاده كرد. براي اين منظور نياز است تا ارجاعي را به اسمبلي WindowsFormsIntegration اضافه كنيم. پس از آن كنترل ياد شده قابل استفاده خواهد بود.


براي نمونه كدهاي XAML پنجره اصلي برنامه مي‌تواند به صورت زير باشد:

<Window x:Class="WpfPdfViewer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <WindowsFormsHost x:Name="WindowsFormsHost1" />
    </Grid>
</Window>

سپس جهت استفاده از كنترل WindowsFormsHost خواهيم داشت:

using WpfPdfViewer.Controls;

namespace WpfPdfViewer
{
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();
            WindowsFormsHost1.Child = new AcroReader(@"PageSummary.pdf");
        }
    }
}

فقط كافي است شيء Child اين كنترل را با وهله‌اي از يوزركنترل AcroReader اضافه شده به برنامه مقدار دهي كنيم.

سؤال: اين روش زياد MVVM friendly نيست. به عبارتي Child را نمي‌توان از طريق Binding مقدار دهي كرد. آيا راهي براي آن وجود دارد؟
پاسخ: بله. روش متداول براي حل اين نوع مشكلات، نوشتن يك DependencyObject و Attached property مناسب مي‌باشد كه به آن‌ها Behaviors هم مي‌گويند. براي مثال يك نمونه از اين پياده سازي را در ذيل مشاهده مي‌كنيد:

using System;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Forms.Integration;

namespace WpfPdfViewer.Behaviors
{
    public class WindowsFormsHostBehavior : DependencyObject
    {
        public static readonly DependencyProperty BindableChildProperty =
                                    DependencyProperty.RegisterAttached("BindableChild",
                                    typeof(Control),
                                    typeof(WindowsFormsHostBehavior),
                                    new UIPropertyMetadata(null, BindableChildPropertyChanged));

        public static Control GetBindableChild(DependencyObject obj)
        {
            return (Control)obj.GetValue(BindableChildProperty);
        }

        public static void SetBindableChild(DependencyObject obj, Control value)
        {
            obj.SetValue(BindableChildProperty, value);
        }

        public static void BindableChildPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            var windowsFormsHost = o as WindowsFormsHost;
            if (windowsFormsHost == null)
                throw new InvalidOperationException("This behavior can only be attached to a WindowsFormsHost.");

            var control = (Control)e.NewValue;
            windowsFormsHost.Child = control;
        }
    }
}

كه نهايتا براي استفاده از آن خواهيم داشت:

<WindowsFormsHost 
       Behaviors:WindowsFormsHostBehavior.BindableChild="{Binding ...}" />

و در ViewModel برنامه هم مانند مثال فوق، فقط كافي است يك وهله از new AcroReader به اين خاصيت قابل انقياد از نوع Control، انتساب داده شود.
يا حتي مي‌توان بجاي نوشتن يك BindableChild، براي مثال مسير فايل pdf را به DependencyObject تعريف شده ارسال كرد و سپس در همانجا اين وهله سازي و انتسابات صورت گيرد (بجاي ViewModel برنامه كه اينبار فقط مسير را تنظيم مي‌كند).


۱۳۹۰/۰۷/۰۷

چند نكته در مورد WPF MediaElement و ويندوز XP


WPF MediaElement به صورت پيش فرض در ويندوز XP كار نمي‌كند؛ مگر اينكه حتما آخرين نگارش موجود Windows Media Player بر روي سيستم نصب شده باشد و حداقل نياز به نگارش 10 به بعد را دارد.
اگر اين نگارش نصب نباشد يا هر خطاي ديگري رخ دهد، آن‌را مي‌توان از طريق روال رويداد گردان MediaFailed بدست آورد.
اگر نگارش بتاي مدياپلير 11 بر روي سيستم نصب باشد، با پيغام نه چندان آشناي "insufficient memory" مواجه خواهيد شد و اهميتي هم ندارد كه سيستم در حال حاضر به چه ميزان حافظه‌ي مهيا دسترسي دارد.
و كلا هر آنچه را كه Windows Media Player بتواند پخش كند، WPF MediaElement نيز قادر به پخش آن‌ها خواهد بود. براي فرمت‌هاي ناشناخته و جديد بايد Codec مخصوص آن‌ها در سيستم نصب شده باشد.

راه حل بهتر، استفاده از پروژه‌ي ديگري است به نام WPF Media Kit. اين پروژه، هر آنچه را كه بتوان توسط برنامه GraphEdit پخش كرد، مي‌تواند نمايش دهد.

استفاده از WPF MediaElement به كمك الگوي MVVM درد بزرگي است؛ چون آنچنان از Binding و Commanding پشتيباني نمي‌كند.

۱۳۹۰/۰۴/۲۰

نمايش يك فايل PDF در WinForms ، WPF و سيلورلايت


شايد PDF را بشود تنها فرمت گزارشگيري دانست كه همه‌جا و در تمام سيستم عامل‌ها پشتيباني مي‌شود. از ويندوز تا لينوكس از وب تا WPF تا سيلورلايت تا همه جا و از همه مهم‌تر اينكه خروجي آن دقيقا همان چيزي است كه كاربر نهايي مي‌خواهد: من مي‌خوام اون چيزي رو كه مي‌بينم، دقيقا همان را، بدون كم و كاست و با همان صفحه بندي، بتوانم چاپ كنم.
براي توليد PDF مي‌شود از كتابخانه‌ي iTextSharp استفاده كرد اما براي نمايش آن حداقل در ويندوز بهترين راه حل استفاده از COM Components‌ شركت Adobe است كه به همراه برنامه رايگان Adobe PDF reader ارائه مي‌شود. در ادامه نحوه‌ي استفاده از اين Active-X را بررسي خواهيم كرد.

نمايش PDF در WPF
در تمام حالت‌ها هدف اين است كه به نحوي به اكتيوايكس شركت Adobe دسترسي پيدا كنيم؛ يا با اضافه كردن آن به پروژه يا استفاده از امكانات يكپارچه مرورگرها. در WPF از زمان ارائه سرويس پك يك دات نت سه و نيم (به بعد)، كنترل مرورگر وب هم به جمع كنترل‌هاي قابل استفاده در آن اضافه شده است. در اينجا به سادگي چند سطر زير مي‌شود يك فايل PDF را در WPF نمايش داد:
<Window x:Class="WpfAppTests.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Pdf Report" Height="495" WindowState="Maximized"
WindowStartupLocation="CenterScreen" Width="703">
<Grid>
<WebBrowser x:Name="WebBrowser1"/>
</Grid>
</Window>
و بعد هم در كدهاي برنامه تنها كافي است كه مقدار Source كنترل WebBrowser را مقدار دهي كرد:
WebBrowser1.Source = new Uri(PdfFilePath);


نمايش PDF در WinForms
اكتيوايكس نمايش دهنده PDF شركت Adobe اساسا در فايل ذيل قرار گرفته است:
C:\Program Files\Common Files\Adobe\Acrobat\ActiveX\AcroPDF.dll
بنابراين براي استفاده از آن در يك برنامه‌ي WinForms بايد مراحل ذيل طي شود:
الف) در VS.NET‌ از طريق منوي Tools گزينه‌ي Choose toolbox items ، برگه‌ي Com components را انتخاب كنيد.
ب) سپس گزينه‌ي Adobe PDF reader كه به همان مسير dll فوق اشاره مي‌كند را انتخاب نمائيد و بر روي دكمه‌ي OK‌ كليك كنيد.
ج) اكنون اين كنترل جديد را بر روي فرم برنامه قرار دهيد. به صورت خودكار COMReference هاي متناظر به پروژه اضافه مي‌شوند.

اكنون نحوه‌ي استفاده از اين شيء COM به همراه آزاد سازي منابع مرتبط به شرح زير خواهند بود:
using System.Windows.Forms;

namespace WindowsFormsAppTests
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Load += Form1_Load;
this.FormClosing += Form1_FormClosing;
}

void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
axAcroPDF1.Dispose();
}

void Form1_Load(object sender, System.EventArgs e)
{
axAcroPDF1.LoadFile(PdfFilePath);
axAcroPDF1.setShowToolbar(true);
axAcroPDF1.Show();
}
}
}

نمايش PDF در Silverlight

در Silverlight هم از نسخه‌ي 4 به بعد كنترل WebBrowser همانند آنچه كه در WPF موجود است، اضافه شده است؛ اما اين كنترل فقط در حالت اجراي در خارج از مرورگر برنامه Silverlight در دسترس مي‌باشد. بنابراين روش ديگري را بايد انتخاب كرد. اين روش بر اساس تعامل سيلورلايت با كدهاي HTML صفحه كار مي‌كند. يك IFrame مخفي را در صفحه بالاي شيء مرتبط با سيلورلايت قرار خواهيم داد. سپس در سيلورلايت Src اين IFrame را به مسير فايل PDF تنظيم مي‌كنيم و همين. به اين ترتيب فايل PDF نمايش داده مي‌شود.
اين IFrame به صورت زير در همان صفحه‌ي aspx ايي كه object مرتبط با Silverlight نمايش داده مي‌شود قرار مي‌گيرد:
<iframe id="pdfFrame" style="visibility:hidden; position:absolute"><b>No Content</b></iframe>
<div id="silverlightControlHost">
سپس در كدهاي سيلورلايت، ابتدا اين IFrame يافت شده:
var iFrame = HtmlPage.Document.GetElementById("pdfFrame");
در ادامه بر اساس اطلاعات مكاني يك Grid ساده به نام pdfHost كه در صفحه قرار گرفته، اين iFrame بالاتر از سطح Grid (بر اساس z-index تنظيم شده) نمايش داده مي‌شود:
var gt = pdfHost.TransformToVisual(Application.Current.RootVisual);
var offset = gt.Transform(new Point(0, 0));
var controlLeft = (int)offset.X;
var controlTop = (int)offset.Y;
iFrame.SetStyleAttribute("left", string.Format("{0}px", controlLeft));
iFrame.SetStyleAttribute("top", string.Format("{0}px", controlTop));
iFrame.SetStyleAttribute("visibility", "visible");
iFrame.SetStyleAttribute("height", string.Format("{0}px", pdfHost.ActualHeight));
iFrame.SetStyleAttribute("width", string.Format("{0}px", pdfHost.ActualWidth));
iFrame.SetStyleAttribute("z-index", "1000");
و در آخر نام فايلي را كه مي‌خواهيم مشاهده كنيم به يك صفحه‌ي aspx در همان سايت ارسال مي‌كنيم:
iFrame.SetProperty("src", "ShowPdf.aspx?file=" + fileName);
كدهاي اين صفحه در حد يك Response.Redirect ساده براي نمايش دادن فايل pdf در مرورگر كافي هستند. در كل در اينجا سيلورلايت تنها نقش انتخاب فايل را به عهده دارد و كار اصلي را خود مرورگر انجام مي‌دهد.

۱۳۸۹/۰۹/۲۷

WPF4 و ويندوز 7 : به خاطر سپاري ليست آخرين فايل‌هاي گشوده شده توسط برنامه


اگر به برنامه‌هاي جديد نوشته شده براي ويندوز 7 دقت كنيم، از يك سري امكانات مخصوص آن جهت بهبود دسترسي پذيري به قابليت‌هايي كه ارائه مي‌دهند، استفاده شده است. براي مثال برنامه‌ي OneNote مجموعه‌ي آفيس را در نظر بگيريد. اگر بر روي آيكون آن در نوار وظيفه‌ي ويندوز كليك راست كنيم، ليست آخرين فايل‌هاي گشوده شده توسط آن مشخص است و با كليك بر روي هر كدام، به سادگي مي‌توان اين فايل را گشود. يك چنين قابليتي در منوي آغازين ويندوز نيز تعبيه شده است (شكل‌هاي زير):




خبر خوب اينكه براي اضافه كردن اين قابليت به برنامه‌هاي WPF4 نيازي به كد نويسي نيست و اين موارد كه تحت عنوان استفاده از Jump list ويندوز 7 تعريف شده‌اند، با كمي دستكاري فايل App.Xaml برنامه، فعال مي‌گردند:

<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
<JumpList.JumpList>
<JumpList ShowRecentCategory="True" />
</JumpList.JumpList>
</Application>
همين! از اين پس هر فايلي كه توسط برنامه‌ي شما با استفاده از common file dialog boxes باز شود به صورت خودكار به ليست مذكور اضافه مي‌گردد (بديهي است Jump lists جزو ويژگي‌هاي ويندوز 7 است و در ساير سيستم عامل‌ها نديد گرفته خواهد شد).


سؤال: من اينكار را انجام دادم ولي كار نمي‌كنه!؟

پاسخ: بله. كار نمي‌كنه! اين قابليت تنها زماني فعال خواهد شد كه علاوه بر نكته‌ي فوق، پسوند فايل يا فايل‌هايي نيز به برنامه‌ي شما منتسب شده باشد. اين انتساب‌ها مطلب جديدي نيست و در تمام برنامه‌هاي ويندوزي بايد توسط بكارگيري API ويندوز مديريت شود. قطعه كد زير اينكار را انجام خواهد داد:
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;

namespace Common.Files
{
//from : http://www.devx.com/vb2themax/Tip/19554?type=kbArticle&trk=MSCP
public class FileAssociation
{
const int ShcneAssocchanged = 0x8000000;
const int ShcnfIdlist = 0;

public static void CreateFileAssociation(
string extension,
string className,
string description,
string exeProgram)
{
// ensure that there is a leading dot
if (extension.Substring(0, 1) != ".")
extension = string.Format(".{0}", extension);

try
{
if (IsAssociated(extension)) return;

// create a value for this key that contains the classname
using (var key1 = Registry.ClassesRoot.CreateSubKey(extension))
{
if (key1 != null)
{
key1.SetValue("", className);
// create a new key for the Class name
using (var key2 = Registry.ClassesRoot.CreateSubKey(className))
{
if (key2 != null)
{
key2.SetValue("", description);
// associate the program to open the files with this extension
using (var key3 = Registry.ClassesRoot.CreateSubKey(string.Format(@"{0}\Shell\Open\Command", className)))
{
if (key3 != null) key3.SetValue("", string.Format(@"{0} ""%1""", exeProgram));
}
}
}
}
}

// notify Windows that file associations have changed
SHChangeNotify(ShcneAssocchanged, ShcnfIdlist, 0, 0);
}
catch (Exception ex)
{
//todo: log ...
}
}

// Return true if extension already associated in registry
public static bool IsAssociated(string extension)
{
return (Registry.ClassesRoot.OpenSubKey(extension, false) != null);
}

[DllImport("shell32.dll")]
public static extern void SHChangeNotify(int wEventId, int uFlags, int dwItem1, int dwItem2);
}
}
و مثالي از نحوه‌ي استفاده از آن:
private static void createFileAssociation()
{
var appPath = Assembly.GetExecutingAssembly().Location;
FileAssociation.CreateFileAssociation(".xyz", "xyz", "xyz File",
appPath
);
}
لازم به ذكر است كه اين كد در ويندوز 7 فقط با دسترسي مديريتي قابل اجرا است (كليك راست و اجرا به عنوان ادمين) و در ساير حالات با خطاي Access is denied متوقف خواهد شد. به همين جهت بهتر است برنامه‌ي نصاب مورد استفاده اين نوع انتسابات را مديريت كند؛ زيرا اكثر آن‌ها با دسترسي مديريتي است كه مجوز نصب را به كاربر جاري خواهند داد. اگر از فناوري Click once استفاده مي‌كنيد به اين مقاله و اگر براي مثال از NSIS كمك مي‌گيريد به اين مطلب مراجعه نمائيد.


سؤال: من اين كارها را هم انجام دادم. الان به چه صورت از آن‌ استفاده كنم؟

زمانيكه كاربري بر روي يكي از اين فايل‌هاي ذكر شده در ليست آخرين فايل‌هاي گشوده شده توسط برنامه كليك كند، آدرس اين فايل به صورت يك آرگومان به برنامه ارسال خواهد شد. براي مديريت آن در WPF بايد به فايل App.Xaml.cs مراجعه كرده و چند سطر زير را به آن افزود:
    public partial class App
{
public App()
{
this.Startup += appStartup;
}

void appStartup(object sender, StartupEventArgs e)
{
if (e.Args.Any())
{
this.Properties["StartupFileName"] = e.Args[0];
}
}
//...

در اين كد، e.Args حاوي مسير فايل انتخابي است. براي مثال در اينجا مقدار آن به خاصيت StartupFileName انتساب داده شده است. اين خاصيت در برنامه‌هاي WPF به صورت يك خاصيت عمومي تعريف شده است و در سراسر برنامه (مثلا در رخداد آغاز فرم اصلي آن يا هر جاي ديگري) به صورت زير قابل دسترسي است:
var startupFileName = Application.Current.Properties["StartupFileName"];

سؤال: برنامه‌ي من از OpenFileDialog براي گشودن فايل‌ها استفاده نمي‌كند. آيا راه ديگري براي افزودن مسيرهاي باز شده به Jump lists ويندوز 7 وجود دارد؟

پاسخ: بله. همانطور كه مي‌دانيد عناصر XAML با اشياء دات نت تناظر يك به يك دارند. به اين معنا كه JumpList تعريف شده در ابتداي اين مطلب در فايل App.XAML ، دقيقا معادل كلاسي به همين نام در دات نت فريم ورك است (تعريف شده در فضاي نام System.Windows.Shell) و با كد نويسي نيز قابل دسترسي و مديريت است. براي مثال:
var jumpList = JumpList.GetJumpList(App.Current);
var jumpPath = new JumpPath();
jumpPath.Path = "some path goes here....";
// If the CustomCategory property is null
// or Empty, the item is added to the Tasks category
jumpPath.CustomCategory = "Files";
JumpList.AddToRecentCategory(jumpPath);
jumpList.Apply();
به همين ترتيب،‌ JumpPath ذكر شده در كدهاي فوق، در كدهاي XAML نيز قابل تعريف است:
<Application x:Class="Win7Wpf4.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
<JumpList.JumpList>
<JumpList ShowRecentCategory="True">
<JumpPath
CustomCategory="Files"
Path="Some path goes here..."
/>
</JumpList>
</JumpList.JumpList>
</Application>

۱۳۸۹/۰۸/۰۱

معرفي يك ابزار گزارشگيري رايگان مخصوص WPF


تا صحبت از گزارشگيري به ميان بيايد احتمالا معرفي ابزارهاي تجاري مانند Reporting services ، كريستال ريپورت، stimulsoft.com ، fast-report.com و امثال آن درصدر ليست توصيه كنندگان و مشاوران قرار خواهند داشت. اما خوب براي ايجاد يك گزارشگيري ساده حتما نيازي نيست تا به اين نوع ابزارهاي تجاري مراجعه كرد. ابزار رايگان و سورس باز جالبي هم در اين باره جهت پروژه‌هاي WPF در دسترس است:



در ادامه در طي يك مثال قصد داريم از اين كتابخانه استفاده كنيم:

1) تنظيم وابستگي‌ها
پس از دريافت كتابخانه فوق، ارجاعات زير بايد به پروژه شما اضافه شوند:
CodeReason.Reports.dll (از پروژه فوق) و ReachFramework.dll (جزو اسمبلي‌هاي استاندارد دات نت است)

2) تهيه منبع داده‌ گزارش
كتابخانه‌ي فوق به صورت پيش فرض با DataTable‌ كار مي‌كند. بنابراين كوئري‌هاي شما يا بايد خروجي DataTable داشته باشد يا بايد از يك سري extension methods براي تبديل IEnumerable به DataTable استفاده كرد (در پروژه پيوست شده در پايان مطلب، اين موارد موجود است).
براي مثال فرض كنيد مي‌خواهيم ركوردهايي را از نوع كلاس Product زير در گزارش نمايش دهيم:

namespace WpfRptTests.Model
{
public class Product
{
public string Name { set; get; }
public int Price { set; get; }
}
}
3) تعريف گزارش
الف) اضافه كردن فايل تشكيل دهنده ساختار و ظاهر گزارش
گزارش‌‌هاي اين كتابخانه مبتني است بر اشياء FlowDocument استاندارد WPF . بنابراين از منوي پروژه گزينه‌ي Add new item در قسمت WPF آن يك FlowDocument جديد را به پروژه اضافه كنيد ( بايد دقت داشت كه Build action اين فايل بايد به Content تنظيم گردد). ساختار ابتدايي اين FlowDocument به صورت زير خواهد بود كه به آن FlowDirection و FontFamily مناسب جهت گزارشات فارسي اضافه شده است. همچنين فضاي نام مربوط به كتابخانه‌ي گزارشگيري CodeReason.Reports نيز بايد اضافه گردد.
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FlowDirection="RightToLeft" FontFamily="Tahoma"
xmlns:xrd="clr-namespace:CodeReason.Reports.Document;assembly=CodeReason.Reports"
PageHeight="29.7cm" PageWidth="21cm" ColumnWidth="21cm">

</FlowDocument>

مواردي كه در ادامه ذكر خواهند شد محتواي اين گزارش را تشكيل مي‌دهند:
ب) مشخص سازي خواص گزارش

<xrd:ReportProperties>
<xrd:ReportProperties.ReportName>SimpleReport</xrd:ReportProperties.ReportName>
<xrd:ReportProperties.ReportTitle>گزارش از محصولات</xrd:ReportProperties.ReportTitle>
</xrd:ReportProperties>
در اينجا ReportName و ReportTitle بايد مقدار دهي شوند (دو dependency property كه در كتابخانه‌ي CodeReason.Reports تعريف شده‌اند)

ج) مشخص سازي Page Header و Page Footer
اگر مي‌خواهيد عباراتي در بالا و پايين تمام صفحات گزارش تكرار شوند مي‌توان از SectionReportHeader و SectionReportFooter اين كتابخانه به صورت زير استفاده كرد:
    <xrd:SectionReportHeader PageHeaderHeight="2" Padding="10,10,10,0" FontSize="12">
<Table CellSpacing="0">
<Table.Columns>
<TableColumn Width="*" />
<TableColumn Width="*" />
</Table.Columns>
<TableRowGroup>
<TableRow>
<TableCell>
<Paragraph>
<xrd:InlineContextValue PropertyName="ReportTitle" />
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Right">
<xrd:InlineDocumentValue PropertyName="PrintDate" Format="dd.MM.yyyy HH:mm:ss" />
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>
</Table>
</xrd:SectionReportHeader>

<xrd:SectionReportFooter PageFooterHeight="2" Padding="10,0,10,10" FontSize="12">
<Table CellSpacing="0">
<Table.Columns>
<TableColumn Width="*" />
<TableColumn Width="*" />
</Table.Columns>
<TableRowGroup>
<TableRow>
<TableCell>
<Paragraph>
نام كاربر:
<xrd:InlineDocumentValue PropertyName="RptBy" Format="dd.MM.yyyy HH:mm:ss" />
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Right">
صفحه
<xrd:InlineContextValue PropertyName="PageNumber" FontWeight="Bold" /> از
<xrd:InlineContextValue PropertyName="PageCount" FontWeight="Bold" />
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>
</Table>
</xrd:SectionReportFooter>

دو نكته در اينجا حائز اهميت هستند: xrd:InlineDocumentValue و xrd:InlineContextValue
InlineDocumentValue را مي‌توان در كد‌هاي برنامه به صورت سفارشي اضافه كرد. بنابراين هر جايي كه نياز بود مقدار ثابتي از طريق كد نويسي به گزارش تزريق و اضافه شود مي‌توان از InlineDocumentValue استفاده كرد. براي مثال در كدهاي ViewModel برنامه كه در ادامه ذكر خواهد شد دو مقدار PrintDate و RptBy به صورت زير تعريف و مقدار دهي شده‌اند:
data.ReportDocumentValues.Add("PrintDate", DateTime.Now);
data.ReportDocumentValues.Add("RptBy", "وحيد");
براي مشاهده مقادير مجاز مربوط به InlineContextValue به فايل ReportContextValueType.cs سورس كتابخانه مراجعه كنيد كه شامل PageNumber, PageCount, ReportName, ReportTitle است و توسط CodeReason.Reports به صورت پويا تنظيم خواهد شد.

د) مشخص سازي ساختار توليدي گزارش

<Section Padding="80,10,40,10" FontSize="12">
<Paragraph FontSize="24" TextAlignment="Center" FontWeight="Bold">
<xrd:InlineContextValue PropertyName="ReportTitle" />
</Paragraph>
<Paragraph TextAlignment="Center">
گزارش از ليست محصولات در تاريخ:
<xrd:InlineDocumentValue PropertyName="PrintDate" Format="dd.MM.yyyy HH:mm:ss" />
توسط:
<xrd:InlineDocumentValue PropertyName="RptBy" Format="dd.MM.yyyy HH:mm:ss" />
</Paragraph>
<xrd:SectionDataGroup DataGroupName="ItemList">
<Table CellSpacing="0" BorderBrush="Black" BorderThickness="0.02cm">
<Table.Resources>
<!-- Style for header/footer rows. -->
<Style x:Key="headerFooterRowStyle" TargetType="{x:Type TableRowGroup}">
<Setter Property="FontWeight" Value="DemiBold"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Background" Value="LightGray"/>
</Style>

<!-- Style for data rows. -->
<Style x:Key="dataRowStyle" TargetType="{x:Type TableRowGroup}">
<Setter Property="FontSize" Value="12"/>
</Style>

<!-- Style for data cells. -->
<Style TargetType="{x:Type TableCell}">
<Setter Property="Padding" Value="0.1cm"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="0.01cm"/>
</Style>
</Table.Resources>

<Table.Columns>
<TableColumn Width="0.8*" />
<TableColumn Width="0.2*" />
</Table.Columns>
<TableRowGroup Style="{StaticResource headerFooterRowStyle}">
<TableRow>
<TableCell>
<Paragraph TextAlignment="Center">
<Bold>نام محصول</Bold>
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Center">
<Bold>قيمت</Bold>
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>

<TableRowGroup Style="{StaticResource dataRowStyle}">
<xrd:TableRowForDataTable TableName="Product">
<TableCell>
<Paragraph>
<xrd:InlineTableCellValue PropertyName="Name" />
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Center">
<xrd:InlineTableCellValue PropertyName="Price" AggregateGroup="Group1" />
</Paragraph>
</TableCell>
</xrd:TableRowForDataTable>
</TableRowGroup>

<TableRowGroup Style="{StaticResource headerFooterRowStyle}">
<TableRow>
<TableCell>
<Paragraph TextAlignment="Right">
<Bold>جمع كل</Bold>
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Center">
<Bold>
<xrd:InlineAggregateValue AggregateGroup="Group1"
AggregateValueType="Sum"
EmptyValue="0"
FontWeight="Bold" />
</Bold>
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>

</Table>

<Paragraph TextAlignment="Center" Margin="5">
در اين گزارش
<xrd:InlineAggregateValue AggregateGroup="Group1"
AggregateValueType="Count"
EmptyValue="هيچ"
FontWeight="Bold" /> محصول با جمع كل قيمت
<xrd:InlineAggregateValue AggregateGroup="Group1"
AggregateValueType="Sum"
EmptyValue="0"
FontWeight="Bold" /> وجود دارند.
</Paragraph>
</xrd:SectionDataGroup>
</Section>
براي اينكه بتوان اين قسمت‌ها را بهتر توضيح داد، نياز است تا تصاوير مربوط به خروجي اين گزارش نيز ارائه شوند:




در ابتدا توسط دو پاراگراف، عنوان گزارش و يك سطر زير آن نمايش داده شده‌اند. بديهي است هر نوع شيء و فرمت مجاز در FlowDocument را مي‌توان در اين قسمت نيز قرار داد.
سپس يك SectionDataGroup جهت نمايش ليست آيتم‌ها اضافه شده و داخل آن يك جدول كه بيانگر ساختار جدول نمايش ركوردهاي گزارش مي‌باشد، ايجاد گرديده است.
سه TableRowGroup در اين جدول تعريف شده‌اند.
TableRowGroup هاي اولي و آخري دو سطر اول و آخر جدول گزارش را مشخص مي‌كنند (سطر عناوين ستون‌ها در ابتدا و سطر جمع كل در پايان گزارش)
از TableRowGroup مياني براي نمايش ركوردهاي مرتبط با نام جدول مورد گزارشگيري استفاده شده است. توسط TableRowForDataTable آن نام اين جدول بايد مشخص شود كه در اينجا همان نام كلاس مدل برنامه است. به كمك InlineTableCellValue، خاصيت‌هايي از اين كلاس را كه نياز است در گزارش حضور داشته باشند، ذكر خواهيم كرد. نكته‌ي مهم آن AggregateGroup ذكر شده است. توسط آن مي‌توان اعمال جمع، محاسبه تعداد، حداقل و حداكثر و امثال آن‌را كه در فايل InlineAggregateValue.cs سورس كتابخانه ذكر شده‌اند، به فيلدهاي مورد نظر اعمال كرد. براي مثال مي‌خواهيم جمع كل قيمت را در پايان گزارش نمايش دهيم به همين جهت نياز بود تا يك AggregateGroup را براي اين منظور تعريف كنيم.
از اين AggregateGroup در سومين TableRowGroup تعريف شده به كمك xrd:InlineAggregateValue جهت نمايش جمع نهايي استفاده شده است.
همچنين اگر نياز بود در پايان گزارش اطلاعات بيشتري نيز نمايش داده شود به سادگي مي‌توان با تعريف يك پاراگراف جديد، اطلاعات مورد نظر را نمايش داد.

4) نمايش گزارش تهيه شده
نمايش اين گزارش بسيار ساده است. View برنامه به صورت زير خواهد بود:
<Window x:Class="WpfRptTests.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:CodeReason.Reports.Controls;assembly=CodeReason.Reports"
xmlns:vm="clr-namespace:WpfRptTests.ViewModel"
Title="MainWindow" WindowState="Maximized" Height="350" Width="525">
<Window.Resources>
<vm:ProductViewModel x:Key="vmProductViewModel" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource vmProductViewModel}}">
<c:BusyDecorator IsBusyIndicatorHidden="{Binding RptGuiModel.IsBusyIndicatorHidden}">
<DocumentViewer Document="{Binding RptGuiModel.Document}" />
</c:BusyDecorator>
</Grid>
</Window>

تعريف ابتدايي RptGuiModel به صورت زير است (جهت مشخص سازي مقادير IsBusyIndicatorHidden و Document در حين بايندينگ اطلاعات):

using System.ComponentModel;
using System.Windows.Documents;

namespace WpfRptTests.Model
{
public class RptGuiModel
{
public IDocumentPaginatorSource Document { get; set; }
public bool IsBusyIndicatorHidden { get; set; }
}
}
و اين View اطلاعات خود را از ViewModel زير دريافت خواهد نمود:
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using CodeReason.Reports;
using WpfRptTests.Helper;
using WpfRptTests.Model;

namespace WpfRptTests.ViewModel
{
public class ProductViewModel
{
#region Constructors (1)

public ProductViewModel()
{
RptGuiModel = new RptGuiModel();
if (Stat.IsInDesignMode) return;
//انجام عمليات نمايش گزارش در يك ترد ديگر جهت قفل نشدن ترد اصلي برنامه
showReportAsync();
}

#endregion Constructors

#region Properties (1)

public RptGuiModel RptGuiModel { set; get; }

#endregion Properties

#region Methods (3)

// Private Methods (3)

private static List<Product> getProducts()
{
var products = new List<Product>();
for (var i = 0; i < 100; i++)
products.Add(new Product { Name = string.Format("Product{0}", i), Price = i });

return products;
}

private void showReport()
{
try
{
//Show BusyIndicator
RptGuiModel.IsBusyIndicatorHidden = false;

var reportDocument =
new ReportDocument
{
XamlData = File.ReadAllText(@"Report\SimpleReport.xaml"),
XamlImagePath = Path.Combine(Environment.CurrentDirectory, @"Report\")
};

var data = new ReportData();

// تعريف متغيرهاي دلخواه و مقدار دهي آن‌ها
data.ReportDocumentValues.Add("PrintDate", DateTime.Now);
data.ReportDocumentValues.Add("RptBy", "وحيد");

// استفاده از يك سري اطلاعات آزمايشي به عنوان منبع داده
data.DataTables.Add(getProducts().ToDataTable());

var xps = reportDocument.CreateXpsDocument(data);
//انقياد آن به صورت غير همزمان در ترد اصلي برنامه
DispatcherHelper.DispatchAction(
() => RptGuiModel.Document = xps.GetFixedDocumentSequence()
);
}
catch (Exception ex)
{
//وجود اين مورد ضروري است زيرا بروز استثناء در يك ترد به معناي خاتمه آني برنامه است
//todo: log errors
}
finally
{
//Hide BusyIndicator
RptGuiModel.IsBusyIndicatorHidden = true;
}
}

private void showReportAsync()
{
var thread = new Thread(showReport);
thread.SetApartmentState(ApartmentState.STA); //for DocumentViewer
thread.Start();
}

#endregion Methods
}
}

توضيحات:
براي اينكه حين نمايش گزارش، ترد اصلي برنامه قفل نشود، از ترد استفاده شد و استفاده ترد به همراه DocumentViewer كمي نكته دار است:
- ترد تعريف شده بايد از نوع STA باشد كه در متد showReportAsync مشخص شده است.
- حين باينديگ Document توليد شده توسط كتابخانه‌ي گزارشگيري به خاصيت Document كنترل، حتما بايد كل عمليات در ترد اصلي برنامه صورت گيرد كه سورس كلاس DispatcherHelper را در فايل پيوست خواهيد يافت.

كل عمليات اين ViewModel در متد showReport رخ مي‌دهد، ابتدا فايل گزارش بارگذاري مي‌شود، سپس متغيرهاي سفارشي مورد نظر تعريف و مقدار دهي خواهند شد. در ادامه يك سري داده آزمايشي توليد و به DataTables گزارش ساز اضافه مي‌شوند. در پايان XPS Document متناظر آن توليد شده و به كنترل نمايشي برنامه بايند خواهد شد.

دريافت سورس اين مثال

۱۳۸۹/۰۷/۲۱

خلاصه‌اي از مبحث نمايش اطلاعات hierarchical در WPF


در اين مطلب خلاصه‌اي را در مورد نحوه‌ي نمايش اطلاعات hierarchical (سلسله مراتبي، درختي) در WPF به همراه يك سري لينك مرتبط ملاحظه خواهيد نمود.

كلاس زير را در نظر بگيريد:
using System.Collections.Generic;

namespace WpfTests.Hierarchy.Raw.Model
{
public class Person
{
private readonly List<Person> _children = new List<Person>();
public IList<Person> Children
{
get { return _children; }
}

public string Name { get; set; }
}
}
و همچنين يك ObservableCollection ساخته شده از آن‌را با مقدار دهي اوليه:
using System.Collections.ObjectModel;

namespace WpfTests.Hierarchy.Raw.Model
{
public class People : ObservableCollection<Person>
{
public People()
{
this.Add(
new Person
{
Name = "P1",
Children =
{
new Person
{
Name="P2",
Children=
{
new Person
{
Name="P3",
Children=
{
new Person
{
Name="P4",
}
}
}
}
}
}
}
);
}
}
}
قصد داريم اين اطلاعات را در يك TreeView نمايش دهيم.
روش صحيح Binding اين نوع اطلاعات در WPF استفاده از HierarchicalDataTemplate است به صورت زير :
<TreeView ItemsSource="{Binding People}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>


يك سري منبع آموزشي براي آشنايي بيشتر با HierarchicalDataTemplate
Hierarchical Databinding in WPF
Binding WPF Treeview and Objects
A TreeView, a HierarchicalDataTemplate, and a 2D collection
Non-recursive WPF TreeView controls

همچنين هنگام كار با بانك‌هاي اطلاعاتي:
- يك Extension method عالي قابل استفاده در LINQ to SQL و همچنين Entity framework به نام AsHierarchy
- مثالي ديگر از كاربرد LINQ to SQL براي اين منظور
- و يا مثالي از ADO.NET و DataSets و مثالي ديگر

۱۳۸۹/۰۷/۰۸

WPF و قالب‌هايي جهت كنترل DataGrid


در مورد معرفي WPF Extended toolkit چندي قبل مطلبي منتشر شد. در ادامه اين بي مهري‌ها (!) مي‌توان به عدم به روز رساني قالب‌هاي ارائه شده براي WPF اشاره كرد. در WPF4 ، كنترل DataGrid از WPF toolkit به مجموعه‌ي كنترل‌هاي اصلي WPF منتقل شده است، اما قالب‌هاي منتشر شده‌ي آن جهت لحاظ كردن اين مورد به روز نشده‌اند. يعني اگر براي مثال يكي از قالب‌هاي موجود را به برنامه خود اعمال كنيد و سپس DataGrid را بر روي فرم قرار دهيد، وصله‌ي ناهماهنگي را مشاهده خواهيد نمود. اين مشكلات در Silverlight وجود ندارند و قالب‌هاي ارائه شده‌ي براي آن به روز بوده و همچنين روز به روز هم تعدادشان بيشتر مي‌شوند.
اما باز هم نمي‌توان ايراد گرفت چون كار ارائه شده سورس باز است. به عبارتي اگر مايكروسافت اين قالب‌ها را به روز نكرده، خوب، لطفا خود شما وقت بگذاريد و اين كار را انجام داده و سپس يك patch ارائه دهيد. ايرادي دارد؟!
براي اين منظور پروژه‌اي در سايت CodePlex ايجاد شده است و تنها به پوشش دات نت سه و نيم و ديتاگريد متعلق به WPF Toolkit پرداخته است :


اگر علاقمند باشيد كه از ديتاگريد بومي دات نت 4 استفاده كنيد مي‌توانيد از اين patch استفاده كنيد.

۱۳۸۹/۰۶/۲۰

معرفي WPF Extended toolkit


يكي از نكات جالبي كه در مورد Silverlight وجود دارد اين است كه هر چند تنها قسمتي از WPF را به ارث برده (براي اينكه حجم افزونه‌ي آن قابل قبول باشد)، اما بيشتر از خود WPF مورد توجه مايكروسافت است! شايد يك دليل آن استفاده از Silverlight در Windows phone 7 باشد. به عبارتي اگر برنامه نويس Silverlight هستيد، هم اكنون برنامه نويس Windows phone 7 نيز مي‌باشيد.
اين توجه بيشتر در Silverlight toolkit كاملا مشخص است. Silverlight toolkit از يك سري ابزار و كامپوننت براي توسعه‌ي ساده‌تر برنامه‌هاي Silverlight به صورت سورس باز و تهيه شده توسط مايكروسافت، تشكيل شده است. حجم WPF toolkit كه آن هم توسط مايكروسافت به صورت سورس باز ارائه و به روز مي‌شود حدود 2 مگابايت است؛ اما حجم Silverlight toolkit حدود 18 مگابايت مي‌باشد! بسياري از كنترل‌ها و امكانات Silverlight toolkit را در WPF نمي‌توانيد پيدا كنيد مانند BusyIndicator ، ChildWindow ، DataForm و غيره. نمونه‌ي ديگر اين توجه WCF RIA Services است. هدفگيري اصلي اين مورد نيز Silverlight است و نه WPF (كه از آن در Visual studio LightSwitch هم استفاده كرده‌اند).
اخيرا يك گروه خيّر كار تبديل و انتقال كنترل‌هاي Silverlight toolkit به WPF toolkit را شروع كرده است كه حاصل آن از آدرس ذيل قابل دريافت است: (اين هم يكي از مزيت‌هاي پروژه‌هاي سورس باز است)



۱۳۸۹/۰۵/۰۴

دو تنظيم ضروري VS.NET جهت كار با WPF و Silverlight


تنظيم اول: تغيير نحوه‌ي نمايش پيش فرض فايل‌هاي XAML

اگر فايل XAML شما اندكي حجيم شود نمايش آن در VS.NET كمي طولاني خواهد شد و حالت پيش فرض نمايش در VS.NET هم split view mode است (نمايش XAML و پيش نمايش آن با هم). اين مورد هم پس از مدتي تبديل به عذاب مي‌شود. براي رفع آن مي‌توان حالت پيش فرض نمايش يك فايل XAML را به XAML View تنها تغيير داد.
براي اين منظور به منوي Tools ، گزينه‌ي Options و سپس قسمت تنظيمات Text editor مراجعه كنيد. در اينجا در قسمت XAML ، گزينه‌ي Miscellaneous را انتخاب كرده و سپس "Always open documents in full XAML view" را تيك بزنيد.



حتي ممكن است اين مورد هم رضايت بخش نباشد. در اين حالت مي‌توان ويرايشگر پيش فرض را كلا تغيير داد. Design tab را در پايين صفحه از دست مي‌دهيم اما هنوز intellisense كار مي‌كند و اگر نياز به designer بود فقط كافي است كليك راست كرده و گزينه‌ي View designer را انتخاب كرد:
روي يك فايل XAML دلخواه كليك راست كرده و گزينه‌ي Open with را انتخاب كنيد. سپس "Source Code (Text) Editor" را انتخاب كرده و روي دكمه‌ي Set as Default كليك كنيد. تمام!
هر چند Blend اين مشكلات را ندارد و با فايل‌هاي حجيم XAML به خوبي كاري مي‌كند.


تنظيم دوم: تغيير نحوه‌ي نمايش مشكلات ناشي از Binding

عموما اگر مشكلاتي در حين عمليات Binding در WPF يا Silverlight وجود داشته باشند، خطاها در Debugger Output Window نمايش داده مي‌شوند. حالت پيش فرض هم فقط روي Error تنظيم شده است به اين معنا كه warning ها را مشاهده نخواهيد كرد. براي تغيير اين مورد بايد به صورت زير عمل كرد:
به منوي Tools ، گزينه‌ي Options و سپس قسمت تنظيمات Debugging مراجعه كنيد. گزينه‌ي Output Window -> WPF Trace Settings را انتخاب نمائيد. سپس در اينجا قسمت WPF trace settings را يافته و مقدار پيش فرض Data binding را كه به Error تنظيم شده است، به Warning تنظيم نمائيد.



۱۳۸۸/۱۲/۱۴

خلاصه‌اي كاربردي در مورد Observable collection


Observable collection در WPF را مي‌توان نوعي ليست جنريك ويژه تعريف كرد كه زمانيكه به كنترلي بايند شد، كنترل را از تغييرات خودش آگاه مي‌كند. براي مثال اگر آيتمي به اين ليست اضافه شد بلافاصله آن آيتم را در كنترل مقيد به آن نيز خواهيد ديد، به همين ترتيب در مورد ويرايش و يا حذف يك آيتم، بدون نياز به كوچكترين تماسي با كنترل مورد نظر. براي مثال اگر مقدار يك خاصيت را تغيير داديد، بلافاصله بدون اينكه به كنترل مقيد به آن اعلام كنيم كه لطفا اين مورد ويژه را براي من تغيير بده، شاهد نتيجه‌ي نهايي خواهيم بود.



اما استفاده‌ي پيشرفته از اين ليست جنريك ويژه به همينجا ختم نشده و حين اضافه كردن كمي پيچيدگي به برنامه مشكلات عديده‌اي بروز مي‌كنند كه آن‌ها را جهت دسترسي ساده‌ي بعدي در زير ليست مي‌كنم:

الف) اصلا Observable collection چيست؟ چكار مي‌كند؟
List vs ObservableCollection vs INotifyPropertyChanged in Silverlight

ب) نمي‌توانم از اين مجموعه‌ي اشياي خودآگاه سازنده در يك ترد استفاده كنم. مشكل كجاست؟
اين روزها نمي‌توان يك برنامه‌ي دسكتاپ خوب را بدون استفاده از تردها متصور شد. اما به محض سعي در به روز رساني اين ليست جنريك در يك ترد ديگر (ترد ديگر منظور هر تردي بجز ترد اصلي برنامه است كه كار مديريت رابط كاربر را به عهده دارد) خطاي زير ظاهر مي‌شود:
This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread

راه حل:
Adding to an ObservableCollection from a background thread

ج) يكي از خاصيت‌هاي يك شيء اين ليست جنريك ويژه را تغيير داده‌ام. اما هيچ تغييري در كنترل بايند شده به آن مشاهده نمي‌كنم. مشكل در كجاست؟
راه حل: پياده سازي اينترفيس INotifyPropertyChanged را فراموش كرده‌ايد:
Data Binding in WPF with the Monostate Pattern

د) خوب، اين كه خيلي دردسر دارد! راه ساده‌تري براي تعريف اين موارد نيست؟!
هوشمندانه‌ترين روشي كه براي حل اين مساله تابحال ديده‌ام:
An easier way to manage INotifyPropertyChanged

ه) زمانيكه اين يك ليست جنريك خودآگاه سازنده را به يك مثلا listview بايند مي‌كنم، ديگر نمي‌توانم با استفاده از متد clear items آن كنترل، نسبت به خالي كردن نماي ظاهري آن اقدام كنم. چكار بايد كرد؟
خطاي مشاهده شده:
Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead

راه حل:
همان Observable collection اصلي را تخليه كنيد، UI به صورت خودكار به روز خواهد شد.

و) اضافه كردن رنجي از اطلاعات به آن به صورتي يكباره ممكن است كند باشد. چه بايد كرد؟
راه حل:
AddRange for ObservableCollection in Silverlight 3


۱۳۸۸/۱۲/۱۰

ارتقاء از WinForms به WPF


اگر مدت‌ها كارتان برنامه نويسي WinForms بوده و اكنون احساس كرده‌ايد كه ديگر WinForms آنچنان توسعه و بسط نخواهد يافت و اكنون WPF تبديل به انتخاب اصلي شركت‌هاي بزرگ شده است و همچنين از پرسه زدن در فوروم‌هاي وارز جهت يافتن فلان كامپوننت خاص براي زيباسازي ظاهر برنامه‌هاي خود خسته شده‌ايد و نياز به معادل بهتري كه اساسا در جهت حذف اين بازار سياه تهيه شده است، احساس مي‌كنيد، بهترين گزينه‌ي موجود WPF خواهد بود كه با كمي دقت، مي‌توان پروژه‌هاي آن‌را تبديل به پروژه‌هاي وب نيز نمود. مطلب 54 صفحه‌اي ذيل، خلاصه‌ي كاربردي سريعي را جهت ارتقاء برنامه نويس‌هاي WinForms به WPF ارائه مي‌دهد:



ماخذ: +

۱۳۸۸/۱۲/۰۷

ويديوهاي رايگان آموزشي WPF


يك سري ويديوي آموزشي رايگان WPF بجا مانده از Boot Camp 2008 را از اينجا مي‌توانيد دريافت كنيد كه شامل مباحث مختلف بايندينگ، styles ، prism و غيره است.



۱۳۸۸/۰۸/۲۰

سري آموزشي PRISM


PRISM يا Composite Application Guidance الگوهايي را براي توليد برنامه‌هاي WPF ويا Silverlight ماژولار با قابليت تست پذيري بالا ارائه مي‌دهند. شعار اين مجموعه built for change و built to last است كه به معناي تهيه سيستم‌هايي با قابليت تغيير بالا و همچنين سهولت نگهداري آن‌ها در دراز مدت مي‌باشد.
جناب Mike Taulty را احتمالا با ويديوهاي آموزش WCF به خاطر داريد. ايشان مجموعه جديدي را به نام Video Series on PRISM for Silverlight 3 تهيه كرده‌اند كه از لينك‌هاي زير قابل دريافت است:





۱۳۸۸/۰۶/۲۶

آموزش رايگان XAML از مايكروسافت


يك دوره آموزشي رايگان XAML اخيرا از طرف مايكروسافت ارائه شده است كه از طريق آدرس زير قابل دسترسي است:


اين كلينيك آموزشي شامل موارد زير است:
  • Navigation Overview
  • Clinic Information
  • Introduction to XAML
  • Overview of XAML
  • Why XAML?
  • XAML Layouts
  • Module Summary
  • XAML and WPF In Action
  • XAML in a Browser
  • Using XAML and code-behind in Desktop Applications
  • Module Summary
  • Unique Features of XAML
  • Resources
  • Styles and ControlTemplates
  • Module Summary
  • Glossary

اين ماژول به صورت آفلاين نيز قابل دريافت است (به حجم 44 مگابايت) اما پيش از آن بايد برنامه offline player آن‌را نصب نمود و طبق روال معمول سايت مايكروسافت، بهتر است از IE جهت مرور اين صفحات استفاده كرد.

۱۳۸۸/۰۶/۱۵

طول و عرض WPF


شايد بد نباشد اين فناوري را از ديدگاه مدت زماني كه بايد به آن تسلط پيدا كرد، بررسي نمود:



بله، مشكل در طول و عرض WPF بوده و مدت زمان يادگيري و تسلط كامل به آن، از فناوري‌هاي قبلي مطرح در دات نت فريم ورك بسيار بيشتر مي‌باشد. (تعداد كلاس‌هاي آن بيشتر از مجموع تعداد كلاس‌هاي نگارش 2 WinForms و ASP.Net است!)

در مقايسه با WinForms و ASP.Net هم موارد زير قابل تامل است:
ASP.NET 2.0 شامل 1098 public types و 1551 classes است.
WinForms 2.0 شامل 777 public types و 1500 classes مي‌باشد.
سيلورلايت 2 را هم كه در تصوير مشاهده مي‌كنيد. شامل 376 public types و 335 classes است.

ماخذ

۱۳۸۸/۰۱/۱۴

Expression Blend WPF Tutorial


دنبال يك سري ويديوي آموزشي Expression blend بودم كه آدرس زير را پيدا كردم:


امكان مشاهده‌ي رايگان آن‌ها موجود است، همچنين اگر برنامه‌ي internet download manager را نصب كنيد، هنگام گشودن هر صفحه، يك آيكون ذخيره سازي ويديوي مورد نظر نيز ظاهر مي‌شود كه به اين صورت مي‌توان تمام ويديوها را دانلود كرد.