در ادامه مطلب پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 4# به تشریح مابقی کلاسهای برنامه میپردازیم.
در این پست به شرح کلاس Rectangle جهت رسم مستطیل و Square جهت رسم مربع میپردازیم
کلاس Rectangle از کلاس پایه طراحی شده در ^ ارث بری دارد. این کلاس ساده بوده و تنها شامل یک سازنده و متد ترسیم شی مستطیل میباشد.
کلاس بعدی کلاس Square میباشد، که از کلاس بالا (Rectangle) ارث بری داشته است، کدهای این کلاس را در زیر مشاهده میکنید.
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 1#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 2#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 3#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 4#
در این پست به شرح کلاس Rectangle جهت رسم مستطیل و Square جهت رسم مربع میپردازیم
using System.Drawing; namespace PWS.ObjectOrientedPaint.Models { /// <summary> /// Rectangle /// </summary> public class Rectangle : Shape { #region Constructors (2) /// <summary> /// Initializes a new instance of the <see cref="Rectangle" /> class. /// </summary> /// <param name="startPoint">The start point.</param> /// <param name="endPoint">The end point.</param> /// <param name="zIndex">Index of the z.</param> /// <param name="foreColor">Color of the fore.</param> /// <param name="thickness">The thickness.</param> /// <param name="isFill">if set to <c>true</c> [is fill].</param> /// <param name="backgroundColor">Color of the background.</param> public Rectangle(PointF startPoint, PointF endPoint, int zIndex, Color foreColor, byte thickness, bool isFill, Color backgroundColor) : base(startPoint, endPoint, zIndex, foreColor, thickness, isFill, backgroundColor) { ShapeType = ShapeType.Rectangle; } /// <summary> /// Initializes a new instance of the <see cref="Rectangle" /> class. /// </summary> public Rectangle() { ShapeType = ShapeType.Rectangle; } #endregion Constructors #region Methods (1) // Public Methods (1) /// <summary> /// Draws the specified g. /// </summary> /// <param name="g">The g.</param> public override void Draw(Graphics g) { if (IsFill) g.FillRectangle(BackgroundBrush, StartPoint.X, StartPoint.Y, Width, Height); g.DrawRectangle(Pen, StartPoint.X, StartPoint.Y, Width, Height); base.Draw(g); } #endregion Methods } }
کلاس بعدی کلاس Square میباشد، که از کلاس بالا (Rectangle) ارث بری داشته است، کدهای این کلاس را در زیر مشاهده میکنید.
using System; using System.Drawing; namespace PWS.ObjectOrientedPaint.Models { /// <summary> /// Square /// </summary> public class Square : Rectangle { #region Constructors (2) /// <summary> /// Initializes a new instance of the <see cref="Square" /> class. /// </summary> /// <param name="startPoint">The start point.</param> /// <param name="endPoint">The end point.</param> /// <param name="zIndex">Index of the z.</param> /// <param name="foreColor">Color of the fore.</param> /// <param name="thickness">The thickness.</param> /// <param name="isFill">if set to <c>true</c> [is fill].</param> /// <param name="backgroundColor">Color of the background.</param> public Square(PointF startPoint, PointF endPoint, int zIndex, Color foreColor, byte thickness, bool isFill, Color backgroundColor) { float x = 0, y = 0; float width = Math.Abs(endPoint.X - startPoint.X); float height = Math.Abs(endPoint.Y - startPoint.Y); if (startPoint.X <= endPoint.X && startPoint.Y <= endPoint.Y) { x = startPoint.X; y = startPoint.Y; } else if (startPoint.X >= endPoint.X && startPoint.Y >= endPoint.Y) { x = endPoint.X; y = endPoint.Y; } else if (startPoint.X >= endPoint.X && startPoint.Y <= endPoint.Y) { x = endPoint.X; y = startPoint.Y; } else if (startPoint.X <= endPoint.X && startPoint.Y >= endPoint.Y) { x = startPoint.X; y = endPoint.Y; } StartPoint = new PointF(x, y); var side = Math.Max(width, height); EndPoint = new PointF(x+side, y+side); ShapeType = ShapeType.Square; Zindex = zIndex; ForeColor = foreColor; Thickness = thickness; BackgroundColor = backgroundColor; IsFill = isFill; } /// <summary> /// Initializes a new instance of the <see cref="Square" /> class. /// </summary> public Square() { ShapeType = ShapeType.Square; } #endregion Constructors } }
این کلاس شامل دو سازنده میباشد که سازنده دوم فقط نوع شی را تعیین میکند و بقیه کارهای آن مانند مستطیل است، در واقع میتوان از یک دیدگاه گفت که مربع یک مستطیل است که اندازه طول و عرض آن یکسان است. در سازنده اول (نحوه ترسیم شکل) ابتدا نقاط ابتدا و انتهای رسم شکل تعیین شده و سپس با توجه به پارامترهای محاسبه شده نوع شی جهت ترسیم و دیگر خصوصیات کلاس مقدار دهی میشود، با این تفاوت که در نقطه EndPoint طول و عرض مربع برابر با بزرگترین مقدار طول و عرض وارد شده در سازنده کلاس تعیین شده و مربع شکل میگیرد. مابقی متدهای ترسیم و ... طبق کلاس پایه مستطیل و Shape تعیین میشود.
مطالب قبل:پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 1#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 2#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 3#
پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 4#
در کلاس ScheduledTasksCoordinator یک متد Remove از لیست اضافه کن:
public void RemoveTask(string taskName) { lock (_syncLock) { var task = _tasks.FirstOrDefault( x => x.Name == taskName); if (task != null) { _tasks.Remove(task); } } }
Theme
برای اینکه بتوانیم ظاهر گرافیکی layoutها را کنترل نماییم، از Theme که مجموعهای از styleهای گرافیکی میباشد، استفاده میکنیم. در اندروید مجموعهای از تمهای از پیش ساخته شده که به آنها Builtin Theme نیز گفته میشود میتوانیم استفاده کنیم. تمها ظاهر گرافیکی کلیه کنترلهای Layout را با نامهای زیر، کنترل میکنند:
اگر بخواهیم از styleهای از پیش طراحی شدهی اندروید استفاده نماییم، ابتدا میتوانیم در صفحهی layout در زامارین، style مربوطه را از بخش Theme استفاده کرده و نتیجه را مشاهده کنیم. ولی تغییر style سبب تغییر layout در زمان اجرا نمیشود. هرگاه بخواهیم از styleهای استاندارد یا builtin اندروید استفاده نماییم، در Activity توسط خصوصیت Theme با فرمت:
تم را بهعنوان تم داخلی وسپس نام کامل تم را مینویسیم.
CustomTheme
در طراحی فرمها ممکن است بخواهیم از یک استایل خاص builtin استفاده کنیم؛ ولی ممکن است بعضی از استایلهای آن را استفاده نکنیم، مانند تمی که از قبل استفاده شدهاست، از روش زیر استفاده میکنیم:
- بر روی دایرکتوری value راست کلیک میکنیم. گزینه add new item را انتخاب و یک فایل xmlfile را با نام style ایجاد میکنیم.
- styleهای جدید منابع application میباشند که در بخش resource از آنها استفاده میکنیم. هر استایل جدید را توسط Style Tag مشخص میکنیم و در خصوصیت Name، نام Style را مشخص میکنیم.
میتوانیم استایل سفارشی شده خودمان را مشخص کنیم.
Navigation Menu
ساخت Menu
منظور همان اضافه کردن کتابخانهها است.
قدم دوم:
ابتدا باید گزینههای منو را در یک فایل xml تعریف نمود. هر گزینهی آن از دو بخش متن اصلی منو و ID منو تشکیل شدهاست.
خصوصیت Android :Title متن اصلی منو را مشخص میکند.
سپس باید در Layout مورد نظر همانند صفحه Main، ساختار اصلی برنامه شامل Toolbar و Menu را بصورت زیر تعریف نماییم:
ساختار منو به صورت زیر است:
در Linearlayout ریشه، گزینه Fitssystemwindow را true میکنیم که سایز linearlayout را با سایز موبایل جدید اندازه میکند. سپس از toolbox، کنترل Toolbarرا به پنجره اضافه میکنیم که در بالای صفحه قرار میگیرد.
toolbar اضافه شده، toolbar استاندارد قبل از متریال دیزاین میباشد. در واقع toolbar اول، Toolbar استاندارد اندروید میباشد. برای آنکه از Toolbar متریال دیزاین استفاده کنیم، کنترلهای متریال دیزاین در بخش supportlibrary اضافه میشود و Toolbar متریال دیزاین را اضافه میکنیم. علامت ؟ یعنی اینکه میخواهیم از اندازه سیستمی استفاده کنیم. اگر بخواهیم حداقل سایز Toolbarبر اساس پیش فرض در دستگاههای مختلف باشد، از علامت Android :attr ? استفاده میکنیم. اگر بخواهیم حداقل ارتفاع پیشنهادی اندروید در هر موبایل متصل شود، از خصوصیت Action Bar Size استفاده میکنیم. این خصوصیت زمانی عمل میکند که Height آن Wrapcontent باشد.
گزینههای منو در کنترلی به نام Navigationview قرار دارد. این کنترل باید در Drawerlayout قرار گیرد. توسط فضای نام منو، محل فایل xml را که منو درون آن قرار گرفته است، مشخص میکنیم. آدرس این دستور در این مسیر میباشد:
Layout gravity آن را end قرار میدهیم که از سمت راست قرار بگیرد. Fit system Window را هم True میکنیم تا گزینههای داخل آنرا هم Fit کند. Theme باید از نوع تمهای متریال دیزاین و با کلمه Them . App Compact. ligth.NoActionBar باشد. برای آنکه اکتیویتیها، متریال دیزاین را ساپورت کنند، میتوان از کلاس App compact Activity استفاده کنیم. Tool bar بصورت پیش فرض لیبل اکتیویتی را نشان میدهد و دستور زیر عنوان Toolbar را حذف میکند.
مدیریت گزینههای منو
به محض انتخاب یک گزینه درون NavigationView، رخدادی به نام NavigationItemSelected صادر میشود که توسط آن میتوانیم گزینهی انتخاب شده را از طریق برنامه نویسی مدیریت کنیم. این کنترل در Android.Support.V7.Widget و NameSpace بالا قرار میگیرد. سپس یک رخداد گردان را با نام navigationItemSelected پیاده سازی میکنیم. اطلاعات مربوط به گزینهی انتخاب شده، در پارامتر دوم از این تابع NavigationView.NavigationItemSelectedEventArgs ذخیره میشود. ID، آیتم انتخاب شده در فایل Menu را باز میگرداند.
مدیریت اکتیویتیها توسط Menu
تکنیک ادغام:
برای آنکه در Layoutهای مختلف، تولبار و منو و یا هر View دیگری را بصورت مشترک استفاده کنیم، یک فایل xml را به دایرکتوری Layout اضافه میکنیم. دستور Merge میتواند تمام layoutها را به درون layoutهای دیگر مانند home,insert ادغام و یا تزریق کند. جهت استفاده از Merge در layoutهای دیگر نیاز به Id منحصر به فرد میباشد.
در اکتیویتیهای دیگر باید Toolbar و مدیریت گزینههای منو با کدهای مشابه Main انجام شود.
بنابراین دستورات xmlTollbar و darawer layout در تمامی Layoutها و دستورات سی شارپ، کنترل کننده Toolbar و منو در تمامی اکتیویتیها تکرار شدهاند.
نام لیآوت را در خصوصیت Layout اضافه میکنیم. برای آنکه کدهای سی شارپ کنترل کنندهی Toolbar و Menu چندین Toolbar وجود دارد که در یکی از آنها یک کلاس واسط از کلاس app compat Activity را به ارث میبریم. تابع Protected را از آن بازنویسی کرده و تمام کدهای مدیریت Toolbar و منو را در آن پیاده سازی میکنیم. تمام اکتیویتیهای برنامه را از این کلاس به ارث میبریم. بنابراین تابع InitieToolbar به تمامی فرزندان نیز به ارث برده میشود. در زمان اجرای دستورات، this ، اکتیویتی جاری میباشد.
ساخت TabPage
پیشنیاز: نصب کتابخانههای متریال دیزاین همانند قبل و طبق ورژن Sdk نصب شده
اگر بخواهیم چندین صفحه را بر روی یکدیگر Stack و یا Overload نماییم، از Tabpage استفاده میکنیم. صفحاتیکه از TabPage استفاده میکنند، با انگشت جابجا میشوند و همانند برنامهی واتساپ Fragment میباشند و هر Fragment دارای layout و اکتیویتی مربوط به خود میباشد. معماری layout آن بصورت زیر است:
ToolBar، در بالای فرم قرار میگیرد. TabLayou که بصورت TabPage آنها را به عهده دارد. Viewpager مدیریت Layoutها را به هنگام Swipe یا جابجایی به عهده دارد.
یک layout را برای Toolbar قرار میدهیم. سپس Layout اصلی main را طراحی میکنیم. پس از اضافه کردن ToolBar، ابزار TabLayout را در بخش SupportLibrary متریال دیزاین انتخاب و در صفحه میکشیم. TabLayout در پایین Toolbar قرار میگیرد و با انتخاب رنگ یکسان برای هر دو، متصل و یکنواخت به نظر میرسد. سپس از Layout از Toolbar آیتم ViewPager را بر روی صفحه قرار میدهیم. اگر LayoutWeight آن را یک قرار دهیم، تمام ارتفاع صفحه را به ما تخصیص میدهد. زمانیکه در TabLayout تبها جابجا میشوند یا بر روی یک آیکن کلیک میشود، صفحه مربوطه در بخش ViewPager به کاربر نمایش داده میشود. هر Page یک فرگمنت میباشد. به ازای هر فرگمنت یک Layout به دایرکتوری layout اضافه کرده و به ازای هر layoutFragment یک Activity Fragment را اضافه میکنیم. یک اکتیویتی از نوع را Android.Support.v4.AppFragment ایجاد میکنیم.
ابتدا باید viewpager در Layout اصلی را پیدا کرده و با دستور زیر به Tablayout متصل کنیم:
زمانیکه آیکن را در TabLayout انتخاب میکنیم یا با انگشت Swipe میکنیم، به ترتیب بین صفحات که از Position صفحه آغاز شدهاند، حرکت میکنیم، باید فرگمنت همان Position را نشان دهیم و این مدیریت توسط بخشی بهنام Adapter انجام میشود. یک Adapter را به کنترلر اضافه میکنیم و از کلاس Fragment pager adapter به ارث میبریم. بر روی کلاس Fragment pager adapter ، دکمههای کنترل و نقطه را میزنیم و سپس کلاس را پیاده سازی میکنیم. در این حالت دو تابع را به ما میدهد: تابع Get item .count مجددا بر روی کلاس پدر راست کلیک میکنیم. در تابع کانت تعداد کل صفحهها را (Layout ها) را انتخاب میکنیم. هرگاه از یک صفحه به صفحهی دیگری انتقال پیدا کنیم، موقعیت صفحه جدیدی که از یک شروع میشود را به تابع Get Item بر اساس موقعیت Object از fragment مربوطه new کرده و بعنوان خروجی باز میگرداند.
و در اکتیویتی اصلی، کد زیر را برای Load فرگمنتها نیز قرار میدهیم:
آیکن برای TabPage
سپس اگر بخواهیم آیکنهای Tab را به ترتیب تعریف کنیم، از تابع Gettabat استفاده میکنیم. پارامتر ورودی آن موقعیت Tab page میباشد و Set icon هم آیکنهای دایرکتوری Drawable را انتخاب میکند.
نمایش متن همراه با عکس
اگر بخواهیم آیکنهای تب پیج را سفارشی کنیم، از Layout استفاده میکنیم که عرض و ارتفاع آن wrap Content باشند و درون آن یک Text view که معادل Lable میباشند، قرار میدهند:
کدهای مطلب جاری برای دریافت: Navigation-TabPage-samples.zip
برای اینکه بتوانیم ظاهر گرافیکی layoutها را کنترل نماییم، از Theme که مجموعهای از styleهای گرافیکی میباشد، استفاده میکنیم. در اندروید مجموعهای از تمهای از پیش ساخته شده که به آنها Builtin Theme نیز گفته میشود میتوانیم استفاده کنیم. تمها ظاهر گرافیکی کلیه کنترلهای Layout را با نامهای زیر، کنترل میکنند:
statusBarColor textColorPrimary colorAccent ColorPrimary WindowBackground
اگر ساختار زیر را در یک صفحه استاندارد برنامههای موبایل را در نظر بگیریم، styleها هر بخش، یک نام منحصر به فرد دارد:
اگر بخواهیم از styleهای از پیش طراحی شدهی اندروید استفاده نماییم، ابتدا میتوانیم در صفحهی layout در زامارین، style مربوطه را از بخش Theme استفاده کرده و نتیجه را مشاهده کنیم. ولی تغییر style سبب تغییر layout در زمان اجرا نمیشود. هرگاه بخواهیم از styleهای استاندارد یا builtin اندروید استفاده نماییم، در Activity توسط خصوصیت Theme با فرمت:
[Activity(Theme = "@style/NameThem")]
در طراحی فرمها ممکن است بخواهیم از یک استایل خاص builtin استفاده کنیم؛ ولی ممکن است بعضی از استایلهای آن را استفاده نکنیم، مانند تمی که از قبل استفاده شدهاست، از روش زیر استفاده میکنیم:
- بر روی دایرکتوری value راست کلیک میکنیم. گزینه add new item را انتخاب و یک فایل xmlfile را با نام style ایجاد میکنیم.
- styleهای جدید منابع application میباشند که در بخش resource از آنها استفاده میکنیم. هر استایل جدید را توسط Style Tag مشخص میکنیم و در خصوصیت Name، نام Style را مشخص میکنیم.
ممکن است در یک Style نتوانیم و یا نخواهیم تمامی Styleهای مورد نیاز را تامین کنیم. از این رو توسط Parent، یک StyleBuition تعریف نموده که این Styleها از آن مشتق میشوند. اگر در Theme جدید گزینهها مشخص شوند، تم اصلی تغییر نمیکند. در غیر اینصورت تمامی گزینههای تعریف شده در تم جدید از ThemParent مقدار دهی میشود.
توسط item میتوان یک style را تعریف نمود. در Activity توسط
[Activity(Theme = "@style/NameThem")]
<?xml version="1.0" encoding="utf-8" ?> <resources> <style name="MainTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="windowNoTitle">true</item> <item name="windowActionBar">false</item> </style> </resources>
Navigation Menu
کتابخانه متریال دیزاین کتابخانه جدیدی میباشد که به اندروید اضافه شدهاست. توسط آن میتوانیم کنترلهای جدید را با استایلهای جدید برای Appهای اندروید، تولید کنیم. ابتدا نیاز به نصب Componentهای ذیل در زامارین میباشد.
استفاده که از کتابخانه متریال دیزاین
برای اینکه بتوانیم Navigation menu را ایجاد کنیم، باید از نیوگت، کتابخانههای Appcompat و Designlibrary را انتخاب و نصب نماییم و اگر نگارش ویژوال استودیوی شما 15.7.3باشد، از ابتدا بصورت اتوماتیک نصب شده است و احتیاجی به این مراحل نمیباشد. بدیهی است در زمان نصب باید از نرم افزارهای تحریم گذر نیز استفاده کرد.ساخت Menu
NavigationMenu، منوی اصلی منو میباشد که با Swipping از گوشه راست به چپ، باز یا بسته میشود یا با کلیک بر روی دکمهی Menu بر روی Toolbar، منو را باز یا بسته میکند.
قدم اول: نصب منومنظور همان اضافه کردن کتابخانهها است.
قدم دوم:
ابتدا باید گزینههای منو را در یک فایل xml تعریف نمود. هر گزینهی آن از دو بخش متن اصلی منو و ID منو تشکیل شدهاست.
بر روی دایرکتوری Resource راست کلیک کرده و یک دایرکتوری یا پوشه را به نام Menu ایجاد میکنیم. بر روی دایرکتوری منو، راست کلیک کرده و یک فایل Xml را به آن اضافه میکنیم. برای آنکه بتوانیم در این فایل دستورات ساخت منو را نوشته و به نحوی که توسط اندروید قابل خواندن و تبدیل به منو باشد، ساختار منو را از آدرس زیر ویژوال استودیو دانلود میکنیم.
<menu xmlns:android="http://schemas.android.com/apk/res/android">
توسط Group مجموعه گزینههای منو را را معرفی میکنیم.
هر گزینه در منو ی اصلی توسط یک آیتم مشخص میشود. برای آنکه هنگام کلیک بر روی هر گزینه از طریق برنامه نویسی بتوانیم گزینه انتخاب شده را شناسایی کنیم، یک آی دی منحصر بفرد را به هر گزینه اختصاص میدهیم. زمانیکه بر روی یک گزینه کلیک میشود، توسط این IDها میتوانیم شناسایی کنیم کدام گزینه انتخاب شدهاست.خصوصیت Android :Title متن اصلی منو را مشخص میکند.
<?xml version="1.0" encoding="utf-8" ?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <group android:checkableBehavior="single"> <item android:id="@+id/menuItemHome" android:title="صفحه اصلی"></item> <item android:id="@+id/menuItemInsertProduct" android:title="ورود کالا جدید" ></item> <item android:id="@+id/menuItemListProduct" android:title="مشاهده کالاها"></item> <item android:id="@+id/menuItemExit" android:title="خروج"></item> </group> </menu>
سپس باید در Layout مورد نظر همانند صفحه Main، ساختار اصلی برنامه شامل Toolbar و Menu را بصورت زیر تعریف نماییم:
<android.support.v7.widget.Toolbar android:layout_width="match_parent" android:id="@+id/toolbar1" android:background="#33B86C" android:minHeight="?android:attr/actionBarSize" android:layout_height="wrap_content"> </android.support.v7.widget.Toolbar>
ساختار منو به صورت زیر است:
<?xml version="1.0" encoding="utf-8" ?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <group android:checkableBehavior="single"> <item android:id="@+id/menuItemHome" android:title="صفحه اصلی"></item> <item android:id="@+id/menuItemInsertProduct" android:title="ورود کالا جدید" ></item> <item android:id="@+id/menuItemListProduct" android:title="مشاهده کالاها"></item> <item android:id="@+id/menuItemExit" android:title="خروج"></item> </group> </menu>
toolbar اضافه شده، toolbar استاندارد قبل از متریال دیزاین میباشد. در واقع toolbar اول، Toolbar استاندارد اندروید میباشد. برای آنکه از Toolbar متریال دیزاین استفاده کنیم، کنترلهای متریال دیزاین در بخش supportlibrary اضافه میشود و Toolbar متریال دیزاین را اضافه میکنیم. علامت ؟ یعنی اینکه میخواهیم از اندازه سیستمی استفاده کنیم. اگر بخواهیم حداقل سایز Toolbarبر اساس پیش فرض در دستگاههای مختلف باشد، از علامت Android :attr ? استفاده میکنیم. اگر بخواهیم حداقل ارتفاع پیشنهادی اندروید در هر موبایل متصل شود، از خصوصیت Action Bar Size استفاده میکنیم. این خصوصیت زمانی عمل میکند که Height آن Wrapcontent باشد.
گذاشتن دکمه منو: برای آنکه بتوانیم دکمه منو را به Toolbar اضافه کنیم، از دکمه Image Button استفاده میکنیم که یک دکمهی معمولی میباشد ولی خلاصهی آن عکس است. در خصوصیت Back ground دکمه، بصورت زیر نام فایل آیکن منو را در دایرکتوری Drawable، مشخص میکنیم و خصوصیت src آنرا null میکنیم تا تصویری بجز تصویر انتخابی نباشد.
برای آنکه بتوانیم پنجره اصلی منو را به صورتیکه دارای قابلیت حرکت به راست و چپ باشد، ایجاد کنیم، از کنترلی بهنام Drowerlayout استفاده میکنیم که بر روی صفحه قرار میگیرد. DrawerLayout در linearlayout ریشه قرار میگیرد و یا بعد از ToolBar و حتما باید خصوصیت fitsystemwindow کنترل Drawer را True کنیم. جهت نمایش گزینههای اصلی در Drawer از کنترل NavigationٰView استفاده میکنیم.گزینههای منو در کنترلی به نام Navigationview قرار دارد. این کنترل باید در Drawerlayout قرار گیرد. توسط فضای نام منو، محل فایل xml را که منو درون آن قرار گرفته است، مشخص میکنیم. آدرس این دستور در این مسیر میباشد:
xmlns:app="http://schemas.android.com/apk/res-auto"
SupportActionBar.SetDisplayShowTitleEnabled(false);
مدیریت گزینههای منو
به محض انتخاب یک گزینه درون NavigationView، رخدادی به نام NavigationItemSelected صادر میشود که توسط آن میتوانیم گزینهی انتخاب شده را از طریق برنامه نویسی مدیریت کنیم. این کنترل در Android.Support.V7.Widget و NameSpace بالا قرار میگیرد. سپس یک رخداد گردان را با نام navigationItemSelected پیاده سازی میکنیم. اطلاعات مربوط به گزینهی انتخاب شده، در پارامتر دوم از این تابع NavigationView.NavigationItemSelectedEventArgs ذخیره میشود. ID، آیتم انتخاب شده در فایل Menu را باز میگرداند.
var navigationview = this.FindViewById<NavigationView>(Resource.Id.navigationView1); navigationview.NavigationItemSelected += Navigationview_NavigationItemSelected; private void Navigationview_NavigationItemSelected(object sender, NavigationView.NavigationItemSelectedEventArgs e) { Intent intent = null; switch (e.MenuItem.ItemId) { case Resource.Id.menuItemHome: break; case Resource.Id.menuItemExit: Finish(); break; case Resource.Id.menuItemInsertProduct: break; case Resource.Id.menuItemListProduct: break; } }
مدیریت اکتیویتیها توسط Menu
با انتخاب گزینه Menu باید اکتیویتی مربوطه انتخاب شود. بنابراین برای هر گزینهی منو یک Layout و اکتیویتی را ایجاد میکنیم و اجرا میکنیم. ولی در اکتیویتی جدید Toolbar وجود ندارد.
برای آنکه در Layoutهای مختلف، تولبار و منو و یا هر View دیگری را بصورت مشترک استفاده کنیم، یک فایل xml را به دایرکتوری Layout اضافه میکنیم. دستور Merge میتواند تمام layoutها را به درون layoutهای دیگر مانند home,insert ادغام و یا تزریق کند. جهت استفاده از Merge در layoutهای دیگر نیاز به Id منحصر به فرد میباشد.
<?xml version="1.0" encoding="utf-8" ?> <merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/toolbarlayout"> <android.support.v7.widget.Toolbar android:layout_width="match_parent" android:id="@+id/toolbar1" android:background="#33B86C" android:minHeight="?android:attr/actionBarSize" android:layout_height="wrap_content"> <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/imageButton1" android:background="@drawable/mainmenu" android:layout_gravity="end" /> </android.support.v7.widget.Toolbar> <android.support.v4.widget.DrawerLayout android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/drawerLayout1" android:fitsSystemWindows="true"> <android.support.design.widget.NavigationView android:minWidth="25px" android:minHeight="25px" android:layout_width="200dp" android:layout_height="match_parent" android:layout_gravity="end" app:menu="@menu/menu" android:id="@+id/navigationView1" android:fitsSystemWindows="true" /> </android.support.v4.widget.DrawerLayout> </merge>
در اکتیویتیهای دیگر باید Toolbar و مدیریت گزینههای منو با کدهای مشابه Main انجام شود.
private void Navigationview_NavigationItemSelected(object sender, NavigationView.NavigationItemSelectedEventArgs e) { Intent intent = null; switch (e.MenuItem.ItemId) { case Resource.Id.menuItemHome: intent = new Intent(this, typeof(MainActivity)); break; case Resource.Id.menuItemExit: Finish(); break; case Resource.Id.menuItemInsertProduct: intent = new Intent(this, typeof(InsertActivity)); break; case Resource.Id.menuItemListProduct: intent = new Intent(this, typeof(ListProductsActivity)); break; } if (intent != null) { } }
حل مشکلات Layout
یک فایل Xml را به Layout اضافه میکنیم و درون آن Tag merge و کدهای مشترک Drawer out و Toolbar را داخل تگ Merge اضافه میکنیم. جهت استفاده از کدهای (مقدار فایل ایکس ام ال ساخته شده که Tag merge داخل آن است) Merge، در layout های دیگر، از دستور Include استفاده میکنیم.نام لیآوت را در خصوصیت Layout اضافه میکنیم. برای آنکه کدهای سی شارپ کنترل کنندهی Toolbar و Menu چندین Toolbar وجود دارد که در یکی از آنها یک کلاس واسط از کلاس app compat Activity را به ارث میبریم. تابع Protected را از آن بازنویسی کرده و تمام کدهای مدیریت Toolbar و منو را در آن پیاده سازی میکنیم. تمام اکتیویتیهای برنامه را از این کلاس به ارث میبریم. بنابراین تابع InitieToolbar به تمامی فرزندان نیز به ارث برده میشود. در زمان اجرای دستورات، this ، اکتیویتی جاری میباشد.
public class BaseAcitivity : AppCompatActivity { protected void InitieToolbar() { var toolbar = this.FindViewById<widgetV7.Toolbar>(Resource.Id.toolbar1); this.SetSupportActionBar(toolbar); //SupportActionBar.SetDisplayShowTitleEnabled(false); var imagebutton = toolbar.FindViewById<ImageButton>(Resource.Id.imageButton1); imagebutton.Click += Imagebutton_Click; var navigationview = this.FindViewById<NavigationView>(Resource.Id.navigationView1); navigationview.NavigationItemSelected += Navigationview_NavigationItemSelected; } private void Navigationview_NavigationItemSelected(object sender, NavigationView.NavigationItemSelectedEventArgs e) { Intent intent = null; switch (e.MenuItem.ItemId) { case Resource.Id.menuItemHome: intent = new Intent(this, typeof(MainActivity)); break; case Resource.Id.menuItemExit: Finish(); break; case Resource.Id.menuItemInsertProduct: intent = new Intent(this, typeof(InsertActivity)); break; case Resource.Id.menuItemListProduct: intent = new Intent(this, typeof(ListProductsActivity)); break; } if (intent != null) StartActivity(intent); } private void Imagebutton_Click(object sender, EventArgs e) { var drawerlayout = this.FindViewById<DrawerLayout>(Resource.Id.drawerLayout1); if (drawerlayout.IsDrawerOpen(Android.Support.V4.View.GravityCompat.End) == false) { drawerlayout.OpenDrawer(Android.Support.V4.View.GravityCompat.End); } else { drawerlayout.CloseDrawer(Android.Support.V4.View.GravityCompat.End); } } }
اگر بخواهیم یک تم در تمامی اکتیویتیها به صورت سراسری استفاده شود، از فایل تنظمیات اندروید بنام AndroidManifest در دایرکتوری Properties استفاده میکنیم و در بخش Application Theme، نام تم را مشخص میکنیم:
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
ساخت TabPage
پیشنیاز: نصب کتابخانههای متریال دیزاین همانند قبل و طبق ورژن Sdk نصب شده
اگر بخواهیم چندین صفحه را بر روی یکدیگر Stack و یا Overload نماییم، از Tabpage استفاده میکنیم. صفحاتیکه از TabPage استفاده میکنند، با انگشت جابجا میشوند و همانند برنامهی واتساپ Fragment میباشند و هر Fragment دارای layout و اکتیویتی مربوط به خود میباشد. معماری layout آن بصورت زیر است:
ToolBar، در بالای فرم قرار میگیرد. TabLayou که بصورت TabPage آنها را به عهده دارد. Viewpager مدیریت Layoutها را به هنگام Swipe یا جابجایی به عهده دارد.
یک layout را برای Toolbar قرار میدهیم. سپس Layout اصلی main را طراحی میکنیم. پس از اضافه کردن ToolBar، ابزار TabLayout را در بخش SupportLibrary متریال دیزاین انتخاب و در صفحه میکشیم. TabLayout در پایین Toolbar قرار میگیرد و با انتخاب رنگ یکسان برای هر دو، متصل و یکنواخت به نظر میرسد. سپس از Layout از Toolbar آیتم ViewPager را بر روی صفحه قرار میدهیم. اگر LayoutWeight آن را یک قرار دهیم، تمام ارتفاع صفحه را به ما تخصیص میدهد. زمانیکه در TabLayout تبها جابجا میشوند یا بر روی یک آیکن کلیک میشود، صفحه مربوطه در بخش ViewPager به کاربر نمایش داده میشود. هر Page یک فرگمنت میباشد. به ازای هر فرگمنت یک Layout به دایرکتوری layout اضافه کرده و به ازای هر layoutFragment یک Activity Fragment را اضافه میکنیم. یک اکتیویتی از نوع را Android.Support.v4.AppFragment ایجاد میکنیم.
public class Fragment1 : Android.Support.V4.App.Fragment { public override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); } public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.Inflate(Resource.Layout.FragmentLayout1, container, false); } }
var tablayout = FindViewById<Android.Support.Design.Widget.TabLayout>(Resource.Id.tabLayout1); var viewpager = FindViewById<ViewPager>(Resource.Id.viewPager1); tablayout.SetupWithViewPager(viewpager);
class TabFragmentAdapter : FragmentPagerAdapter { public TabFragmentAdapter(FragmentManager fm) : base(fm) { } public override int Count => 3; public override Fragment GetItem(int position) { switch (position) { case 0: return new Fragment1(); case 1: return new Fragment2(); case 2: return new Fragment3(); default: return new Fragment1(); } } //int f1() { return 100; } //int f1 => 100; }
viewpager.Adapter = new TabFragmentAdapter(this.SupportFragmentManager);
آیکن برای TabPage
سپس اگر بخواهیم آیکنهای Tab را به ترتیب تعریف کنیم، از تابع Gettabat استفاده میکنیم. پارامتر ورودی آن موقعیت Tab page میباشد و Set icon هم آیکنهای دایرکتوری Drawable را انتخاب میکند.
tablayout.GetTabAt(0).SetIcon(Resource.Drawable.iconCall);
نمایش متن همراه با عکس
اگر بخواهیم آیکنهای تب پیج را سفارشی کنیم، از Layout استفاده میکنیم که عرض و ارتفاع آن wrap Content باشند و درون آن یک Text view که معادل Lable میباشند، قرار میدهند:
View iconlayout1 = LayoutInflater.Inflate(Resource.Layout.custom_TabIconLayout, null); var txt = iconlayout1.FindViewById<TextView>(Resource.Id.tabTextIcon); txt.Text = "تماس"; txt.SetCompoundDrawablesWithIntrinsicBounds(Resource.Drawable.iconCall, 0, 0, 0); tablayout.GetTabAt(0).SetCustomView(iconlayout1);
کدهای مطلب جاری برای دریافت: Navigation-TabPage-samples.zip
نظرات مطالب
پیاده سازی UnitOfWork به وسیله MEF
من کلاسهام به این شکله:
کلاس کانتکسهای من
در تعاریف کلاسهایی که از IDBContext ارث میبرن اکسپورت شدن (این یک نمونه از کلاسهای منه)
در طرف دیگر برای لود کردن کلاس زیر نوشتم
و در کانتکس اصلی برنامه این پلاگین هارو لود میکنم
کلاس کانتکسهای من
public class VegaContext : DbContext, IUnitOfWork, IDbContext { #region Constructors (2) /// <summary> /// Initializes the <see cref="VegaContext" /> class. /// </summary> static VegaContext() { Database.SetInitializer<VegaContext>(null); } /// <summary> /// Initializes a new instance of the <see cref="VegaContext" /> class. /// </summary> public VegaContext() : base("LocalSqlServer") { } #endregion Constructors #region Properties (2) /// <summary> /// Gets or sets the languages. /// </summary> /// <value> /// The languages. /// </value> public DbSet<Language> Languages { get; set; } /// <summary> /// Gets or sets the resources. /// </summary> /// <value> /// The resources. /// </value> public DbSet<Resource> Resources { get; set; } #endregion Properties #region Methods (2) // Public Methods (1) /// <summary> /// Setups the specified model builder. /// </summary> /// <param name="modelBuilder">The model builder.</param> public void Setup(DbModelBuilder modelBuilder) { //todo modelBuilder.Configurations.Add(new ResourceMap()); modelBuilder.Configurations.Add(new LanguageMap()); modelBuilder.Entity<Resource>().ToTable("Vega_Languages_Resources"); modelBuilder.Entity<Language>().ToTable("Vega_Languages_Languages"); //base.OnModelCreating(modelBuilder); } // Protected Methods (1) /// <summary> /// This method is called when the model for a derived context has been initialized, but /// before the model has been locked down and used to initialize the context. The default /// implementation of this method does nothing, but it can be overridden in a derived class /// such that the model can be further configured before it is locked down. /// </summary> /// <param name="modelBuilder">The builder that defines the model for the context being created.</param> /// <remarks> /// Typically, this method is called only once when the first instance of a derived context /// is created. The model for that context is then cached and is for all further instances of /// the context in the app domain. This caching can be disabled by setting the ModelCaching /// property on the given ModelBuidler, but note that this can seriously degrade performance. /// More control over caching is provided through use of the DbModelBuilder and DbContextFactory /// classes directly. /// </remarks> protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new ResourceMap()); modelBuilder.Configurations.Add(new LanguageMap()); modelBuilder.Entity<Resource>().ToTable("Vega_Languages_Resources"); modelBuilder.Entity<Language>().ToTable("Vega_Languages_Languages"); base.OnModelCreating(modelBuilder); } #endregion Methods #region IUnitOfWork Members /// <summary> /// Sets this instance. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <returns></returns> public new IDbSet<TEntity> Set<TEntity>() where TEntity : class { return base.Set<TEntity>(); } #endregion }
در طرف دیگر برای لود کردن کلاس زیر نوشتم
public class LoadContexts { public LoadContexts() { var directoryPath = HttpRuntime.BinDirectory;//AppDomain.CurrentDomain.BaseDirectory; //"Dll folder path"; var directoryCatalog = new DirectoryCatalog(directoryPath, "*.dll"); var aggregateCatalog = new AggregateCatalog(); aggregateCatalog.Catalogs.Add(directoryCatalog); var container = new CompositionContainer(aggregateCatalog); container.ComposeParts(this); } //[Import] //public IPlugin Plugin { get; set; } [ImportMany] public IEnumerable<IDbContext> Contexts { get; set; } }
public class MainContext : DbContext, IUnitOfWork { public MainContext() : base("LocalSqlServer") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); var contextList = new LoadContexts(); //ObjectFactory.GetAllInstances<IDbContext>(); foreach (var context in contextList.Contexts) context.Setup(modelBuilder); Database.SetInitializer(new MigrateDatabaseToLatestVersion<MainContext, Configuration>()); //Database.SetInitializer(new DropCreateDatabaseAlways<MainContext>()); } /// <summary> /// Sets this instance. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <returns></returns> public IDbSet<TEntity> Set<TEntity>() where TEntity : class { return base.Set<TEntity>(); } }
با موفقیت همه پلاگینها لود میشه و مشکلی در عملیات نیست. اما Attributeهای کلاس هارو نمیشناسه. مثلا پیام خطا تعریف شده در MVC نمایش داده نمیشه چون وجود نداره ولی وقتی کلاس مورد نظر از IValidatableObject ارث میبره خطایهای من نمایش داده میشه. میخوام از خود متادیتاهای استاندارد استفاده کنم.
تا قبل از ES 6 در جاوا اسکریپت از توابع جهت ایجاد کامپوننتهایی با قابلیت استفاده مجدد استفاده میشد. این امر برای برنامهنویسانی که با زبانهای OOP آشنایی دارند، شاید چندان خوشایند نباشد. در TypeScript نیز همانند ES 6 امکان استفاده از کلاسها مهیا است.
سازنده (Constructor)
همانطور که مشاهده میکنید یک سازنده شبیه به یک متد است؛ با این تفاوت که برای نام آن از کلمه کلیدی constructor استفاده میشود. در TypeScript برای یک کلاس تنها یک سازنده را میتوانیم داشته باشیم. البته در دیگر زبانهای برنامهنویسی امکان تعریف چندین سازنده را با پارامترهای مختلف برای یک کلاس میتوانید داشته باشید. برای رسیدن به این هدف در TypeScript میتوان از Optional Parameters استفاده کرد. برای ایجاد یک وهله از کلاس فوق میتوانیم به این صورت عمل کنیم:
در کد فوق با استفاده از کلمهی کلیدی new یک وهله از کلاس ReferenceItem را ایجاد کردهایم و در نهایت آن را به متغیری با نام encyclopedia انتساب دادهایم. یعنی در واقع با استفاده از new توانستهایم سازندهی کلاس را فراخوانی کرده و سپس وهلهایی از آن را به متغیر ذکر شده انتساب دهیم.
پراپرتی، متد
برای دسترسی به این پراپرتی میتوانیم از سینتکس نقطه (.) استفاده کنیم. روش دوم برای تعریف یک پراپرتی، ایجاد accessorهای سفارشی است. accessors در واقع توابع getter و setter هستند که به شما در نحوهی get و set کردن یک پراپرتی کمک خواهند کرد:
همانطور که مشاهده میکنید، accessorهایی را برای پراپرتی editor با استفاده از کلمات کلیدی get و set ایجاد کردهایم. این accessorها در واقع توابعی همنام هستند. تابع get همیشه فاقد پارامتر است. میتوانیم برای تابع get نوع برگشتی را نیز تعیین کنیم (به عنوان مثال در کد فوق نوع برگشتی string است). setter نیز باید تنها یک پارامتر از ورودی دریافت کند. همچنین نمیتوانیم برای آن نوع برگشتی را تعیین کنیم. درون بدنهی این accessorها میتوانیم هر نوع کنترلی را بر روی پراپرتی داشته باشیم. برای دسترسی این accessorها نیز باید از سینتکس نقطه (.) استفاده کنیم.
Parameter properties
با کمک Parameter properties میتوانیم به صورت خلاصهتری اینکار را انجام دهیم:
همانطور که مشاهده میکنید اینکار را با افزودن کلمهی کلیدی public به ابتدای پارامتر name انجام دادهایم. در اینحالت دیگر نیازی به تعریف یک پراپرتی اضافی درون کلاس نخواهیم داشت. کامپایلر TypeScript خودش یک پراپرتی را با همین نام ایجاد کرده و مقدار دریافتی از سازنده را برای آن ست خواهد کرد.
Access Modifiers
همانطور که مشاهده میکنید با استفاده از کلمهی کلیدی extends توانستهایم یک sub-class ایجاد کنیم. بنابراین وهلههای کلاس Journal علاوه بر پراپرتیهای خود (در اینجا contributors ) دارای پراپرتی title و همچنین متد printItem نیز هستند. نکتهایی که در اینجا وجود دارد این است که تمامی sub-classها یا کلاسهای مشتق شده باید درون سازندهی خود، تابع super را فراخوانی کنند؛ با اینکار سازندهی کلاس پایه فراخوانی خواهد شد.
با استفاده از super.printItem به کامپایلر TypeScript گفتهایم که تمامی کدهای درون متد printItem در کلاس پایه نیز اجرا شوند. اگر مایل بودید میتوانید از آن صرفنظر کنید.
همانطور که مشاهده میکنید درون یک کلاس abstract میتوانیم متدهای abstract را نیز داشته باشیم؛ یعنی تنها امضای متد را تعیین کرده و پیادهسازی آن را به کلاسهای مشتق شده واگذار کنیم.
در حالت کلی یک کلاس قالبی برای ایجاد اشیاء است. تمامی اشیاء ایجاد شده از این الگو دارای یکسری پراپرتی و متد میباشند. از پراپرتیها جهت تعریف وضعیتها و از متدها جهت تعریف رفتارها استفاده خواهد شد. همچنین مزیت اصلی یک کلاس، کپسولهسازی قابلیتهای یک موجودیت خاص است. همانند دیگر زبانهای شیءگرا، در TypeScript نیز یک کلاس میتواند ویژگیهای زیر را داشته باشد:
- سازنده (constructor)
- پراپرتی، متد
- Access Modifiers
- ارثبری
- کلاسهای Abstract
در ادامه هر کدام از موارد فوق را بررسی خواهیم کرد.
سازنده (Constructor)
از سازندهها جهت مقداردهی وهلههای یک کلاس استفاده میشود. در ادامه یک کلاس جدید را با استفاده از کلمهی کلیدی class ایجاد کردهایم. این کلاس دارای یک سازنده است:
class ReferenceItem { constructor(title: string, publisher?: string) { // perform initialization here } }
let encyclopedia = new ReferenceItem('WorldPedia', 'WorldPub');
پراپرتی، متد
همانند اینترفیسها، کلاسها نیز میتوانند پراپرتی و متد داشته باشند. با این تفاوت که در کلاسها جزئیات پیادهسازی نیز ذکر خواهد شد. در یک کلاس به دو روش متفاوت میتوانیم پراپرتی را تعریف کنیم. روش اول همانند تعریف یک متغیر است. به عنوان مثال در کلاس زیر یک پراپرتی با نام numberOfPages را از نوع عددی تعریف کردهایم:
class ReferenceItem { numberOfPages: number; }
class ReferenceItem { numberOfPages: number; get editor(): string { // custom getter logic goes here, should return a value } set editor(newEditor: string) { // custom setter logic goes here } }
متدها نیز توابعی هستند که درون یک کلاس تعریف میشوند. برای نمونه در کد زیر یک تابع با نام printChapterTitle را تعریف کردهایم که یک پارامتر را از ورودی دریافت کرده و هیچ مقداری را در خروجی بر نمیگرداند:
class ReferenceItem { numberOfPages: number; get editor(): string { // custom getter logic goes here, should return a value } set editor(newEditor: string) { // custom setter logic goes here } printChapterTitle(chapterNum: number): void { // print title here } }
در حالت عادی برای مقداردهی اولیهی پراپرتیها یک شیء میتوانیم یکسری پارامتر را برای سازنده کلاس تعریف کرده و درون سازنده، پراپرتیهای موردنیازمان را مقداردهی کنیم:
class Author { name: string; constructor(authorName: string) { name = authorName; } }
class Author { constructor(public name: string){} }
Static Properties
تاکنون دربارهی اعضای مربوط به هر وهله از کلاسها صحبت کردیم؛ یعنی اعضایی که در زمان وهلهسازی در دسترس خواهند بود. در واقع میتوانیم اعضای استاتیک را نیز برای کلاسها داشته باشیم. منظور از استاتیک این است که مقادیر یک عضوء استاتیک در وهلههای مختلف یک شیء، متفاوت نیست. بلکه یک مقدار آن برای تمامی وهلهها به اشتراک گذاشته خواهد شد:
class Library { constructor(public name: string) {} static description: string = 'A source of knowledge'; } let lib = new Library('New York Public Library'); console.log(lib.name); // available on instances of the class console.log(Library.description);
با استفاده از Access Modifier میتوانیم میدان دید یک پراپرتی و یا یک متد را برای مصرف کنندهی کلاس کنترل کنیم. TypeScript دارای سه Access Modifier است:
public: در حالت پیشفرض تمامی اعضای یک کلاس عمومی (public) هستند. در نتیجه لزومی به ذکر آن برای پراپرتیها و متدها نیست. یک حالت استثناء، استفاده از Parameter properties است. در این حالت باید کلمهی کلیدی public حتماً ذکر شود.
private: برای محدود کردن دسترسی اعضای یک کلاس میتوانید از کلمهی کلیدی private استفاده کنید. در اینحالت مصرف کنندهی کلاس به اعضای خصوصی (private) دسترسی نخواهد داشت.
protected: این modifier نیز شبیه به private عمل میکند، با این تفاوت که توسط subclassهای مربوط به کلاس تعریف شده در آن نیز قابل دسترس است.
Inheritance
منظور از Inheritance یا ارثبری، اشتراکگذاری تعاریف یک کلاس برای یک یا چند sub-class است. فرض کنید یک کلاس با نام ReferenceItem با یکسری اعضای تعریف شده درون آن داریم و میخواهیم دو کلاس مشتق شده را از این کلاس تهیه کنیم. در اینحالت کلاس ReferenceItem کلاس پایه (base class) و کلاسهای مشتق شده از آن sub-class نامیده میشوند. بنابراین وهلههای ایجاد شده از کلاسهای مشتق شده دارای پراپرتیهای کلاس پایه نیز خواهند بود. برای داشتن قابلیت ارثبری در TypeScript میتوانیم به اینصورت عمل کنیم:
class ReferenceItem { title: string; printItem(): void { // print something here } } class Journal extends ReferenceItem { constructor() { super(); } contributors: string[]; }
لازم به ذکر است که میتوان متدهای کلاس پایه را درون کلاسهای مشتق شده، override کرد. برای اینکار کافی است متد موردنظر در کلاس پایه را درون کلاس مشتق شده مجدداً تعریف کرده و منطق موردنظر را درون آن نوشت:
class Journal extends ReferenceItem { constructor() { super(); } printItem(): void { super.printItem(); console.log('message from Journal'); } contributors: string[]; }
Abstract Classes
کلاسهای Abstract یک نوع خاص از کلاسها هستند که نمیتوان آنها را وهلهسازی کرد. یعنی تنها برای تعریف کلاسهای پایه از آنها استفاده خواهد شد. این نوع کلاسها شبیه به اینترفیسها هستند؛ اما ممکن است دارای پیادهسازی نیز باشند. در ادامه یک نمونه از abstract class را مشاهده میکنید:
abstract class ReferenceItem { private _publisher: string; static departement: string = 'Research'; constructor(public title: string, protected year: number) { } printItem(): void { console.log('message from abstract class'); } get publisher(): string { return this._publisher.toUpperCase(); } set publisher(newPublisher: string) { this._publisher = newPublisher; } abstract printCitation(): void; } class Encyclopedia extends ReferenceItem { constructor(newTitle: string, newYear, public edition: number) { super(newTitle, newYear); } printCitation(): void { console.log('message'); } } let test = new Encyclopedia('WorldPerdia', 1900, 10); test.printItem();
در حین کار با برنامههای وب، چشمپوشی از جاوا اسکریپت عملا ممکن نیست؛ هرچند با Blazor، امکان انجام کارهایی را یافتهایم که پیشتر با MVC و یا Razor pages میسر نبودند، اما هیچگاه به تنهایی نمیتواند جایگزین کامل جاوا اسکریپت، در تولید برنامههای وب باشد. بنابراین ضروری است که نحوهی یکپارچگی جاوا اسکریپت را با برنامههای مبتنی بر Blazor، بررسی کنیم.
ایجاد کامپوننت جدید BlazorJS
برای بررسی نحوهی تعامل جاوا اسکریپت و Blazor، در ابتدا کامپوننت جدید Pages\LearnBlazor\BlazorJS.razor را ایجاد کرده:
و همچنین مدخل منوی آنرا نیز بر اساس مسیریابی ابتدای فایل این کامپوننت، به فایل Shared\NavMenu.razor اضافه میکنیم:
روش فراخوانی کدهای جاوا اسکریپتی از طریق کدهای سیشارپ Blazor
فرض کنید میخواهیم در حین کلیک بر روی دکمهای مانند دکمهی حذف، ابتدا تائیدیهای را توسط تابع confirm جاوا اسکریپتی، از کاربر اخذ کنیم. روش انجام چنین کاری در برنامههای مبتنی بر Blazor به صورت زیر است:
توضیحات:
- در اینجا میخواهیم تابع استاندارد confirm جاوا اسکریپتی را از طریق کدهای سیشارپ، با کلیک بر روی دکمهی Test Confirm Button، فراخوانی کنیم. به همین جهت onclick@ این دکمه، به متد TestConfirmBox کدهای UI سیشارپ این کامپوننت، متصل شدهاست.
- برای دسترسی به توابع جاوا اسکریپتی، نیاز است سرویس توکار IJSRuntime را به کدهای کامپوننت تزریق کنیم که روش انجام آنرا توسط دایرکتیو inject@ مشاهده میکنید. برای دسترسی به این سرویس توکار، نیاز به تنظیمات ابتدایی خاصی نیست و اینکار پیشتر انجام شدهاست.
- سرویس JsRuntime تزریق شده، دو متد مهم InvokeVoidAsync و InvokeAsync را جهت فراخوانی توابع جاوا اسکریپتی به همراه دارد. اگر تابعی، خروجی غیر void داشته باشد، باید از متد InvokeAsync استفاده کرد. برای مثال خروجی تابع استاندارد confirm، از نوع boolean است. بنابراین نوع این خروجی را به صورت یک آرگومان جنریک متد InvokeAsync مشخص کردهایم.
- اولین پارامتر متد InvokeAsync، نام رشتهای تابع جاوا اسکریپتی است که قرار است صدا زده شود. پارامترهای اختیاری بعدی که به صورت params object?[]? args تعریف شدهاند، لیست نامحدود آرگومانهای ورودی این متد هستند.
- فیلد ConfirmMessage، پیامی را جهت اخذ تائید، تعریف میکند که به عنوان پارامتر متد confirm، توسط JsRuntime.InvokeAsync فراخوانی خواهد شد.
- فیلد ConfirmResult، نتیجهی فراخوانی متد confirm جاوا اسکریپتی را به همراه دارد.
- در اینجا روش عکس العمل نشان دادن به خروجی دریافتی از متد جاوااسکریپتی را نیز مشاهده میکنید. پس از پایان متد TestConfirmBox که یک متد رویدادگران است، همانطور که در مطلب بررسی «چرخهی حیات کامپوننتها» نیز بررسی کردیم، متد StateHasChanged، در پشت صحنه فراخوانی میشود که سبب رندر مجدد UI خواهد شد. بنابراین در حین رندر مجدد UI، بر اساس مقدار جدید ConfirmResult دریافت شدهی از کاربر، با تشکیل یک if/else@، میتوان به نتیجهی تائید یا عدم تائید کاربر، واکنش نشان داد. با این توضیحات در اولین بار نمایش کامپوننت جاری چون مقدار ConfirmResult مساوی false است، پیام زیر را مشاهده میکنیم:
اما در ادامه با کلیک بر روی دکمه و تائید پیام ظاهر شده، عبارت زیر ظاهر میشود:
روش افزودن یک کتابخانهی خارجی جاوا اسکریپتی به پروژههای Blazor
فرض کنید میخواهیم پیامهای برنامه را توسط کتابخانهی معروف جاوا اسکریپتی Toastr نمایش دهیم؛ با این دمو.
مرحلهی اول کار با این کتابخانه، دریافت فایلهای CSS و JS آن است. برای این منظور قصد داریم از برنامهی مدیریت بستههای LibMan استفاده کنیم:
بنابراین خط فرمان را در ریشهی پروژه گشوده و پنج دستور فوق را اجرا میکنیم. دستور اول، ابزار خط فرمان LibMan را نصب میکند. دستور دوم، یک فایل libman.json خالی را در این پوشه ایجاد میکند و سه دستور بعدی، جیکوئری، بوت استرپ و toastr را دریافت و در پوشهی wwwroot/lib قرار میدهند. Toastr برای اجرا، نیاز به jQuery نیز دارد.
البته تعاریف مداخل آنها به فایل libman.json نیز اضافه میشوند. مزیت آن، اجرای دستور libman restore برای بازیابی و نصب مجدد تمام بستههای ذکر شدهی در فایل libman.json است.
پس از دریافت بستههای سمت کلاینت آن، مداخل مرتبط را به فایل Pages\_Host.cshtml برنامهی Blazor Server اضافه خواهیم کرد (و یا در فایل wwwroot/index.html برنامههای Blazor WASM).
مدخل فایل css آنرا در قسمت head و فایل js آنرا پیش از بسته شدن تگ body تعریف میکنیم. در اینجا نیازی به ذکر پوشهی آغازین wwwroot نیست؛ چون base href تعریف شده، به این پوشه اشاره میکند.
یک نکته: میتوان فایل csproj برنامه را به صورت زیر تغییر داد تا کار اجرای دستور libman restore را قبل از build، به صورت خودکار انجام دهد:
روش فراخوانی یک کتابخانهی خارجی جاوا اسکریپتی در پروژههای Blazor
پس از افزودن فایلهای سمت کلاینت toastr و تعریف مداخل آن در فایل Pages\_Host.cshtml برنامهی Blazor Server جاری، اکنون میخواهیم از این کتابخانه استفاده کنیم. یک روش کار با این نوع کتابخانههای عمومی و سراسری به صورت زیر است:
- ابتدا فایل خالی جدید wwwroot\js\common.js را ایجاد میکنیم.
- سپس تابع عمومی و سراسری ShowToastr را بر اساس امکانات کتابخانهی toastr و مستندات آن، به صورت زیر ایجاد میکنیم:
چون تابع ShowToastr به شیء window انتساب داده شدهاست، در سراسر برنامهی جاری قابل دسترسی است.
سطر اول آن هم برای رفع عدم تداخل با بوت استرپ 4x اضافه شدهاست. بوت استرپ 4x به همراه کلاسهای CSS مشابهی است که نیاز است با تنظیم toastClass به مقداری دیگر، این تداخل را برطرف کرد.
- در ادامه مدخل تعریف فایل wwwroot\js\common.js را به انتهای تگ body فایل Pages\_Host.cshtml اضافه میکنیم:
در آخر برای آزمایش آن به کامپوننت Pages\LearnBlazor\BlazorJS.razor مراجعه کرده و تابع سراسری ShowToastr را دقیقا مانند روشی که در مورد تابع confirm بکار بردیم، توسط سرویس JsRuntime، فراخوانی میکنیم:
در اینجا دو دکمه، جهت فراخوانی متد ShowToastr با پارامترهای مختلفی تعریف شدهاند. چون تابع ShowToastr خروجی ندارد، به همین جهت اینبار از متد InvokeVoidAsync استفاده کردهایم. پارامتر اول آن، نام متد ShowToastr است. پارامترهای دوم و سوم آن با آرگومانهای (type, message) تعریف شدهی تابع ShowToastr تطابق دارند. به علاوه در این مثال، روش ارسال پارامترها را نیز در onlick@ توسط arrow functions مشاهده میکنید.
کاهش کدهای تکراری فراخوانی متدهای جاوا اسکریپتی با تعریف متدهای الحاقی
میتوان جهت کاهش تکرار کدهای استفاده از تابع ShowToastr، متدهای الحاقی زیر را برای سرویس IJSRuntime تهیه کرد:
و سپس فضای نام آنرا به فایل Imports.razor_ معرفی نمود تا در تمام کامپوننتهای برنامه قابل استفاده شوند.
به این ترتیب به فراخوانیهای ساده شدهی زیر خواهیم رسید:
فراخوانی یک متد عمومی واقع در کامپوننت فرزند از طریق کامپوننت والد
فرض کنید در کامپوننت فرزند Pages\LearnBlazor\LearnBlazorComponents\ChildComponent.razor که در قسمتهای قبل آنرا تکمیل کردیم، متد عمومی زیر تعریف شدهاست:
اکنون اگر بخواهیم این متد عمومی را از طریق کامپوننت والد یا دربرگیرندهی آن فراخوانی کنیم، نیاز است از مفهوم جدیدی به نام ref استفاده کرد. برای این منظور به کامپوننت Pages\LearnBlazor\ParentComponent.razor مراجعه کرده و تغییرات زیر را اعمال میکنیم:
با استفاده از ref@ که به فیلد ChildComp انتساب داده شدهاست، میتوان ارجاعی از کامپوننت فرزند را (وهلهای از کلاس مرتبط با آنرا) در کامپوننت جاری بدست آورد و سپس از آن جهت فراخوانی متدهای عمومی کامپوننت فرزند استفاده کرد.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-11.zip
ایجاد کامپوننت جدید BlazorJS
برای بررسی نحوهی تعامل جاوا اسکریپت و Blazor، در ابتدا کامپوننت جدید Pages\LearnBlazor\BlazorJS.razor را ایجاد کرده:
@page "/BlazorJS" <h3>BlazorJS</h3> @code { }
<li class="nav-item px-3"> <NavLink class="nav-link" href="BlazorJS"> <span class="oi oi-list-rich" aria-hidden="true"></span> BlazorJS </NavLink> </li>
روش فراخوانی کدهای جاوا اسکریپتی از طریق کدهای سیشارپ Blazor
فرض کنید میخواهیم در حین کلیک بر روی دکمهای مانند دکمهی حذف، ابتدا تائیدیهای را توسط تابع confirm جاوا اسکریپتی، از کاربر اخذ کنیم. روش انجام چنین کاری در برنامههای مبتنی بر Blazor به صورت زیر است:
@page "/BlazorJS" @inject IJSRuntime JsRuntime <h3>BlazorJS</h3> <div class="row"> <button class="btn btn-secondary" @onclick="TestConfirmBox">Test Confirm Button</button> </div> <div class="row"> @if (ConfirmResult) { <p>Confirmation has been made!</p> } else { <p>Confirmation Pending!</p> } </div> @code { string ConfirmMessage = "Are you sure you want to click?"; bool ConfirmResult; async Task TestConfirmBox() { ConfirmResult = await JsRuntime.InvokeAsync<bool>("confirm", ConfirmMessage); } }
- در اینجا میخواهیم تابع استاندارد confirm جاوا اسکریپتی را از طریق کدهای سیشارپ، با کلیک بر روی دکمهی Test Confirm Button، فراخوانی کنیم. به همین جهت onclick@ این دکمه، به متد TestConfirmBox کدهای UI سیشارپ این کامپوننت، متصل شدهاست.
- برای دسترسی به توابع جاوا اسکریپتی، نیاز است سرویس توکار IJSRuntime را به کدهای کامپوننت تزریق کنیم که روش انجام آنرا توسط دایرکتیو inject@ مشاهده میکنید. برای دسترسی به این سرویس توکار، نیاز به تنظیمات ابتدایی خاصی نیست و اینکار پیشتر انجام شدهاست.
- سرویس JsRuntime تزریق شده، دو متد مهم InvokeVoidAsync و InvokeAsync را جهت فراخوانی توابع جاوا اسکریپتی به همراه دارد. اگر تابعی، خروجی غیر void داشته باشد، باید از متد InvokeAsync استفاده کرد. برای مثال خروجی تابع استاندارد confirm، از نوع boolean است. بنابراین نوع این خروجی را به صورت یک آرگومان جنریک متد InvokeAsync مشخص کردهایم.
- اولین پارامتر متد InvokeAsync، نام رشتهای تابع جاوا اسکریپتی است که قرار است صدا زده شود. پارامترهای اختیاری بعدی که به صورت params object?[]? args تعریف شدهاند، لیست نامحدود آرگومانهای ورودی این متد هستند.
- فیلد ConfirmMessage، پیامی را جهت اخذ تائید، تعریف میکند که به عنوان پارامتر متد confirm، توسط JsRuntime.InvokeAsync فراخوانی خواهد شد.
- فیلد ConfirmResult، نتیجهی فراخوانی متد confirm جاوا اسکریپتی را به همراه دارد.
- در اینجا روش عکس العمل نشان دادن به خروجی دریافتی از متد جاوااسکریپتی را نیز مشاهده میکنید. پس از پایان متد TestConfirmBox که یک متد رویدادگران است، همانطور که در مطلب بررسی «چرخهی حیات کامپوننتها» نیز بررسی کردیم، متد StateHasChanged، در پشت صحنه فراخوانی میشود که سبب رندر مجدد UI خواهد شد. بنابراین در حین رندر مجدد UI، بر اساس مقدار جدید ConfirmResult دریافت شدهی از کاربر، با تشکیل یک if/else@، میتوان به نتیجهی تائید یا عدم تائید کاربر، واکنش نشان داد. با این توضیحات در اولین بار نمایش کامپوننت جاری چون مقدار ConfirmResult مساوی false است، پیام زیر را مشاهده میکنیم:
اما در ادامه با کلیک بر روی دکمه و تائید پیام ظاهر شده، عبارت زیر ظاهر میشود:
روش افزودن یک کتابخانهی خارجی جاوا اسکریپتی به پروژههای Blazor
فرض کنید میخواهیم پیامهای برنامه را توسط کتابخانهی معروف جاوا اسکریپتی Toastr نمایش دهیم؛ با این دمو.
مرحلهی اول کار با این کتابخانه، دریافت فایلهای CSS و JS آن است. برای این منظور قصد داریم از برنامهی مدیریت بستههای LibMan استفاده کنیم:
dotnet tool install -g Microsoft.Web.LibraryManager.Cli libman init libman install bootstrap --provider unpkg --destination wwwroot/lib/bootstrap libman install jquery --provider unpkg --destination wwwroot/lib/jquery libman install toastr --provider unpkg --destination wwwroot/lib/toastr
البته تعاریف مداخل آنها به فایل libman.json نیز اضافه میشوند. مزیت آن، اجرای دستور libman restore برای بازیابی و نصب مجدد تمام بستههای ذکر شدهی در فایل libman.json است.
پس از دریافت بستههای سمت کلاینت آن، مداخل مرتبط را به فایل Pages\_Host.cshtml برنامهی Blazor Server اضافه خواهیم کرد (و یا در فایل wwwroot/index.html برنامههای Blazor WASM).
<head> <base href="~/" /> <link rel="stylesheet" href="lib/toastr/build/toastr.min.css" /> </head> <body> <script src="lib/jquery/dist/jquery.min.js"></script> <script src="lib/toastr/build/toastr.min.js"></script> <script src="_framework/blazor.server.js"></script> </body>
یک نکته: میتوان فایل csproj برنامه را به صورت زیر تغییر داد تا کار اجرای دستور libman restore را قبل از build، به صورت خودکار انجام دهد:
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net5.0</TargetFramework> </PropertyGroup> <Target Name="DebugEnsureLibManEnv" BeforeTargets="BeforeBuild" Condition=" '$(Configuration)' == 'Debug' "> <!-- Ensure libman is installed --> <Exec Command="libman --version" ContinueOnError="true"> <Output TaskParameter="ExitCode" PropertyName="ErrorCode" /> </Exec> <Error Condition="'$(ErrorCode)' != '0'" Text="libman is required to build and run this project. To continue, please run `dotnet tool install -g Microsoft.Web.LibraryManager.Cli`, and then restart your command prompt or IDE." /> <Message Importance="high" Text="Restoring dependencies using 'libman'. This may take several minutes..." /> <Exec WorkingDirectory="$(MSBuildProjectDirectory)" Command="libman restore" /> </Target> </Project>
روش فراخوانی یک کتابخانهی خارجی جاوا اسکریپتی در پروژههای Blazor
پس از افزودن فایلهای سمت کلاینت toastr و تعریف مداخل آن در فایل Pages\_Host.cshtml برنامهی Blazor Server جاری، اکنون میخواهیم از این کتابخانه استفاده کنیم. یک روش کار با این نوع کتابخانههای عمومی و سراسری به صورت زیر است:
- ابتدا فایل خالی جدید wwwroot\js\common.js را ایجاد میکنیم.
- سپس تابع عمومی و سراسری ShowToastr را بر اساس امکانات کتابخانهی toastr و مستندات آن، به صورت زیر ایجاد میکنیم:
window.ShowToastr = (type, message) => { // Toastr don't work with Bootstrap 4.2 toastr.options.toastClass = "toastr"; // https://github.com/CodeSeven/toastr/issues/599 if (type === "success") { toastr.success(message, "Operation Successful", { timeOut: 20000 }); } if (type === "error") { toastr.error(message, "Operation Failed", { timeOut: 20000 }); } };
سطر اول آن هم برای رفع عدم تداخل با بوت استرپ 4x اضافه شدهاست. بوت استرپ 4x به همراه کلاسهای CSS مشابهی است که نیاز است با تنظیم toastClass به مقداری دیگر، این تداخل را برطرف کرد.
- در ادامه مدخل تعریف فایل wwwroot\js\common.js را به انتهای تگ body فایل Pages\_Host.cshtml اضافه میکنیم:
<script src="lib/jquery/dist/jquery.min.js"></script> <script src="lib/toastr/build/toastr.min.js"></script> <script src="js/common.js"></script> <script src="_framework/blazor.server.js"></script> </body>
در آخر برای آزمایش آن به کامپوننت Pages\LearnBlazor\BlazorJS.razor مراجعه کرده و تابع سراسری ShowToastr را دقیقا مانند روشی که در مورد تابع confirm بکار بردیم، توسط سرویس JsRuntime، فراخوانی میکنیم:
@page "/BlazorJS" @inject IJSRuntime JsRuntime <div class="row"> <button class="btn btn-success" @onclick="@(()=>TestSuccess("Success Message"))">Test Toastr Success</button> <button class="btn btn-danger" @onclick="@(()=>TestFailure("Error Message"))">Test Toastr Failure</button> </div> @code { async Task TestSuccess(string message) { await JsRuntime.InvokeVoidAsync("ShowToastr", "success", message); } async Task TestFailure(string message) { await JsRuntime.InvokeVoidAsync("ShowToastr", "error", message); } }
کاهش کدهای تکراری فراخوانی متدهای جاوا اسکریپتی با تعریف متدهای الحاقی
میتوان جهت کاهش تکرار کدهای استفاده از تابع ShowToastr، متدهای الحاقی زیر را برای سرویس IJSRuntime تهیه کرد:
using System.Threading.Tasks; using Microsoft.JSInterop; namespace BlazorServerSample.Utils { public static class JSRuntimeExtensions { public static ValueTask ToastrSuccess(this IJSRuntime JSRuntime, string message) { return JSRuntime.InvokeVoidAsync("ShowToastr", "success", message); } public static ValueTask ToastrError(this IJSRuntime JSRuntime, string message) { return JSRuntime.InvokeVoidAsync("ShowToastr", "error", message); } } }
@using BlazorServerSample.Utils
async Task TestSuccess(string message) { //await JsRuntime.InvokeVoidAsync("ShowToastr", "success", message); await JsRuntime.ToastrSuccess(message); }
فراخوانی یک متد عمومی واقع در کامپوننت فرزند از طریق کامپوننت والد
فرض کنید در کامپوننت فرزند Pages\LearnBlazor\LearnBlazorComponents\ChildComponent.razor که در قسمتهای قبل آنرا تکمیل کردیم، متد عمومی زیر تعریف شدهاست:
@inject IJSRuntime JsRuntime @code { // ... public async Task TestSuccess(string message) { await JsRuntime.ToastrSuccess(message); } }
@page "/ParentComponent" <ChildComponent OnClickBtnMethod="ShowMessage" @ref="ChildComp" Title="This title is passed as a parameter from the Parent Component"> <ChildContent> A `Render Fragment` from the parent! </ChildContent> <DangerChildContent> A danger content from the parent! </DangerChildContent> </ChildComponent> <div class="row"> <button class="btn btn-success" @onclick="@(()=>ChildComp.TestSuccess("Done!"))">Show Alert</button> </div> @code { ChildComponent ChildComp; // ... }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-11.zip
این روش پس از امتحان، جواب داد:
اگر متد (base.OnModelCreating(builder را در انتهای کار قرار دهید، تنظیمات پیش فرض کلاس پایه IdentityDbContext (یعنی همان نامهای قدیمی) اعمال میشوند و تنظیمات شما بازنویسی خواهند شد. این متد باید در ابتدای کار فراخوانی شود.
protected override void OnModelCreating(DbModelBuilder builder) { base.OnModelCreating(builder); builder.Entity<ApplicationUser>().ToTable("Users"); builder.Entity<CustomRole>().ToTable("Roles"); builder.Entity<CustomUserClaim>().ToTable("UserClaims"); builder.Entity<CustomUserRole>().ToTable("UserRoles"); builder.Entity<CustomUserLogin>().ToTable("UserLogins"); }
نظرات مطالب
EF Code First #12
این نکته برای برنامههای کنسول و سرویس ویندوز است. در برنامههای WinForms و WPF با بسته شدن فرم، به صورت خودکار این اشیاء هم تخریب میشوند.
برای بررسی بهتر این موضوع، متد فوق را به کلاس Context برنامه اضافه کنید. یک breakpoint روی آن قرار دهید و بعد فرم را باز کرده، با بانک اطلاعاتی کار کنید و سپس فرم را ببندید.
public class MyContext : DbContext { protected override void Dispose(bool disposing) { Debug.WriteLine("MyContext Dispose() is called."); base.Dispose(disposing); } }
اشتراکها