<script src="/Asset/Scripts/js?v="></script> <script src="/Asset/Scripts/JQuery/Zebra/js?v="></script> <script src="/Asset/Scripts/Angular/Asset/js?v="></script>
شما برای این کار باید چند مرحله را انجام دهید:
مرحلهی اول: یک پروژهی جدید بسازید
نکته: برای اینکار میتوانید پروژه را از نوع ASP.NET 5 Web Application نیز انتخاب نمایید و مراحل کوتاهتری را طی نمایید. اما راه اندازی دستی قسمتهای مختلف پروژه برای یکبار، به درک بهتر ساختار آن کمک زیادی میکند. از طرفی کار کردن بر روی یک پروژهی تمیز و خالی، برای انتقال دادههای مورد نیاز از یک پروژهی دیگر، از بروز خطاهای پیش بینی نشده و تداخلهای احتمالی نیز جلوگیری میکند.
مرحلهی دوم: اعمال تنظیمات جهت
استفادهی از MVC
همان طور که مشاهده میکنید در این پروژه دیگر خبری از web.config نیست. اما نگران نباشید، امکان اعمال تنظیمات، باز هم وجود دارد و فقط به فایلهای json منتقل شدهاند که project.json هم یکی از آنهاست. برای استفادهی از Microsoft.AspNet.Mvc فقط کافی است فایل project.json را باز کنید و در قسمت "dependencies" پکیج "Microsoft.AspNet.Mvc" را به آن اضافه نمایید تا به صورت خودکار دانلود و به پروژه اضافه گردد.
"dependencies": { "Microsoft.AspNet.Server.IIS": "1.0.0-beta5", "Microsoft.AspNet.Server.WebListener": "1.0.0-beta5", "Microsoft.AspNet.Mvc": "6.0.0-beta5" },
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } public void Configure(IApplicationBuilder app) { app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
<h1>My First MVC6 Website!</h1>
برنامه را اجرا کنید تا خروجی مورد انتظار را مشاهده نمایید.
مرحلهی سوم: انتقال فایلهای پروژهی قبلی به پروژهی جدید
Viewهای مربوط به این controller را نیز به پوشهی Views/Home منتقل نمایید و پروژه را بار دیگر اجرا نمایید. حال باید نمایی از محتوای این فایلها بدون اعمال استایلها را مشاهده نمایید.
همان طور که میدانیم MVC 5 برای استایل صفحات خود از bootstrap استفاده میکند و فایلهای مورد نیاز آن در پوشههای Content و Script -که در root سایت موجود هستند- قرار دارند و نیز ارجاع به این فایلها در Layout.cshtml_ قرار دارد. اما در MVC 6 قضیه کمی متفاوت و البته بهتر شده است. در MVC 6 تمام فایلهای client-side شامل css و js در پوشهی wwwroot قرار دارند و ما میتواینم فایلهای bootstrap و غیره را از پروژهی خود، به این مکان کپی نماییم. ولی روش بهتر، استفاده از ابزارهای bower و gulp برای این کار است. همان طور که میدانید bower یک package manager برای نصب، به روزرسانی و مدیریت کتابخانههای سمت کلاینت و gulp نیز یک task runner برای انجام کارهای مختلف از قبیل script minification و ... در سمت کلاینت است. gulp و bower به طور تو کار در ویژوال 2015 پشتیبانی میشوند و حتی اگر پروژهی خود را از نوع ASP.NET 5 Web Application انتخاب کرده باشید، به صورت پیش فرض از آنها استفاده میکند.
در اینجا برای استفاده، ابتدا یک فایل از نوع Bower JSON Configuration را به root پروژه اضافه کرده و آن را bower.json بنامید و در خاصیت "dependencies" آن bootstrap, jquery, jquery-validation, jquery-validation-unobtrusive را اضافه نمایید.
نکته: من در این قسمت، در restore کردن پکیجها با استفاده از bower، با خطای زیر مواجه شدم:
visual 2015 ECMDERR Failed to execute "git ls-remote --tags --heads git://github.com/jquery/jquery.git", exit code of #-532462766
من از نسخهی Visual Studio 2015 Update 1 CTP استفاده میکنم، ولی ظاهرا این مشکل در نسخههای دیگر هم وجود دارد و فایل bower.cmd ویژوال به درستی کار نمیکند. من برای حل این مشکل، ابتدا git را نصب کردم و در تنظیمات bower، مسیر پیش فرض ویژال رو به مسیر نصب git تغییر دادم. یعنی در پروژه بر روی پوشهی Bower کلیک راست و configure external tools را انتخاب کردم و تیک $(DevEnvDir)\Extensions\Microsoft\Web Tools\External\git را برداشته و در عوض مسیر پیش فرض خودم یعنی C:\Program Files\Git\bin را اضافه کردم.
اگر راه درست و اصولیتری برای حل این مشکل وجود دارد ممنون میشوم دوستان راهنمایی بفرمایند.
خب بعد از اضافه کردن خاصیت dependencies و پکیجهای مورد نیاز، خاصیت exportsOverride را نیز مانند نمونه به فایل bower.json اضافه نمایید.
{ "name": "ASP.NET", "private": true, "dependencies": { "bootstrap": "3.3.6", "jquery": "2.1.4", "jquery-validation": "1.14.0", "jquery-validation-unobtrusive": "3.2.4" }, "exportsOverride": { "bootstrap": { "js": "dist/js/*.*", "css": "dist/css/*.*", "fonts": "dist/fonts/*.*" }, "jquery": { "": "jquery.{js,min.js,min.map}" }, "jquery-validation": { "": "jquery.validate.js" }, "jquery-validation-unobtrusive": { "": "jquery.validate.unobtrusive.{js,min.js}" } } }
"devDependencies": { "gulp": "3.9.0", "rimraf": "2.4.4", "gulp-concat": "2.6.0" }
var gulp = require('gulp'); var rimraf = require('rimraf'); var paths = { bower: "./bower_components/", lib: "./wwwroot/lib/" }; gulp.task('clean', function (callback) { rimraf(paths.lib, callback); }); gulp.task('default', ['clean'], function () { var bower = { "bootstrap": "bootstrap/dist/**/*.{js,map,css,ttf,svg,woff,eot}", "jquery": "jquery/jquery*.{js,map}", "jquery-validation": "jquery-validation/jquery.validate.js", "jquery-validation-unobtrusive": "jquery-validation-unobtrusive/jquery.validate.unobtrusive.js" }; for (var destinationDir in bower) { gulp.src(paths.bower + bower[destinationDir]) .pipe(gulp.dest(paths.lib + destinationDir)); } });
اگر پردازش فوق با موفقیت و بدون خطا انجام شود، میتوانید پکیجهای ایجاد شده را در مسیر wwwroot/lib، مشاهده نمایید.
مرحلهی چهارم: ویرایش برخی از view ها
حال که پکیجهای مورد نیاز پروژه، در پوشهی wwwroot قرار گرفتند، باید view هایی که ارجاعی را به این فایلها دارند، نیز ویرایش نماییم. یکی از این فایلها Layout.cshtml_ است که در مسیر Views/Shared قرار دارد. این فایل را باز کرده و به جای متد ()Styles .Render از عنصر <link> برای لود کردن استایلهای بوت استرپ و غیره استفاده نمایید.
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" /> <link rel="stylesheet" href="~/css/site.css" />
<script src="~/lib/jquery/dist/jquery.js"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
مرحلهی پنجم(اختیاری): جایگزین کردن متدهای Html helper با ساختار Tag
Helper
یکی از ویژگیهای جالب و مفید MVC 6 ساختار TagHalper ها هستند که در واقع جایگزینی برای متدهای HtmlHelper و عملکردی مشابه به آنها دارند. البته استفاده از این ویژگی اجباری نیست ولی اگر تعداد ویوهای شما زیاد نیست و خواهان استفادهی از این قابلیت در پروژهی خود هستید، تنها کاری که باید انجام دهید، پیدا کردن HtmlHelperها و جایگزینی آنها به صورت زیر میباشد:
@Html.TextBoxFor(model => model.Name, new { style = "width: 100px" })
<input asp-for="Name" style="width: 100px" />
نتیجه گیری
همان طور ملاحظه نمودید انتقال پروژه از ASP.NET MVC 5 به ASP.NET MVC 6 شامل انجام چند مرحله است و دشواری خاصی ندارد. عمدهی این تغییرات و پیچیدگیها هم مربوط به انتقال فایلهای client-side و نحوهی کار با ابزارهای مدیریت پکیج میشود و البته تنظیماتی که در این بین باید انجام شوند. البته قسمتهای دیگری مانند تنظیمات bundling و connection string نیز با MVC 5 تفاوت هایی دارد و کار با آنها نیز بسیار ساده میباشد.
Claim type: http://schemas.microsoft.com/claims/authnmethodsreferences - Claim value: pwd Claim type: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier - Claim value: 1 Claim type: auth_time - Claim value: 1651417673 Claim type: http://schemas.microsoft.com/identity/claims/identityprovider - Claim value: local Claim type: name - Claim value: meysanm Claim type: given_name - Claim value: soleymani Claim type: role - Claim value: admin Claim type: http://schemas.microsoft.com/claims/authnmethodsreferences - Claim value: pwd Claim type: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier - Claim value: 1 Claim type: auth_time - Claim value: 1651417673 Claim type: http://schemas.microsoft.com/identity/claims/identityprovider - Claim value: local Claim type: name - Claim value: meysanm Claim type: given_name - Claim value: soleymani Claim type: role - Claim value: admin
[_Host.cshtml] <script src="_framework/blazor.server.js"></script> <script> window.Blazor.defaultReconnectionHandler.onConnectionDown = function () { setTimeout(function () { location.reload(); }, 7000); } window.Blazor.defaultReconnectionHandler._reconnectCallback = function (d) { document.location.reload(); } </script>
برای اینکه بتوانیم ظاهر گرافیکی layoutها را کنترل نماییم، از Theme که مجموعهای از styleهای گرافیکی میباشد، استفاده میکنیم. در اندروید مجموعهای از تمهای از پیش ساخته شده که به آنها Builtin Theme نیز گفته میشود میتوانیم استفاده کنیم. تمها ظاهر گرافیکی کلیه کنترلهای Layout را با نامهای زیر، کنترل میکنند:
statusBarColor textColorPrimary colorAccent ColorPrimary WindowBackground
اگر بخواهیم از 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 را مشخص میکنیم.
[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
ساخت Menu
منظور همان اضافه کردن کتابخانهها است.
قدم دوم:
ابتدا باید گزینههای منو را در یک فایل xml تعریف نمود. هر گزینهی آن از دو بخش متن اصلی منو و ID منو تشکیل شدهاست.
<menu xmlns:android="http://schemas.android.com/apk/res/android">
خصوصیت 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 باشد.
گزینههای منو در کنترلی به نام 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
برای آنکه در 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 اضافه میکنیم. برای آنکه کدهای سی شارپ کنترل کنندهی 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); } } }
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
فرض کنید بخواهید به کاربر این امکان را بدهید تا بتواند چندین گزینه را برای یک فیلد انتخاب کند. به عنوان یک مثال ساده فرض کنید گزینه ای از مدل، پارچههای مورد علاقه یک نفر هست. کاربر میتواند چندین پارچه را انتخاب کند. و این فرض را هم بکنید که به لیست پارچهها گزینه دیگری اضافه نخواهد شد. پارچه (Fabric) را مثلا میتوانیم به صورت زیر تقسیم بندی کنیم :
- پنبه (Cotton)
- ابریشم (Silk)
- پشم (Wool)
- ابریشم مصنوعی (Rayon)
- پارچههای دیگر (Other)
با توجه به اینکه دیگر قرار نیست به این لیست گزینه دیگری اضافه شود میتوانیم آنرا به صورت یک نوع داده شمارشی (Enum) تعریف کنیم. مثلا بدین صورت:
public enum Fabric { [Description("پنبه")] Cotton, [Description("ابریشم")] Silk, [Description("پشم")] Wool, [Description("ابریشم مصنوعی")] Rayon, [Description("پارچههای دیگر")] Other }
حال فرض کنید View Model زیر فیلدی از نوع نوع داده شمارشی Fabric دارد:
public class MyViewModel { public Fabric Fabric { get; set; } }
توجه داشته باشید که فیلد Fabric از کلاس MyViewModel باید چند مقدار را در خود نگهداری کند. یعنی میتواند هر کدام از گزینههای Cotton، Silk، Wool، Rayon، Other به صورت جداگانه یا ترکیبی باشد. اما در حال حاضر با توجه به اینکه یک فیلد Enum معمولی فقط میتواند یک مقدار را در خودش ذخیره کند قابلیت ذخیره ترکیبی مقادیر در فیلد Fabric از View Model بالا وجود ندارد.
اما راه حل این مشکل استفاده از پرچم (Flags) در تعریف نوع داده شمارشی هست. با استفاده از پرچم نوع داده شمارشی بالا به صورت زیر باید تعریف شود:
[Flags] public enum Fabric { [Description("پنبه")] Cotton = 1, [Description("ابریشم")] Silk = 2, [Description("پشم")] Wool = 4, [Description("ابریشم مصنوعی")] Rayon = 8, [Description("پارچههای دیگر")] Other = 128 }
برای آشنایی بیشتر با این موضوع به کدهای زیر نگاه کنید:
Fabric cotWool = Fabric.Cotton | Fabric.Wool; int cotWoolValue = (int) cotWool;
حال فرض کنید فیلد Fabric از View Model ذکر شده (کلاس MyViewModel) را به صورت لیستی از چک باکسها نمایش دهیم. مثل زیر:
برای این کار از پروژه MVC Enum Flags کمک خواهیم گرفت. این پروژه شامل یک Html Helper برای تبدیل یه Enum به یک CheckBoxList و همچنین شامل Model Binder مربوطه هست. البته بعضی از کدهای Html Helper آن احتیاج به تغییر داشت که آنرا انجام دادم ولی بایندر آن بسیار خوب کار میکند.
خوب html helper مربوط به آن به صورت زیر میباشد:
public static IHtmlString CheckBoxesForEnumFlagsFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression) { ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData); Type enumModelType = metadata.ModelType; // Check to make sure this is an enum. if (!enumModelType.IsEnum) { throw new ArgumentException("This helper can only be used with enums. Type used was: " + enumModelType.FullName.ToString() + "."); } // Create string for Element. var sb = new StringBuilder(); foreach (Enum item in Enum.GetValues(enumModelType)) { if (Convert.ToInt32(item) != 0) { var ti = htmlHelper.ViewData.TemplateInfo; var id = ti.GetFullHtmlFieldId(item.ToString()); //Derive property name for checkbox name var body = expression.Body as MemberExpression; var propertyName = body.Member.Name; var name = ti.GetFullHtmlFieldName(propertyName); //Get currently select values from the ViewData model TEnum selectedValues = expression.Compile().Invoke(htmlHelper.ViewData.Model); var label = new TagBuilder("label"); label.Attributes["for"] = id; label.Attributes["style"] = "display: inline-block;"; var field = item.GetType().GetField(item.ToString()); // Add checkbox. var checkbox = new TagBuilder("input"); checkbox.Attributes["id"] = id; checkbox.Attributes["name"] = name; checkbox.Attributes["type"] = "checkbox"; checkbox.Attributes["value"] = item.ToString(); if ((selectedValues as Enum != null) && ((selectedValues as Enum).HasFlag(item))) { checkbox.Attributes["checked"] = "checked"; } sb.AppendLine(checkbox.ToString()); // Check to see if DisplayName attribute has been set for item. var displayName = field.GetCustomAttributes(typeof(DisplayNameAttribute), true) .FirstOrDefault() as DisplayNameAttribute; if (displayName != null) { // Display name specified. Use it. label.SetInnerText(displayName.DisplayName); } else { // Check to see if Display attribute has been set for item. var display = field.GetCustomAttributes(typeof(DisplayAttribute), true) .FirstOrDefault() as DisplayAttribute; if (display != null) { label.SetInnerText(display.Name); } else { label.SetInnerText(item.ToDescription()); } } sb.AppendLine(label.ToString()); // Add line break. sb.AppendLine("<br />"); } } return new HtmlString(sb.ToString()); }
public static string ToDescription(this Enum value) { var attributes = (DescriptionAttribute[])value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false); return attributes.Length > 0 ? attributes[0].Description : value.ToString(); }
@Html.CheckBoxesForEnumFlagsFor(x => x.Fabric)
که باعث تولید خروجی که در تصویر (الف) نشان داده شد میشود. و همچنین مدل بایندر مربوط به آن به صورت زیر هست:
public class FlagEnumerationModelBinder : DefaultModelBinder { public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { if (bindingContext == null) throw new ArgumentNullException("bindingContext"); if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)) { var values = GetValue<string[]>(bindingContext, bindingContext.ModelName); if (values.Length > 1 && (bindingContext.ModelType.IsEnum && bindingContext.ModelType.IsDefined(typeof(FlagsAttribute), false))) { long byteValue = 0; foreach (var value in values.Where(v => Enum.IsDefined(bindingContext.ModelType, v))) { byteValue |= (int)Enum.Parse(bindingContext.ModelType, value); } return Enum.Parse(bindingContext.ModelType, byteValue.ToString()); } else { return base.BindModel(controllerContext, bindingContext); } } return base.BindModel(controllerContext, bindingContext); } private static T GetValue<T>(ModelBindingContext bindingContext, string key) { if (bindingContext.ValueProvider.ContainsPrefix(key)) { ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(key); if (valueResult != null) { bindingContext.ModelState.SetModelValue(key, valueResult); return (T)valueResult.ConvertTo(typeof(T)); } } return default(T); } }
این مدل بایندر را باید به این صورت در متد Application_Start فایل Global.asax فراخوانی کنیم:
ModelBinders.Binders.Add(typeof(Fabric), new FlagEnumerationModelBinder());
مشاهده میکنید که در اینجا دقیقا مشخص کرده ایم که این مدل بایندر برای نوع داده شمارشی Fabric هست. اگر نیاز دارید تا این بایندر برای نوع دادههای شمارشی دیگری نیز به کار رود نیاز هست تا این خط کد را برای هر کدام از آنها تکرار کنید. اما راه حل بهتر این هست که کلاسی به صورت زیر تعریف کنیم و تمامی نوع دادههای شمارشی که باید از بایندر بالا استفاده کنند را در یک پراپرتی آن برگشت دهیم. مثلا بدین صورت:
public class ModelEnums { public static IEnumerable<Type> Types { get { var types = new List<Type> { typeof(Fabric) }; return types; } } }
سپس به متد Application_Start رفته و کد زیر را اضافه میکنیم:
foreach (var type in ModelEnums.Types) { ModelBinders.Binders.Add(type, new FlagEnumerationModelBinder()) }
اگر گزینههای پشم و ابریشم مصنوعی را از CheckBoxList تولید شده انتخاب کنیم، بدین صورت:
شکل (ب)
و سپس فرم را پست کنید، موردی شبیه زیر مشاهده میکنید:
همچنین مقدار عددی معادل در این جا برابر 12 میباشد که از جمع دو مقدار Wool و Rayon به دست آمده است. بدین ترتیب در یک فیلد از مدل، گزینههای انتخابی توسط کاربر قرار گرفته شده اند.
پروژه مربوط به این مثال را از لینک زیر دریافت کنید:
MvcEnumFlagsProjectSample.zip
پی نوشت : پوشههای bin و obj و packages جهت کاهش حجم پروژه از آن حذف شده اند. برای بازسازی پوشه packages لطفا به مطلب بازسازی کامل پوشه packages بستههای NuGet به صورت خودکار مراجعه کنید.
جدول HTML و JQuery
table#tblUsers.table.table-striped.table-bordered.table-hover>thead>tr>th{row}+th{Name}+th{Last Name}+th{Operations}^^tbody>tr*6>(td{row}+td{FirstName}+td{LastName}+td>button.btn.btn-primary{Edit}+button.btn.btn-danger{Delete})
<table id="tblUsers" class="table table-striped table-bordered table-hover"> <thead> <tr> <th>row</th> <th>Name</th> <th>Last Name</th> <th>Operations</th> </tr> </thead> <tbody> <tr> <td>row</td> <td>FirstName</td> <td>LastName</td> <td> <button>Edit</button> <button>Delete</button></td> </tr> <tr> <td>row</td> <td>FirstName</td> <td>LastName</td> <td> <button class="btn btn-primary">Edit</button> <button class="btn btn-danger">Delete</button></td> </tr> <tr> <td>row</td> <td>FirstName</td> <td>LastName</td> <td> <button class="btn btn-primary">Edit</button> <button class="btn btn-danger">Delete</button></td> </tr> <tr> <td>row</td> <td>FirstName</td> <td>LastName</td> <td> <button class="btn btn-primary">Edit</button> <button class="btn btn-danger">Delete</button></td> </tr> <tr> <td>row</td> <td>FirstName</td> <td>LastName</td> <td> <button class="btn btn-primary">Edit</button> <button class="btn btn-danger">Delete</button></td> </tr> <tr> <td>row</td> <td>FirstName</td> <td>LastName</td> <td> <button class="btn btn-primary">Edit</button> <button class="btn btn-danger">Delete</button></td> </tr> </tbody> </table>
ul>li[ng-repeat="user in Users"]>p[ng-bind="{{user.UserName}}"]+a{Details}
<ul> <li ng-repeat="user in Users"> <p ng-bind="{{user.UserName}}"></p> <a href="">Details</a> </li> </ul>
<!-- Type this --> div.container <!-- Creates this --> <div class="container"></div>
<!-- Type this --> ul#userList <!-- Creates this --> <ul id="userList"></ul>
<!-- Type this --> div.container#wrapper <!-- Creates this --> <div class="container" id="wrapper"></div>
<!-- Type this --> button.btn.btn-primary <!-- Creates this --> <button class="btn btn-primary"></button>
<!-- Type this --> .container <!-- Creates this --> <div class="container"></div>
<!-- Type this --> div.container>div#header <!-- Creates this --> <div class="container"> <div id="header"></div> </div>
<!-- Type this --> div.navbar>div.navbar-inner>ul.navbar <!-- Creates this --> <div class="navbar"> <div class="navbar-inner"> <ul class="navbar"></ul> </div> </div>
<!-- Type this --> div[title] <!-- Creates this --> <div title=""></div>
<!-- Type this --> input[type="text" placeholder="First Name"] <!-- Creates this --> <input type="text" value="" placeholder="First Name" />
<!-- Type this --> ul[ng-repeat="user in Users"]>li[ng-model="user.FirstName"] <!-- Creates this --> <ul ng-repeat="user in Users"> <li ng-model="user.FirstName"></li> </ul>
{} متن (Text)
این عملگر، متن مورد نظر شما را داخل عنصر قرار میدهد.
<!-- Type this --> div>h1{My Title} <!-- Creates this --> <div> <h1>My Title</h1> </div>
<!-- Type this --> form>input[type="text"]+input[type="checkbox"] <!-- Creates this --> <form> <input type="text" value="" /> <input type="checkbox" value="" /> </form>
<!-- Type this --> div#header>img+h1{Title} <!-- Creates this --> <div id="header"> <img src="" alt="" /> <h1>Title</h1> </div>
<!-- Type this --> table>thead>tr>th{row}^^tbody>tr>td{row1} <!-- Creates this --> <table> <thead> <tr> <th>row</th> </tr> </thead> <tbody> <tr> <td>row1</td> </tr> </tbody> </table>
<!-- Type this --> ul>li*3>p{Hello} <!-- Creates this --> <ul> <li> <p> Hello </p> </li> <li> <p> Hello </p> </li> <li> <p> Hello </p> </li> </ul>
<!-- Type this --> (div.container>h1{title}+div.content{Some Text Here})*2 <!-- Creates this --> <div class="container"> <h1>title</h1> <div class="content"> Some Text Here </div> </div> <div class="container"> <h1>title</h1> <div class="content"> Some Text Here </div> </div>
<!-- Type this --> ul>li*2>p{item $} <!-- Creates this --> <ul> <li> <p> item 1 </p> </li> <li> <p> item 2 </p> </li> </ul>
<!-- Type this --> ul>li*2>p{item $$$} <!-- Creates this --> <ul> <li> <p> item 001 </p> </li> <li> <p> item 002 </p> </li> </ul>
<!-- Type this --> div>(header>div)+section>(ul>li*2>a)+footer>(div>span) <!-- Creates this --> <div> <header> <div></div> </header> <section> <ul> <li><a href=""></a></li> <li><a href=""></a></li> </ul> <footer> <div> <span></span> </div> </footer> </section> </div>
<!-- Type this --> div>(h1>lorem5)+(h3>lorem3) <!-- Creates this --> <div> <h1>Lorem ipsum dolor sit amet.</h1> <h3>Lorem ipsum dolor.</h3> </div>
<script src="//code.jquery.com/jquery-1.11.3.min.js"></script> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css"> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script> <link href="~/content/css/bootstrap-dialog.min.css" rel=stylesheet"></link> <script src="~/Scripts/bootstrap-dialog.min.js"></script>
var dialog=new BootstrapDialog(); dialog.Title="عنوان دیالوگ"; dialog.Message="متن پنجره"; //فعال سازی این خصوصیت باعث میشود یک دکمه بستن به //پنجره اضافه شده و همچنین توسط کلیک کاربر در خارج از صفحه //باعث بسته شدن پنجره شود یا استفاده از کلید //ESC dialog.Closable=false; //تغییر اندازه دیالوگ Dialog.Size=BootstrapDialogSize.SizeNormal; //رنگ بندی دیالوگ را تغییر میدهد.مقدار زیر باعث میشود //دیالوگ با رنگبندی قرمز نمایش داده شود تا برای نمایش خطاها مناسب باشد Dialog.Type=BootstrapDialogType.Danger; //برای اعمال کردن یک کلاس استابل دلخواه Dialog.CssClass=""; //آیکن برای دیالوگ-استفاده از نام کلاس آیکنهای بوت استراپ Dialog.SpinIcon=""; //یک توصیف است که فقط در کد صفحه نمایش داده میشود //استفاده خاصی ندارد Dialog.Description=""; //بعد از بستن دیالوگ ، کدهای آن در صفحه حذف خواهند شد //اگر میخواهید کد را بارها و بارها نمایش دهید //آن را با مقدار ناصحیح مقدار دهی کنید dialog.AutoDestory=false; //========== رویدادها ============= //این رویدا قبل از نمایش دیالوگ نمایش داده میشود dialog.OnShow="function(){alert('before Dialog');}"; //این رویداد بعد از نمایش دیالوگ اجرا میشود dialog.OnShown="function(){alert('after Dialog shown');}"; //موقع درخواست بستن دیالوگ قبل از بسته شدن اجرا میگردد dialog.OnHide="function(){alert('before Dialog close');}"; //بعد از بسته شدن دیالوگ اجرا میشود dialog.OnHidden="function(){alert('after Dialog close');}";
@{ var dialog=new BootstrapDialog(); dialog.... // ........ } @HTML.BootstrapDialog("example1",dialog)
var dialog=new BootstrapDialog(); var cancelButton=new BootstrapDialogButton("cancelButton"); //cancelButton.id="cancelButton"; cancelButton.label="Cancel"; cancelButton.Key=65; cancelButton.Action="function(){alert('You Clicked!');}"; dialog.AddButton(cancelButton);
dialog.RemoveButton("cancelButton");
داده ها
dialog.AddData("key","value");
dialog.RemoveData("key");
متدها
- دستی: که میتوانید اطلاعات متدها را در همان صفحه مثال و مستندات ببینید و از نامی که به دکمهها و پنجرهها میدهید آنها را اعمال کنید.
- با استفاده از کلاس: کلاس ما شامل دو متد دیگر برای کنترل متدها میباشد. حدود 13 متد در آن پشتیبانی میشود که باعث میشود در بسیاری از اوقات دیگری نیازی به دانستن نام متدها نداشته باشید. یکی از متدها برای استفاده در Helper طراحی شده است که خروجی آن از نوع MvcHtmlString است و متد دیگر خروجی string دارند که میتوانید در صورتیکه خواستید، در رویدادها و خارج از Html Helper از آن استفاده کنید.
$( "#btnshowpopup" ).click(function() { @HTML.RunBootstrapDialogMethod("example1",BootstrapDialogMethods.Open) });
$( "#btnshowpopup" ).click(function() { @HTML.RunBootstrapDialogMethod("example1",BootstrapDialogMethods.SetData,new{"key","value"}) });
cancelButton.Action="function(){{{0}}}"; cancelButton.Action=string.format(cancelButton.Action,RunBootstrapDialogMethod("example1",BootstrapDialogMethods.Close));
به عنوان یک مثال نهایی کد زیر را نوشته که نتیجه آن را در تصویر زیر میبینم:
@{ const string dialogName = "errorDialog"; var cancelButton = new BootstrapDialogButton(); cancelButton.Id = "btncancel"; cancelButton.Label = "بستن"; cancelButton.Action = "function(){{{0}}}"; cancelButton.Action = String.Format(cancelButton.Action, Dialogs.RunBootstrapDialogMethod(dialogName, BootstrapDialogMethods.Close)); var dialog = new BootstrapDialog(); dialog.AddButton(cancelButton); dialog.Title = "عنوان"; dialog.Message = "پیام هشدار"; dialog.DialogType=BootstrapDialogType.Warning; dialog.DialogSize=BootstrapDialogSize.SizeNormal; dialog.Closable = false; dialog.AddData("data1","5"); } @Html.BootstrapDialog(dialogName, dialog) @Html.RunBootstrapDialogMethod(dialogName,BootstrapDialogMethods.Open);
مدل برنامه، جهت تامین دادههای خود ارجاع دهنده و درختی
فرض کنید قصد داریم لیستی از کامنتهای تو در تو را مدل سازی کنیم که در آن هر کامنت، میتواند چندین کامنت تا بینهایت سطح تو در تو را داشته باشد:
namespace BlazorTreeView.ViewModels; public class Comment { public IList<Comment> Comments = new List<Comment>(); public string? Text { set; get; } }
using BlazorTreeView.ViewModels; namespace BlazorTreeView.Pages; public partial class TreeView { private IReadOnlyDictionary<string, object> ChildrenHtmlAttributes { get; } = new Dictionary<string, object>(StringComparer.Ordinal) { { "style", "list-style: none;" }, }; private IList<Comment> Comments { get; } = new List<Comment> { new() { Text = "پاسخ یک", }, new() { Text = "پاسخ دو", Comments = new List<Comment> { new() { Text = "پاسخ اول به پاسخ دو", Comments = new List<Comment> { new() { Text = "پاسخی به پاسخ اول پاسخ دو", }, }, }, new() { Text = "پاسخ دوم به پاسخ دو", }, }, }, new() { Text = "پاسخ سوم", }, }; }
طراحی کامپوننت DntTreeView
برای اینکه بتوانیم به یک کامپوننت با قابلیت استفادهی مجدد بررسیم، کدهای نمایش اطلاعات تو در تو و درختی را توسط کامپوننت سفارشی DntTreeView پیاده سازی خواهیم کرد. پیشنیازهای آن نیز به صورت زیر است:
- این کامپوننت باید جنریک باشد؛ یعنی باید به صورت زیر شروع شود:
/// <summary> /// A custom DntTreeView /// </summary> public partial class DntTreeView<TRecord> {
/// <summary> /// The treeview's self-referencing items /// </summary> [Parameter] public IEnumerable<TRecord>? Items { set; get; }
/// <summary> /// The treeview item's template /// </summary> [Parameter] public RenderFragment<TRecord>? ItemTemplate { set; get; }
/// <summary> /// The content displayed if the list is empty /// </summary> [Parameter] public RenderFragment? EmptyContentTemplate { set; get; }
public class Comment { public IList<Comment> Comments = new List<Comment>(); public string? Text { set; get; } }
/// <summary> /// The property which returns the children items /// </summary> [Parameter] public Expression<Func<TRecord, IEnumerable<TRecord>>>? ChildrenSelector { set; get; }
<DntTreeView TRecord="Comment" Items="Comments" ChildrenSelector="m => m.Comments"
public partial class DntTreeView<TRecord> { private Expression? _lastCompiledExpression; internal Func<TRecord, IEnumerable<TRecord>>? CompiledChildrenSelector { private set; get; } // ... protected override void OnParametersSet() { if (_lastCompiledExpression != ChildrenSelector) { CompiledChildrenSelector = ChildrenSelector?.Compile(); _lastCompiledExpression = ChildrenSelector; } } }
@namespace BlazorTreeView.Pages.Components @typeparam TRecord @if (Items is null || !Items.Any()) { @EmptyContentTemplate } else { <CascadingValue Value="this"> <ul @attributes="AdditionalAttributes"> @foreach (var item in Items) { <DntTreeViewChildrenItem TRecord="TRecord" ParentItem="item"/> } </ul> </CascadingValue> }
- نمایش توسط کامپوننت دومی به نام DntTreeViewChildrenItem انجام میشود که آنهم جنریک است و شیء item جاری را توسط خاصیت ParentItem دریافت میکند.
- در اینجا یک CascadingValue اشاره کننده به شیء this را هم مشاهده میکنید. این روش، یکی از روشهای اجازه دادن دسترسی به خواص و امکانات یک کامپوننت والد، در کامپوننتهای فرزند است که در ادامه از آن استفاده خواهیم کرد.
تکمیل کامپوننت بازگشتی DntTreeViewChildrenItem.razor
اگر به حلقهی foreach (var item in Items) در کامپوننت DntTreeView.razor دقت کنید، یک سطح را بیشتر پوشش نمیدهد؛ اما کامنتهای ما چندسطحی و تو در تو هستند و عمق آنها هم مشخص نیست. به همین جهت نیاز است به نحوی بتوان یک طراحی recursive و بازگشتی را در کامپوننتهای Blazor داشت که خوشبختانه این مورد پیشبینی شدهاست و هر کامپوننت Blazor، میتواند خودش را نیز فراخوانی کند:
@namespace BlazorTreeView.Pages.Components @typeparam TRecord <li @attributes="@SafeOwnerTreeView.ChildrenHtmlAttributes" @key="ParentItem?.GetHashCode()"> @if (SafeOwnerTreeView.ItemTemplate is not null && ParentItem is not null) { @SafeOwnerTreeView.ItemTemplate(ParentItem) } @if (Children is not null) { <ul> @foreach (var item in Children) { <DntTreeViewChildrenItem TRecord="TRecord" ParentItem="item"/> } </ul> } </li>
کدهای پشت صحنهی این کامپوننت یعنی فایل DntTreeViewChildrenItem.razor.cs به صورت زیر است:
/// <summary> /// A custom DntTreeView /// </summary> public partial class DntTreeViewChildrenItem<TRecord> { /// <summary> /// Defines the owner of this component. /// </summary> [CascadingParameter] public DntTreeView<TRecord>? OwnerTreeView { get; set; } private DntTreeView<TRecord> SafeOwnerTreeView => OwnerTreeView ?? throw new InvalidOperationException("`DntTreeViewChildrenItem` should be placed inside of a `DntTreeView`."); /// <summary> /// Nested parent item to display /// </summary> [Parameter] public TRecord? ParentItem { set; get; } private IEnumerable<TRecord>? Children => ParentItem is null || SafeOwnerTreeView.CompiledChildrenSelector is null ? null : SafeOwnerTreeView.CompiledChildrenSelector(ParentItem); }
یعنی این کامپوننت ابتدا ParentItem، یا اولین سطح ممکن و در دسترس را رندر میکند. سپس با استفاده از Expression Func مهیای در کامپوننت والد، شیء فرزند را در صورت وجود یافته و سپس به صورت بازگشتی آنرا با فراخوانی مجدد خودش ، رندر میکند.
روش استفاده از کامپوننت DntTreeView
اکنون که کار توسعهی کامپوننت جنریک DntTreeView پایان یافت، روش استفادهی از آن به صورت زیر است:
<div class="card" dir="rtl"> <div class="card-header"> DntTreeView </div> <div class="card-body"> <DntTreeView TRecord="Comment" Items="Comments" ChildrenSelector="m => m.Comments" style="list-style: none;" ChildrenHtmlAttributes="ChildrenHtmlAttributes"> <ItemTemplate Context="record"> <div class="card mb-1"> <div class="card-body"> <span>@record.Text</span> </div> </div> </ItemTemplate> <EmptyContentTemplate> <div class="alert alert-warning"> There is no item to display! </div> </EmptyContentTemplate> </DntTreeView> </div> </div>
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorTreeView.zip
کامپوننت توسعه یافتهی در اینجا در هر دو حالت Blazor WASM و Blazor Server کار میکند.