اشتراکها
با گسترش استفاده از کامپیوتر در بسیاری از امور روزمره انسانها سازگار بودن برنامهها با سلیقه کاربران به یکی از نیازهای اصلی برنامههای کامپیوتری تبدیل شده است. بدون شک زبان و فرهنگ یکی از مهمترین عوامل در ایجاد ارتباط نزدیک بین برنامه و کاربر به شمار میرود و نقشی غیر قابل انکار در میزان موفقیت یک برنامه به عهده دارد. از این رو در این نوشته تلاش بر آن است تا یکی از سادهترین و در عین حال کاراترین راههای ممکن برای ایجاد برنامههای چند زبانه با استفاده از تکنولوژی 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 نامگذاری کنید و مقادیر مورد نظر را به آن اضافه نمایید.
دقت کنید که namespace ای که کلاس string در آن قرار دارد به فایل xaml اضافه شده است و پیشوند system به آن نسبت داده شده است.
با افزودن یک ResourceDictionary به پروژه، آن ResourceDictionary به MergedDictionary کلاس App اضافه میشود. بنابراین فایل App.xaml به شکل زیر خواهد بود:
برای اینکه بتوانیم محتوای Buttonهای موجود را به صورت داینامیک و در زمان اجرای برنامه، از داخل Resourceها بگیریم، از DynamicResource استفاده میکنیم.
بسیار خوب! اکنون باید شروع به ایجاد یک 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 به شکل زیر خواهد بود:
خروجی این پروژه یک فایل با نام Culture_fa-IR_Farsi.dll خواهد بود که حاوی یک ResourceDictionary برای زبان فارسی میباشد.
در ادامه میخواهیم راهکاری ارئه دهیم تا بتوان فایلهای dll مربوط به زبانها را در زمان اجرای برنامه اصلی، load کرده و نام زبانها را در داخل ComboBox ای که داریم نشان دهیم. سپس با انتخاب هر زبان در ComboBox، محتوای Buttonها بر اساس زبان انتخاب شده تغییر کند.
برای سهولت کار، نام فایلها را به گونه ای انتخاب کردیم که بتوانیم سادهتر به این هدف برسیم. نام هر فایل از سه بخش تشکیل شده است:
پوشه ای با نام Languages در کنار فایل اجرایی برنامه اصلی ایجاد کنید و فایل Culture_fa-IR_Farsi.dll را درون آن کپی کنید. تصمیم داریم همه dllهای مربوط به زبانها را داخل این پوشه قرار دهیم تا مدیریت آنها سادهتر شود.
برای مدیریت بهتر فایلهای مربوط به زبانها یک کلاس با نام CultureAssemblyModel خواهیم ساخت که هر instance از آن نشانگر یک فایل زبان خواهد بود. یک کلاس با این نام به پروژه اضافه کنید و propertyهای زیر را در آن تعریف نمایید:
اکنون باید لیست cultureهای موجود را از داخل پوشه languages خوانده و نام آنها را در ComboBox نمایش دهیم.
برای خواندن لیست cultureهای موجود، لیستی از CultureAssmeblyModelها ایجاد کرده و با استفاده از متد LoadCultureAssmeblies، آن را پر میکنیم.
پس از دریافت اطلاعات cultureهای موجود، زمان نمایش آنها در ComboBox است. این کار بسیار ساده است، تنها کافی است ItemsSource آن را با لیستی از CultureAssmeblyModelها که ساختیم، مقدار دهی کنیم.
البته لازم به ذکر است که برای نمایش فقط نام هر CultureAssemblyModel در ComboBox، باید ItemTemplate مناسبی برای ComboBox ایجاد کنیم. در مثال ما ItemTemplate به شکل زیر خواهد بود:
توجه داشته باشید که با وجود اینکه فقط نام را در ComboBox نشان میدهیم، اما باز هم هر آیتم از ComboBox یک instance از نوع CultureAssemblyModel میباشد.
در مرحله بعد، قرار است متدی بنویسیم که اطلاعات زبان انتخاب شده را گرفته و با جابجایی ResourceDictionary ها، زبان برنامه را تغییر دهیم.
متدی با نام LoadCulture در کلاس App ایجاد میکنیم که یک CultureAssemblyModel به عنوان ورودی دریافت کرده و ResourceDictionary داخل آن را load میکند و آن را با ResourceDictionary فعلی موجود در App.xaml جابجا مینماید.
با این کار، Button هایی که قبلا مقدار Content خود را از Resourceهای موجود دریافت میکردند، اکنون از Resourceهای جابجا شده خواهند گرفت و به این ترتیب زبان انتخاب شده بر روی برنامه اعمال میشود.
برای ارسال زبان انتخاب شده به این متد، باید رویداد SelectionChanged را برای ComboBox مدیریت کنیم:
کار انجام شد!
از مزیتهای این روش میتوان به WPF-Native بودن، سادگی در پیاده سازی، قابلیت load کردن هر زبان جدیدی در زمان اجرا بدون نیاز به کوچکترین تغییر در برنامه اصلی و همچنین پشتیبانی کامل از نمایش زبانهای مختلف از جمله فارسی اشاره کرد.
مروری بر روشهای موجود
همواره روشهای مختلفی برای پیاده سازی یک ایده در دنیای نرم افزار وجود دارد که هر روش را میتوان بر حسب نیاز مورد استفاده قرار داد. در برنامههای مبتنی بر 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>
در ادامه میخواهیم راهکاری ارئه دهیم تا بتوان فایلهای 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های موجود، لیستی از 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); } }
comboboxLanguages.ItemsSource = CultureAssemblies;
<ComboBox HorizontalAlignment="Left" Margin="10" VerticalAlignment="Top" MinWidth="100" Name="comboboxLanguages"> <ComboBox.ItemTemplate> <DataTemplate> <Label Content="{Binding DisplayText}"/> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox>
در مرحله بعد، قرار است متدی بنویسیم که اطلاعات زبان انتخاب شده را گرفته و با جابجایی 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); }
void comboboxLanguages_SelectionChanged(object sender, SelectionChangedEventArgs e) { var selectedCulture = (CultureAssemblyModel)comboboxLanguages.SelectedItem; App app = Application.Current as App; app.LoadCulture(selectedCulture); }
کار انجام شد!
از مزیتهای این روش میتوان به WPF-Native بودن، سادگی در پیاده سازی، قابلیت load کردن هر زبان جدیدی در زمان اجرا بدون نیاز به کوچکترین تغییر در برنامه اصلی و همچنین پشتیبانی کامل از نمایش زبانهای مختلف از جمله فارسی اشاره کرد.
VSCode یکی از بهترین ادیتورهایی است که از آن میتوان برای توسعهی برنامههای Angular استفاده کرد و در این بین افزونههای ویژهای جهت کار با Angular برای آن تدارک دیده شدهاند که در ادامه تعدادی از مهمترینهای آنها را بررسی میکنیم.
Angular Essentials
این افزونه گروهی از مهمترین افزونههای موجود را به صورت بسته بندی شده ارائه میدهد و با نصب آن، تعدادی از افزونههایی را که در ادامه نامبرده خواهند شد، به صورت یکجا و خودکار دریافت خواهید کرد.
Angular Language Service
نگارشهای اخیر Angular به همراه یک سرویس زبان نیز میباشند که به ادیتورهای مختلف این امکان را میدهد تا توسط این ویژگی بتوانند قابلیتهای ویرایشی بهتری را جهت برنامههای Angular ارائه کنند. برای مثال ویرایش مطلوب قالبهای کامپوننتهای Angular و استفادهی از Syntax خاص آن، موردی است که توسط هیچکدام از HTML ادیتورهای موجود پشتیبانی نمیشود. اکنون به کمک سرویس زبان Angular و افزونهی ویژهی آن برای VSCode که توسط تیم اصلی Angular توسعه یافتهاست، امکان ویرایش غنی قالبهای HTML ایی آن فراهم شدهاست. این افزونه یک چنین قابلیتهایی را فراهم میکند:
الف) AOT Diagnostic messages
اگر قالب HTML ایی مورد استفاده (چه به صورت inline و چه در یک فایل html مجزا) به خاصیتی تعریف نشده اشاره کند، بلافاصله خطای مرتبطی ظاهر خواهد شد:
ب) Completions lists یا همان Intellisense
ج) امکان Go to definition با کلیک راست بر روی خواص و متدهای ذکر شدهی در قالب.
د) Quick info که با نزدیک کردن اشارهگر ماوس به خاصیت یا متدی در صفحه، اطلاعات بیشتری را در مورد آن نمایش میدهد.
angular2-inline
علاوه بر افزونهی سرویس زبانهای Angular، این افزونه نیز قابلیت درک قالبهای inline کامپوننتها را داشته و به همراه syntax highlighting و همچنین Intellisense است.
Auto Import
حین کار با TypeScript، هر ماژولی که در صفحه ارجاعی داشته باشد، باید در ابتدای فایل جاری import شود. افزونهی Auto Import با بررسی ماژولهای موجود و فراهم آوردن Intellisense ایی بر اساس آنها، اینکار را سادهتر میکند:
بنابراین این افزونه صرفا مختص به Angular نیست و برای کارهای متداول TypeScript نیز بسیار مفید است.
TSLint
این افزونه ابزار TSLint را با VSCode یکپارچه میکند. بنابراین نیاز است پیش از نصب این افزونه، وابستگیهای ذیل را نیز به صورت سراسری نصب کرد:
کار TSLint انجام static code analysis است؛ چیزی شبیه به افزونههایی مانند ریشارپر در ویژوال استودیو که راهنماهایی را در مورد بهتر کردن کیفیت کدهای نوشته شده ارائه میدهد.
تعدادی از امکانات آنرا پس از نصب، با فشردن دکمهی F1 میتوان مشاهده کرد:
برای مثال تولید فایل tslint.json، امکان سفارشی سازی موارد بررسی شوندهی توسط این افزونه را فراهم میکند و اگر برنامهی خود را توسط Angular CLI ایجاد کردهاید، این فایل هم اکنون در ریشهی پروژه قرار دارد.
در مورد TSLint در مطلب «Angular CLI - قسمت دوم - ایجاد یک برنامهی جدید» بیشتر توضیح داده شدهاست و اینبار به کمک این افزونه، خطاهای یاد شده را دقیقا درون محیط ادیتور و به صورت خودکار و یکپارچهای مشاهده خواهید کرد.
Angular v4 TypeScript Snippets
سیستم کار VSCode مبتنی بر ایجاد فایلهای خالی است و مفهوم قالبهای از پیش آمادهی فایلها در آن وجود ندارد. اما با کمک Code Snippets میتوان این خلاء را پر کرد. افزونهی Angular v4 TypeScript Snippets دقیقا به همین منظور طراحی شدهاست و زمانیکه حروف -a یا -rx را در صفحه تایپ میکنید، منویی ظاهر خواهد شد که توسط آن میتوان قالب ابتدایی شروع به کار با انواع و اقسام جزئیات پروژههای Angular را تهیه کرد.
Path Intellisense
این افزونه مسیر فایلهای موجود را به صورت یک Intellisense ارائه میکند و به این صورت به سادگی میتوان مسیرهای اسکریپتها و یا شیوهنامهها را در ادیتور انتخاب و وارد کرد.
Angular Essentials
این افزونه گروهی از مهمترین افزونههای موجود را به صورت بسته بندی شده ارائه میدهد و با نصب آن، تعدادی از افزونههایی را که در ادامه نامبرده خواهند شد، به صورت یکجا و خودکار دریافت خواهید کرد.
Angular Language Service
نگارشهای اخیر Angular به همراه یک سرویس زبان نیز میباشند که به ادیتورهای مختلف این امکان را میدهد تا توسط این ویژگی بتوانند قابلیتهای ویرایشی بهتری را جهت برنامههای Angular ارائه کنند. برای مثال ویرایش مطلوب قالبهای کامپوننتهای Angular و استفادهی از Syntax خاص آن، موردی است که توسط هیچکدام از HTML ادیتورهای موجود پشتیبانی نمیشود. اکنون به کمک سرویس زبان Angular و افزونهی ویژهی آن برای VSCode که توسط تیم اصلی Angular توسعه یافتهاست، امکان ویرایش غنی قالبهای HTML ایی آن فراهم شدهاست. این افزونه یک چنین قابلیتهایی را فراهم میکند:
الف) AOT Diagnostic messages
اگر قالب HTML ایی مورد استفاده (چه به صورت inline و چه در یک فایل html مجزا) به خاصیتی تعریف نشده اشاره کند، بلافاصله خطای مرتبطی ظاهر خواهد شد:
ب) Completions lists یا همان Intellisense
ج) امکان Go to definition با کلیک راست بر روی خواص و متدهای ذکر شدهی در قالب.
د) Quick info که با نزدیک کردن اشارهگر ماوس به خاصیت یا متدی در صفحه، اطلاعات بیشتری را در مورد آن نمایش میدهد.
angular2-inline
علاوه بر افزونهی سرویس زبانهای Angular، این افزونه نیز قابلیت درک قالبهای inline کامپوننتها را داشته و به همراه syntax highlighting و همچنین Intellisense است.
Auto Import
حین کار با TypeScript، هر ماژولی که در صفحه ارجاعی داشته باشد، باید در ابتدای فایل جاری import شود. افزونهی Auto Import با بررسی ماژولهای موجود و فراهم آوردن Intellisense ایی بر اساس آنها، اینکار را سادهتر میکند:
بنابراین این افزونه صرفا مختص به Angular نیست و برای کارهای متداول TypeScript نیز بسیار مفید است.
TSLint
این افزونه ابزار TSLint را با VSCode یکپارچه میکند. بنابراین نیاز است پیش از نصب این افزونه، وابستگیهای ذیل را نیز به صورت سراسری نصب کرد:
> npm install -g tslint typescript
تعدادی از امکانات آنرا پس از نصب، با فشردن دکمهی F1 میتوان مشاهده کرد:
برای مثال تولید فایل tslint.json، امکان سفارشی سازی موارد بررسی شوندهی توسط این افزونه را فراهم میکند و اگر برنامهی خود را توسط Angular CLI ایجاد کردهاید، این فایل هم اکنون در ریشهی پروژه قرار دارد.
در مورد TSLint در مطلب «Angular CLI - قسمت دوم - ایجاد یک برنامهی جدید» بیشتر توضیح داده شدهاست و اینبار به کمک این افزونه، خطاهای یاد شده را دقیقا درون محیط ادیتور و به صورت خودکار و یکپارچهای مشاهده خواهید کرد.
Angular v4 TypeScript Snippets
سیستم کار VSCode مبتنی بر ایجاد فایلهای خالی است و مفهوم قالبهای از پیش آمادهی فایلها در آن وجود ندارد. اما با کمک Code Snippets میتوان این خلاء را پر کرد. افزونهی Angular v4 TypeScript Snippets دقیقا به همین منظور طراحی شدهاست و زمانیکه حروف -a یا -rx را در صفحه تایپ میکنید، منویی ظاهر خواهد شد که توسط آن میتوان قالب ابتدایی شروع به کار با انواع و اقسام جزئیات پروژههای Angular را تهیه کرد.
Path Intellisense
این افزونه مسیر فایلهای موجود را به صورت یک Intellisense ارائه میکند و به این صورت به سادگی میتوان مسیرهای اسکریپتها و یا شیوهنامهها را در ادیتور انتخاب و وارد کرد.
اشتراکها
سری توسعهی برنامههای #C در VS Code
C# and .NET Development in VS Code for Beginners
Writing C# applications in Visual Studio Code has never been easier! We recently introduced the new C# Dev Kit extension In this video series, you’ll learn how to get started writing, debugging, testing, and running your C# applications productively in VS Code using the new C# Dev Kit extension.
اشتراکها
ASP.NET Core و سازمانها
However, it is clear that Microsoft’s future direction is in the Core space . Yes, the last update to the full .NET framework did include improvements for ASP.NET and WebForms, but clearly the future innovation and hard work will be in the new core frameworks like .NET core, ASP.NET Core, Entity Framework core, and whatever other cores come along in the future.
Intro to Windows Workflow Foundation (Part 1 of 7): Workflow in Windows Applications (Level 100)
This webcast is a code-focused introduction to developing workflow-enabled Microsoft Windows platform applications. We cover the basics of developing, designing, and debugging workflow solutions. Gain the knowledge and insight you need to be confident choosing workflow for everyday applications.
Intro to Windows Workflow Foundation (Part 2 of 7): Simple Human Workflow Using E-mail (Level 200)
Have you thought about how you might apply the workflow concept to e-mail? In this webcast New Zealand based regional director, Chris Auld, leads attendees through a simple worked example of the use of SMTP e-mail as part of a workflow solution. Chris demonstrates how to create custom activities to query Active Directory to retrieve user data, send e-mail, and wait for e-mail responses to continue the workflow process. This code-intensive session gives users taking their first steps with workflow a good grounding in some of the key extensibility concepts.
Intro to Windows Workflow Foundation (Part 3 of 7): Hosting and Communications Options in Workflow Scenarios (Level 300)
The session looks at options for hosting workflow applications. We cover managing events, instance tracking, and persistence, and provide a close look at the simple communications mechanisms that are available for you to use in your workflow applications.
Intro to Windows Workflow Foundation (Part 4 of 7): Workflow, Messaging, and Services: Developing Distributed Applications with Workflows (Level 300)
Web service technologies have typically taken a "do-it-yourself" approach to maintaining the interoperation state of services. Using workflow, developers now have tools that allow them to describe the long-running state of their services and delegate much of the state management to the underlying platform. Managing this state correctly becomes even more challenging in applications that coordinate work across multiple services either within an organization or at an Internet scale. This session looks at how developers who use either Microsoft ASMX or Microsoft's framework for building service-oriented applications, code-named "Indigo", can create workflow-oriented applications that are both faster to write and more manageable and flexible once deployed.
Intro to Windows Workflow Foundation (Part 5 of 7): Developing Event Driven State Machine Workflows (Level 300)
State machines used to be something that you had to first draw on paper and then implement in code. This session shows how to use technologies to create event-driven workflows and how to apply this to a typical programming problem. We introduce the concept of a flexible process and show how this can help with modeling real-world processes using state and sequential workflow. Plenty of coding is included to illustrate how you can seamlessly merge state machine design and your code.
Intro to Windows Workflow Foundation (Part 6 of 7): Extending Workflow Capabilities with Custom Activities (Level 300)
It is helpful to think of activities as controls within a workflow, similar to controls used with Microsoft ASP.NET Pages or Microsoft Windows Forms. You can use activities to encapsulate execution logic, communicate with the host and decompose a workflow into reusable components. This session examines the simple process of creating custom activities. If you want to expose activities to other developers designing workflows, you are likely to find this session valuable.
Intro to Windows Workflow Foundation (Part 7 of 7): Developing Rules Driven Workflows (Level 300)
Rules can be a powerful business tool when combined with workflow. In this session, learn how to develop more advanced activities that support the modeling of rich business behavior such as human workflow. Understand when to use rules for business logic, and see how rule policies allow for the description of sophisticated behavior in an integrated and flexible way. This session gives you an interesting insight into the power of using workflow at the core of a line of business application.
This webcast is a code-focused introduction to developing workflow-enabled Microsoft Windows platform applications. We cover the basics of developing, designing, and debugging workflow solutions. Gain the knowledge and insight you need to be confident choosing workflow for everyday applications.
Intro to Windows Workflow Foundation (Part 2 of 7): Simple Human Workflow Using E-mail (Level 200)
Have you thought about how you might apply the workflow concept to e-mail? In this webcast New Zealand based regional director, Chris Auld, leads attendees through a simple worked example of the use of SMTP e-mail as part of a workflow solution. Chris demonstrates how to create custom activities to query Active Directory to retrieve user data, send e-mail, and wait for e-mail responses to continue the workflow process. This code-intensive session gives users taking their first steps with workflow a good grounding in some of the key extensibility concepts.
Intro to Windows Workflow Foundation (Part 3 of 7): Hosting and Communications Options in Workflow Scenarios (Level 300)
The session looks at options for hosting workflow applications. We cover managing events, instance tracking, and persistence, and provide a close look at the simple communications mechanisms that are available for you to use in your workflow applications.
Intro to Windows Workflow Foundation (Part 4 of 7): Workflow, Messaging, and Services: Developing Distributed Applications with Workflows (Level 300)
Web service technologies have typically taken a "do-it-yourself" approach to maintaining the interoperation state of services. Using workflow, developers now have tools that allow them to describe the long-running state of their services and delegate much of the state management to the underlying platform. Managing this state correctly becomes even more challenging in applications that coordinate work across multiple services either within an organization or at an Internet scale. This session looks at how developers who use either Microsoft ASMX or Microsoft's framework for building service-oriented applications, code-named "Indigo", can create workflow-oriented applications that are both faster to write and more manageable and flexible once deployed.
Intro to Windows Workflow Foundation (Part 5 of 7): Developing Event Driven State Machine Workflows (Level 300)
State machines used to be something that you had to first draw on paper and then implement in code. This session shows how to use technologies to create event-driven workflows and how to apply this to a typical programming problem. We introduce the concept of a flexible process and show how this can help with modeling real-world processes using state and sequential workflow. Plenty of coding is included to illustrate how you can seamlessly merge state machine design and your code.
Intro to Windows Workflow Foundation (Part 6 of 7): Extending Workflow Capabilities with Custom Activities (Level 300)
It is helpful to think of activities as controls within a workflow, similar to controls used with Microsoft ASP.NET Pages or Microsoft Windows Forms. You can use activities to encapsulate execution logic, communicate with the host and decompose a workflow into reusable components. This session examines the simple process of creating custom activities. If you want to expose activities to other developers designing workflows, you are likely to find this session valuable.
Intro to Windows Workflow Foundation (Part 7 of 7): Developing Rules Driven Workflows (Level 300)
Rules can be a powerful business tool when combined with workflow. In this session, learn how to develop more advanced activities that support the modeling of rich business behavior such as human workflow. Understand when to use rules for business logic, and see how rule policies allow for the description of sophisticated behavior in an integrated and flexible way. This session gives you an interesting insight into the power of using workflow at the core of a line of business application.
در قسمت قبل، سرویس و کامپوننت دریافت اطلاعات اتاقها را از Web API برنامه، تکمیل کردیم. در این قسمت با استفاده از اطلاعات مهیا شده، UI آنرا نیز تکمیل خواهیم کرد.
نمایش منتظر بمانید در حین بارگذاری اولیهی کامپوننت
کامپوننتهایی که قرار است اطلاعات را از یک Web API دریافت کنند، مدتی باید منتظر بمانند تا عملیات رفت و برگشت به سرور، تکمیل شود. در این بین میتوان یک loading را به کاربر نمایش داد:
- فیلد Rooms را در قسمت قبل، در متد LoadRooms، از Web API دریافت و مقدار دهی کردیم. تا زمان تکمیل عملیات این متد، فیلد Rooms، فاقد عضوی است؛ بنابراین قسمت else شرط فوق اجرا میشود که یک loading را نمایش خواهد داد. مابقی UI برنامه در قسمت if آن قرار میگیرد.
- هر زمانیکه کار روال رویدادگردان OnInitializedAsync به پایان برسد (که شامل اجرای متد LoadRooms نیز هست)، سبب فراخوانی خودکار StateHasChanged میشود. این فراخوانی، UI را مجددا رندر میکند. به همین جهت است که پس از پایان کار، محتوای if، رندر خواهد شد.
- از این loading سفارشی که در میانهی صفحه نمایش داده میشود، میتوان در فایل wwwroot\index.html نیز بجای loading پیشفرض آن استفاده کرد:
افزودن خواصی جدید به HotelRoomDTO
میخواهیم به کاربر امکان تغییر تعداد روزهای اقامت را بدهیم. این انتخاب باید در لیست اتاقهای نمایش داده شده، با تغییر تعداد روزهای اقامت (TotalDays) و هزینهی جدید متناظر با آن (TotalAmount)، منعکس شود. به همین جهت این خواص را به HotelRoomDTO، اضافه میکنیم:
محاسبات مربوط به این خواص را هم میتوان در همان کامپوننت HotelRooms.razor، پس از بارگذاری لیست اتاقها از Web API، انجام داد:
افزودن امکان تغییر تعداد روزهای اقامت در همان صفحهی نمایش لیست اتاقها
همانطور که در تصویر فوق هم مشاهده میکنید، میخواهیم در این صفحه نیز کاربر بتواند زمان شروع اقامت و مدت مدنظر را تغییر دهد. به همین جهت، HomeModel ای را که در قسمت قبل از Local Storage دریافت کردیم، به فرم زیر متصل میکنیم تا اجزای آن در این فرم، نمایش داده شده و قابل تغییر شوند:
نکتهی مهم این فرم، مدیریت قسمت کلیک بر روی دکمهی Update است که سبب فراخوانی روال رویدادگران OnValidSubmit میشود:
در ابتدای عملیات، فیلد جدید IsProcessing را به true تنظیم میکنیم. این مورد سبب میشود تا برچسب دکمهی Update به Processing... تغییر کند. سپس فیلد محاسباتی EndDate را بر اساس اطلاعات جدید فرم، به روز رسانی میکنیم. در ادامه، مجددا این اطلاعات را در Local Storage ذخیره سازی کرده و کار LoadRoomsAsync را انجام میدهیم که به همراه آن، خواص جدید تعداد روزها و هزینهی اقامت نیز مجددا محاسبه میشوند. در آخر برچسب دکمهی Update را به حالت اول باز میگردانیم.
سؤال: زمانیکه IsProcessing به true تنظیم میشود که هنوز کار متد رویدادگردان SaveBookingInfo به پایان نرسیدهاست و فراخوانی خودکار StateHasChanged در پایان متدهای رویدادگردان صورت میگیرد. پس چطور است که سبب رندر مجدد UI و تغییر برچسب دکمهی Update میشود؟
پاسخ به این سؤال را در قسمت 6 این سری با بررسی چرخهی حیات کامپوننتها، مشاهده کردیم:
«البته متدهای رویدادگردان async، دوبار سبب فراخوانی ضمنی StateHasChanged میشوند؛ یکبار زمانیکه قسمت sync متد به پایان میرسد (در این مثال یعنی تا قبل از اولین await نوشته شده) و یکبار هم زمانیکه کار فراخوانی کلی متد به پایان خواهد رسید»
نمایش لیست اتاقها
نمایش لیست اتاقها مطابق تصویر فوق، دو قسمت اصلی را دارد:
الف) نمایش لیست تصاویر منتسب به یک اتاق، توسط کامپوننت carousel بوت استرپ
- هرچند این قطعه کد، طولانی به نظر میرسد اما قسمتهای مختلف آن صرفا بر اساس مستندات سایت بوت استرپ، جهت تشکیل ساختار ابتدایی و استاندارد کامپوننت carousel، تهیه شدهاند.
- سپس در حلقهای که برای نمایش لیست اتاقها تهیه کردهایم، قسمتهای مختلف carousel را تکمیل میکنیم که در اینجا نیاز به ایندکس تصاویر، لیست تصاویر و یک Id منحصربفرد برای این carousel خاص را دارد تا بتوان چندین وهله از آنرا در صفحه قرار داد که این id را بر اساس Id اتاق مشخص کردهایم.
دو نکته:
- در این مثال برای تعریف لینک به تصاویر، کد زیر را مشاهده میکنید:
و این ImagesBaseAddress، به صورت زیر تعریف شده که همان آدرس برنامهی blazor server ای است که مشخصات اتاقها و تصاویر را ثبت میکند:
بنابراین اگر میخواهید تصاویر را هم مشاهده کنید، باید برنامهی مجزای blazor server این سری نیز در حال اجرا باشد.
- کامپوننت carousel برای اجرا، نیاز به فایل lib/bootstrap/dist/js/bootstrap.bundle.min.js را نیز دارد. به همین جهت مدخل اسکریپت آنرا باید به فایل wwwroot\index.html اضافه کرد.
ب) نمایش جزئیات نام و هزینهی اتاق
قسمت دوم حلقهی foreach نمایش لیست اتاقها، جهت نمایش جزئیات هر اتاق تعریف شدهاست:
- در این مثال از MarkupString استفاده شده تا بتوان یک محتوای HTML ای را در صفحه نمایش داد.
- هر اتاق نمایش داده شده، لینکی را به صفحهی خاص خودش نیز دارد که آنرا در قسمت بعدی تکمیل میکنیم.
- در اینجا TotalAmount و TotalDays محاسباتی و قابل تغییر بر اساس انتخاب کاربر نیز درج شدهاند.
یک تمرین: در برنامهی Blazor Server، سرویسی را جهت درج مشخصات امکانات رفاهی هتل تهیه کردیم. این امکانات رفاهی را از طریق Web API برنامه دریافت و سپس در برنامهی سمت کلاینت نمایش دهید.
بنابراین تکمیل این تمرین شامل تهیهی موارد زیر است که کدنویسی آن، با دو قسمت اخیر این سری دقیقا یکی است و نکتهی جدیدی را به همراه ندارد (و کدهای کامل آن را از انتهای بحث میتوانید دریافت کنید):
- تهیهی HotelAmenityController در پروژهی Web API که به کمک IAmenityService، لیست امکانات رفاهی را بازگشت میدهد.
- تهیهی ClientHotelAmenityService در پروژهی WASM که همانند ClientHotelRoomService قسمت قبل ، از Web API، لیست HotelAmenityDTOها را دریافت میکند.
- ثبت سرویس جدید ClientHotelAmenityService در Program.cs.
- در آخر حلقهای را بر روی لیست HotelAmenityDTO دریافتی از ClientHotelRoomService در کامپوننت Index.razor تشکیل داده و آنها را نمایش میدهیم.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-28.zip
نمایش منتظر بمانید در حین بارگذاری اولیهی کامپوننت
کامپوننتهایی که قرار است اطلاعات را از یک Web API دریافت کنند، مدتی باید منتظر بمانند تا عملیات رفت و برگشت به سرور، تکمیل شود. در این بین میتوان یک loading را به کاربر نمایش داد:
@page "/hotel/rooms" @if (Rooms is not null && Rooms.Any()) { } else { <div style="position:fixed;top:50%;left:50%;margin-top:-50px;margin-left:-100px;"> <img src="images/loader.gif" /> </div> } @code { IEnumerable<HotelRoomDTO> Rooms = new List<HotelRoomDTO>(); // ... }
- هر زمانیکه کار روال رویدادگردان OnInitializedAsync به پایان برسد (که شامل اجرای متد LoadRooms نیز هست)، سبب فراخوانی خودکار StateHasChanged میشود. این فراخوانی، UI را مجددا رندر میکند. به همین جهت است که پس از پایان کار، محتوای if، رندر خواهد شد.
- از این loading سفارشی که در میانهی صفحه نمایش داده میشود، میتوان در فایل wwwroot\index.html نیز بجای loading پیشفرض آن استفاده کرد:
<body> <div id="app"> <div style=" position: fixed; top: 50%; left: 50%; margin-top: -50px; margin-left: -100px; " > <img src="images/ajax-loader.gif" /> </div> </div>
افزودن خواصی جدید به HotelRoomDTO
میخواهیم به کاربر امکان تغییر تعداد روزهای اقامت را بدهیم. این انتخاب باید در لیست اتاقهای نمایش داده شده، با تغییر تعداد روزهای اقامت (TotalDays) و هزینهی جدید متناظر با آن (TotalAmount)، منعکس شود. به همین جهت این خواص را به HotelRoomDTO، اضافه میکنیم:
namespace BlazorServer.Models { public class HotelRoomDTO { // ... public int TotalDays { get; set; } public decimal TotalAmount { get; set; } } }
@code { HomeVM HomeModel = new HomeVM(); // ... private async Task LoadRoomsAsync() { Rooms = await HotelRoomService.GetHotelRoomsAsync(HomeModel.StartDate, HomeModel.EndDate); foreach (var room in Rooms) { room.TotalAmount = room.RegularRate * HomeModel.NoOfNights; room.TotalDays = HomeModel.NoOfNights; } } }
افزودن امکان تغییر تعداد روزهای اقامت در همان صفحهی نمایش لیست اتاقها
همانطور که در تصویر فوق هم مشاهده میکنید، میخواهیم در این صفحه نیز کاربر بتواند زمان شروع اقامت و مدت مدنظر را تغییر دهد. به همین جهت، HomeModel ای را که در قسمت قبل از Local Storage دریافت کردیم، به فرم زیر متصل میکنیم تا اجزای آن در این فرم، نمایش داده شده و قابل تغییر شوند:
@if (Rooms is not null && Rooms.Any()) { <EditForm Model="HomeModel" OnValidSubmit="SaveBookingInfo" class="bg-light"> <div class="pt-3 pb-2 px-5 mx-1 mx-md-0 bg-secondary"> <DataAnnotationsValidator /> <div class="row px-3 mx-3"> <div class="col-6 col-md-4"> <div class="form-group"> <label class="text-warning">Check in Date</label> <InputDate @bind-Value="HomeModel.StartDate" class="form-control" /> </div> </div> <div class="col-6 col-md-4"> <div class="form-group"> <label class="text-warning">Check Out Date</label> <input @bind="HomeModel.EndDate" disabled="disabled" readonly="readonly" type="date" class="form-control" /> </div> </div> <div class=" col-4 col-md-2"> <div class="form-group"> <label class="text-warning">No. of nights</label> <select class="form-control" @bind="HomeModel.NoOfNights"> <option value="Select" selected disabled="disabled">(Select No. Of Nights)</option> @for (var i = 1; i <= 10; i++) { <option value="@i">@i</option> } </select> </div> </div> <div class="col-8 col-md-2"> <div class="form-group" style="margin-top: 1.9rem !important;"> @if (IsProcessing) { <button class="btn btn-success btn-block form-control"> <i class="fa fa-spin fa-spinner"></i>Processing... </button> } else { <input type="submit" value="Update" class="btn btn-success btn-block form-control" /> } </div> </div> </div> </div> </EditForm>
@code { bool IsProcessing; // ... private async Task SaveBookingInfo() { IsProcessing = true; HomeModel.EndDate = HomeModel.StartDate.AddDays(HomeModel.NoOfNights); await LocalStorage.SetItemAsync(ConstantKeys.LocalInitialBooking, HomeModel); await LoadRoomsAsync(); IsProcessing = false; } }
سؤال: زمانیکه IsProcessing به true تنظیم میشود که هنوز کار متد رویدادگردان SaveBookingInfo به پایان نرسیدهاست و فراخوانی خودکار StateHasChanged در پایان متدهای رویدادگردان صورت میگیرد. پس چطور است که سبب رندر مجدد UI و تغییر برچسب دکمهی Update میشود؟
پاسخ به این سؤال را در قسمت 6 این سری با بررسی چرخهی حیات کامپوننتها، مشاهده کردیم:
«البته متدهای رویدادگردان async، دوبار سبب فراخوانی ضمنی StateHasChanged میشوند؛ یکبار زمانیکه قسمت sync متد به پایان میرسد (در این مثال یعنی تا قبل از اولین await نوشته شده) و یکبار هم زمانیکه کار فراخوانی کلی متد به پایان خواهد رسید»
نمایش لیست اتاقها
نمایش لیست اتاقها مطابق تصویر فوق، دو قسمت اصلی را دارد:
الف) نمایش لیست تصاویر منتسب به یک اتاق، توسط کامپوننت carousel بوت استرپ
@foreach (var room in Rooms) { <div class="row p-2 my-3 " style="border-radius:20px; border: 1px solid gray"> <div class="col-12 col-lg-3 col-md-4"> <div id="carouselExampleIndicators_@room.Id" class="carousel slide mb-4 m-md-3 m-0 pt-3 pt-md-0" data-ride="carousel"> <ol class="carousel-indicators"> @{ int imageIndex = 0; int innerImageIndex = 0; } @foreach (var image in room.HotelRoomImages) { if (imageIndex == 0) { <li data-target="#carouselExampleIndicators_@room.Id" data-slide-to="@imageIndex" class="active"></li> } else { <li data-target="#carouselExampleIndicators_@room.Id" data-slide-to="@imageIndex"></li> } imageIndex++; } </ol> <div class="carousel-inner"> @foreach (var image in room.HotelRoomImages) { var imageUrl = $"{ImagesBaseAddress}/{image.RoomImageUrl}"; if (innerImageIndex == 0) { <div class="carousel-item active"> <img class="d-block w-100" style="border-radius:20px;" src="@imageUrl" alt="First slide"> </div> } else { <div class="carousel-item"> <img class="d-block w-100" style="border-radius:20px;" src="@imageUrl" alt="First slide"> </div> } innerImageIndex++; } </div> <a class="carousel-control-prev" href="#carouselExampleIndicators_@room.Id" role="button" data-slide="prev"> <span class="carousel-control-prev-icon" aria-hidden="true"></span> <span class="sr-only">Previous</span> </a> <a class="carousel-control-next" href="#carouselExampleIndicators_@room.Id" role="button" data-slide="next"> <span class="carousel-control-next-icon" aria-hidden="true"></span> <span class="sr-only">Next</span> </a> </div> </div> }
- سپس در حلقهای که برای نمایش لیست اتاقها تهیه کردهایم، قسمتهای مختلف carousel را تکمیل میکنیم که در اینجا نیاز به ایندکس تصاویر، لیست تصاویر و یک Id منحصربفرد برای این carousel خاص را دارد تا بتوان چندین وهله از آنرا در صفحه قرار داد که این id را بر اساس Id اتاق مشخص کردهایم.
دو نکته:
- در این مثال برای تعریف لینک به تصاویر، کد زیر را مشاهده میکنید:
var imageUrl = $"{ImagesBaseAddress}/{image.RoomImageUrl}";
@code { string ImagesBaseAddress = "https://localhost:5006";
- کامپوننت carousel برای اجرا، نیاز به فایل lib/bootstrap/dist/js/bootstrap.bundle.min.js را نیز دارد. به همین جهت مدخل اسکریپت آنرا باید به فایل wwwroot\index.html اضافه کرد.
ب) نمایش جزئیات نام و هزینهی اتاق
قسمت دوم حلقهی foreach نمایش لیست اتاقها، جهت نمایش جزئیات هر اتاق تعریف شدهاست:
@foreach (var room in Rooms) { <div class="col-12 col-lg-9 col-md-8"> <div class="row pt-3"> <div class="col-12 col-lg-8"> <p class="card-title text-warning" style="font-size:xx-large">@room.Name</p> <p class="card-text"> @((MarkupString)room.Details) </p> </div> <div class="col-12 col-lg-4"> <div class="row pb-3 pt-2"> <div class="col-12 col-lg-11 offset-lg-1"> <a href="@($"hotel/room-details/{room.Id}")" class="btn btn-success btn-block">Book</a> </div> </div> <div class="row "> <div class="col-12 pb-5"> <span class="float-right"> <span class="float-right">Occupancy : @room.Occupancy adults </span><br /> <span class="float-right pt-1">Room Size : @room.SqFt sqft</span><br /> <h4 class="text-warning font-weight-bold pt-4"> <span style="border-bottom:1px solid #ff6a00"> @room.TotalAmount.ToString("#,#.00;(#,#.00#)") </span> </h4> <span class="float-right">Cost for @room.TotalDays nights</span> </span> </div> </div> </div> </div> </div> </div> }
- هر اتاق نمایش داده شده، لینکی را به صفحهی خاص خودش نیز دارد که آنرا در قسمت بعدی تکمیل میکنیم.
- در اینجا TotalAmount و TotalDays محاسباتی و قابل تغییر بر اساس انتخاب کاربر نیز درج شدهاند.
یک تمرین: در برنامهی Blazor Server، سرویسی را جهت درج مشخصات امکانات رفاهی هتل تهیه کردیم. این امکانات رفاهی را از طریق Web API برنامه دریافت و سپس در برنامهی سمت کلاینت نمایش دهید.
بنابراین تکمیل این تمرین شامل تهیهی موارد زیر است که کدنویسی آن، با دو قسمت اخیر این سری دقیقا یکی است و نکتهی جدیدی را به همراه ندارد (و کدهای کامل آن را از انتهای بحث میتوانید دریافت کنید):
- تهیهی HotelAmenityController در پروژهی Web API که به کمک IAmenityService، لیست امکانات رفاهی را بازگشت میدهد.
- تهیهی ClientHotelAmenityService در پروژهی WASM که همانند ClientHotelRoomService قسمت قبل ، از Web API، لیست HotelAmenityDTOها را دریافت میکند.
- ثبت سرویس جدید ClientHotelAmenityService در Program.cs.
- در آخر حلقهای را بر روی لیست HotelAmenityDTO دریافتی از ClientHotelRoomService در کامپوننت Index.razor تشکیل داده و آنها را نمایش میدهیم.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-28.zip
اشتراکها
ایجاد Responsive Layered Slider
in this tutorial you will lean how to create
a responsive layered slider. The slider will have multiple slides, each
slide will have multiple layers which can have a different fade in
effect.
The entire slider works by using just a JavaScript document and you
don't have to attach any CSS style sheets to use it. Each layer will
have 18 possible effects which you can use to your liking. All
animations are created by inserting CSS code into the page using
JavaScript. Demo