if (User.IsInRole("Admins")) return Redirect("~/Admins/Default"); else if (User.IsInRole("Editors")) return Redirect("~/Editors/Default"); else //...
در مقاله قبلی، درباره نحوه نصب و راه اندازی اولین پروژه Xamarin Forms کمی صحبت کردیم. حال وقت آن رسیدهاست که درباره ساختار اپلیکیشنهای Xamarin Forms بیشتر بحث کنیم. در سیستم عاملهای مختلف، رابطهای کاربری با اسامی مختلفی مانند Control ، Widget ، View و Element صدا زده میشوند که هدف تمامی آنها نمایش و ارتباط با کاربر میباشد. در Xamarin Forms به تمام عناصری که در صفحه نمایش نشان داده میشوند، Visual Elements گفته میشود؛ که در سه گروه بندی اصلی قرار میگیرند:
· Page
· Layout
· View
هر چیزی که فضایی را در صفحه اشغال کند، یک Visual Element است. Xamarin Forms از یک ساختار سلسه مراتبی Parent-Child برای UI استفاده میکند. به طور مثال یک اپلیکیشن را در نظر بگیرید. هر اپلیکیشن به طور کلی از چندین صفحه تشکیل شده است. هر Page برای چینش کنترلهای مختلف، از یک سری Layout استفاده میکند و هر Layout هم شامل چندین View مختلف میباشد.
در مقاله قبلی، پروژه اولیه خود را ساختیم. اگر به پروژه Shared مراجعه کنیم، خواهیم دید که این پروژه دارای کلاسی به نام App است. اگر به خاطر داشته باشید، گفتیم که این کلاس اولین Page درون اپلیکیشن را مشخص میکند و همچنین میتوان برای مدیریت LifeCycle اپلیکیشن مانند OnStart و ... از آن استفاده کرد. در متد سازنده، صفحهای به نام MainPage به عنوان اولین صفحه برنامه مشخص شده بود. به کدهای این صفحه بار دیگر نگاهی کنیم تا بتوانیم کمی بر روی این کدها توضیحاتی را ارائه دهیم:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:PreviewerTest" x:Class="PreviewerTest.MainPage"> <Label Text="Welcome to Xamarin Forms!" VerticalOptions="Center" HorizontalOptions="Center" /> </ContentPage>
همانطور که میبینید ساختار سلسه مراتبی را به خوبی میتوانید در این کدها مشاهده کنید. در وهله اول یک ContentPage را به عنوان والد اصلی مشاهده میکنید. Page ها در Xamarin Forms انواع مختلفی دارند که ContentPage یکی از آنهاست و از آن میتوانید به عنوان یک صفحه ساده استفاده کنید.
در درون این صفحه یک Label را به عنوان Child صفحه مشاهده میکنید (تمامی کنترلها در زمارین در زیر گروه View قرار میگیرند).
نتیجه این کدها صفحهای ساده با یک لیبل است که تمامی صفحه را اشغال کردهاست. اگر شما View دیگری را در زیر این لیبل اضافه کنید خواهید دید که این دو، روی هم میافتند و شما نمیتوانید کنترل زیر آن را مشاهده کنید. همانطور که در بالا گفتیم زمارین از المنتی به نام Layout برای چینش عناصر استفاده میکند. Layout های مختلفی در زمارین وجود دارند که هر کدام به طُرق مختلفی این عناصر را در کنار هم میچینند. یکی از آنها StackLayout میباشد.
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:XamarinSample" x:Class="XamarinSample.MainPage"> <StackLayout> <Label Text="Welcome to Xamarin Forms!" VerticalOptions="Center" HorizontalOptions="Center" /> <Button Text="Ok!"/> </StackLayout> </ContentPage>
StackLayout عناصر فرزند خود را به صورت افقی و عمودی در کنار هم در صفحه میچیند.
اگر به خاطر داشته باشید، در هنگام ساخت پروژه زمارین چندین پروژه برای پلتفرمهای مختلف در کنار آن ساخته شد. پروژه XamarinSample.Android برای ساخت و مدیریت پروژه در پلتفرم اندروید، مورد استفاده قرار میگیرد. همانطور که گفتیم کدهای درون این پروژهها با پروژه Shared ادغام شده و با هم اجرا خواهند شد. وقت آن رسیده که سری به کدهای آن بزنیم و نحوهی اجرای پروژه Shared را توسط پروژه اندروید ببینیم.
وقتی پروژه اندروید را باز کنید با کلاسی به نام MainActivity مواجه خواهید شد. این کلاس وظیفه ایجاد Activity اصلی برنامه را دارد.
namespace XamarinSample.Droid { [Activity(Label = "XamarinSample", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] public class MainActivity: global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity { protected override void OnCreate(Bundle bundle) { TabLayoutResource = Resource.Layout.Tabbar; ToolbarResource = Resource.Layout.Toolbar; base.OnCreate(bundle); global::Xamarin.Forms.Forms.Init(this, bundle); LoadApplication(new XamarinSample.App()); } } }
در Attribute بالای سر کلاس، برخی از ویژگیها مانند تم، آیکن، سایز و ... مقداردهی شدهاند. همچنین باعث میشود که در صورت تغییر Orientation و سایز، Activity از اول ساخته نشود. در متد OnCreate علاوه بر استایل دهی به TabLayout و ToolBar ها متدی به نام Forms.Init صدا زده شده است. این متد استاتیک که در تمامی پروژهها فراخوانی میشود، سیستم Xamarin Forms را در هر کدام از پلت فرمها بارگزاری میکند.
current=1&rowCount=10&sort[sender]=asc&searchPhrase=&id=b0df282a-0d67-40e5-8558-c9e93b7befed
[AttributeUsage(AttributeTargets.Property,Inherited = true)] public class RequestBodyField:Attribute { public string Field; public RequestBodyField(string field) { this.Field = field; } }
public class EmployeesRequestBody { [RequestBodyField("current")] public int CurrentPage { get; set; } [RequestBodyField("rowcount")] public int RowCount { get; set; } [RequestBodyField("searchPhrase")] public string SearchPhrase { get; set; } [RequestBodyField("sort")] public NameValueCollection SortDictionary { get; set; } }
public T GetFromQueryString<T>() where T : new() { var obj = new T(); var queryString = HttpContext.Current.Request.QueryString; var queries = HttpUtility.ParseQueryString(queryString.ToString()); var properties = typeof(T).GetProperties(); foreach (var property in properties) { foreach (Attribute attribute in property.GetCustomAttributes(true)) { var requestBodyField = attribute as RequestBodyField; if (requestBodyField == null) continue; //get value of query string var valueAsString = queries[requestBodyField.Field]; var converter = TypeDescriptor.GetConverter(property.PropertyType); var value = converter.ConvertFrom(valueAsString); if (value == null) continue; property.SetValue(obj, value, null); } } return obj; }
HttpContext.Current.Request.QueryString
سپس در مرحلهی بعدی با استفاده از Reflection پراپرتیهایی را که دارای attribute تعریف شده هستند، پیدا میکنیم.
var converter = TypeDescriptor.GetConverter(property.PropertyType); var value = converter.ConvertFrom(valueAsString);
سپس در صورتی که مقدار صحیح دریافت شود و برابر null نباشد، مقدار را در پراپرتی مربوطه جا میدهیم.
نکتهای که در اینجا نیاز به تلاش بیشتر دارد، کلید sort در کوئری استرینگ است. با نگاهی دقیقتر متوجه میشوید که خود کلید دو مقدار دارد که یکی از مقادیرش با کلید ترکیب شده است. این حالت روش ارسال آرایهها با نام کلیدی متفاوت در کوئری استرینگ است. این حالت ارسال باعث میشود که گرید بتواند حالت multi sort را نیز پیاده سازی کند.
پس برای دریافت این نوع مقادیر کمی کد به آن اضافه میکنیم. برای دریافت مقادیر آرایهای کد زیر را به سیستم اضافه میکنیم:
if (valueAsString == null) { var keys = from key in queries.AllKeys where key.StartsWith(requestBodyField.Field) select key; var collection = new NameValueCollection(); foreach (var key in keys) { var openBraketIndex = key.IndexOf("[", StringComparison.Ordinal); var closeBraketIndex = key.IndexOf("]", StringComparison.Ordinal); if (openBraketIndex < 0 || closeBraketIndex < 0) throw new Exception("query string is corrupted."); openBraketIndex++; //get key in [...] var fieldName = key.Substring(openBraketIndex, closeBraketIndex - openBraketIndex); collection.Add(fieldName, queries[key] ); } property.SetValue(obj, collection, null); continue; }
public T GetFromQueryString<T>() where T : new() { var obj = new T(); var properties = typeof(T).GetProperties(); var queryString = HttpContext.Current.Request.QueryString; var queries = HttpUtility.ParseQueryString(queryString.ToString()); foreach (var property in properties) { foreach (Attribute attribute in property.GetCustomAttributes(true)) { var requestBodyField = attribute as RequestBodyField; if (requestBodyField == null) continue; //get value of query string var valueAsString = queries[requestBodyField.Field]; if (valueAsString == null) { var keys = from key in queries.AllKeys where key.StartsWith(requestBodyField.Field) select key; var collection = new NameValueCollection(); foreach (var key in keys) { var openBraketIndex = key.IndexOf("[", StringComparison.Ordinal); var closeBraketIndex = key.IndexOf("]", StringComparison.Ordinal); if (openBraketIndex < 0 || closeBraketIndex < 0) throw new Exception("query string is corrupted."); openBraketIndex++; //get key in [...] var fieldName = key.Substring(openBraketIndex, closeBraketIndex - openBraketIndex); collection.Add(fieldName, queries[key]); } property.SetValue(obj, collection, null); continue; } var converter = TypeDescriptor.GetConverter(property.PropertyType); var value = converter.ConvertFrom(valueAsString); if (value == null) continue; property.SetValue(obj, value, null); } } return obj; }
حال به صورت زیر این متد را صدا میزنیم:
public virtual ActionResult GetEmployees() { var request = new Requests().GetFromQueryString<EmployeesRequestBody>(); }
متغیرها در ES 6
واژهی کلیدی let
تاکنون به کمک واژهی کلیدی var امکان تعریف متغیرها در جاوا اسکریپت مهیا بودند. برای نمونه در مثال زیر، متغیر x داخل بدنهی if با استفاده از var تعریف شدهاست:
var doWork = function(flag){ if(flag){ var x = 3; } return x; };
زمانیکه از var استفاده میشود، برای یک متغیر دو نوع میدان دید را میتوان متصور شد:
- اگر خارج از بدنهی تابع تعریف شود، این متغیر عمومی خواهد بود.
- اگر داخل بدنهی تابع تعریف شود، میدان دید آن محدود به همان بدنهی تابع میشود. در این حالت چیزی به نام block scope بیمفهوم است. در متد doWork فوق، هرچند متغیر x داخل بدنهی بلاک if تعریف شدهاست، اما این x در کل بدنهی تابع در دسترس است و نه صرفا داخل بلاک if. این مورد تا پیش از ES 6 منشاء بسیاری از باگها بودهاست.
بنابراین در اینجا چون x تعریف شده، میدان دیدی در سطح متد دارد، return x معتبر بوده و در حالت دریافت پارامتر true، مقدار 3 را بر میگرداند و در حالت false هم همچنان مقداری را دریافت خواهیم کرد و این مقدار undefined است (اما پیام خطای عدم دسترسی به x را دریافت نمیکنیم).
به این رفتار اصطلاحا hoisting میگویند. در این حالت موتور جاوا اسکریپت، تمام متغیرهای تعریف شدهی توسط var را به صورت ضمنی به ابتدای تعریف متد منتقل کرده و آنها را در آنجا تعریف میکند. به همین جهت است که return x تعریف شدهی در انتهای متد، قابلیت دسترسی به x داخل بدنهی if را دارد.
در ES 6 برای رفع این مشکل، واژهی کلیدی جدیدی به نام let معرفی شدهاست و هدف آن مهیا کردن block scoping تعریف متغیرها است:
var doWork = function(flag){ if(flag){ let x = 3; } return x; };
بله. همانطور که مشاهده میکنید، اینبار میدان دید x به if block تعریف شدهی در آن محدود گشته و دیگر خارج از آن مفهومی ندارد و تعریف نشدهاست. به همین جهت زمانیکه به return x میرسیم، پیام تعریف نشده بودن x را دریافت خواهیم کرد. برای اینکه قطعه کد فوق کار کند، نیاز است return x را به داخل بدنهی قطعهی if تعریف شده، انتقال داد.
این block scoping مهیا شدهی توسط let، با حلقهی for نیز کار میکند:
var doWork = function(){ for(let i = 0; i< 10; i++){ } /* return i won't work */ return 0; };
یک نکته
مفهوم block scoping با تعریف {} معنا پیدا میکند. بنابراین میتوانید یک قطعهی دلخواه را با تعریف {} نیز مشخص کنید:
و یا در مثال ذیل چندین قطعهی تو در تو را مشاهده میکنید:
let outer = 'I am so eccentric!' { let inner = 'I play with neighbors in my block and the sewers' { let innermost = 'I only play with neighbors in my block' } // accessing innermost here would throw } // accessing inner here would throw // accessing innermost here would throw
نمونهی دیگر آن تعریف یک متد داخل یک بلاک است:
{ let _nested = 'secret' function nested () { return _nested } } console.log(nested())
در ES 6 نمیتوان به متغیرهای تعریف شدهی توسط let داخل یک بلاک، در خارج از آن دسترسی یافت. اگر میخواهید سطح دسترسی به متد را افزایش دهید، نیاز است به شکل ذیل عمل کنید و متد را خارج از بدنهی بلاک با سطح دسترسی بیشتری تعریف نمائید:
var nested; { let _nested = 'secret' nested = function () { return _nested } } console.log(nested()) // <- 'secret'
واژهی کلیدی const
در ES 6 برای ایجاد و مقدار دهی متغیرهای فقط خواندنی، واژهی کلیدی const افزوده شدهاست. در اینجا const نیز مانند let دارای block scoping است.
doWork = function() { const value = 10; value = 11; return value; }
در ES 6، انتساب یک مقدار به یک const، پس از تعریف آن، منجر به بروز خطای syntax error خواهد شد. همچنین تعریف مجدد آن نیز چنین خطایی را سبب خواهد شد.
یک نکته
هر چند const سبب read only شدن یک متغیر میشود، اما آنرا immutable نمیکند:
const items = { people: ['you', 'me'] } items.people.push('test') console.log(items)
همانطور که مشاهده میکنید، هنوز هم میتوان به شیء تعریف شده، آیتمی را اضافه کرد (در اینجا test به آرایهی people اضافه شدهاست).
آشنایی با مفهوم shadowing
همان مثال ابتدای بحث را در نظر بگیرید:
var doWork = function(flag){ if(flag){ let x = 10; var x = 3; return x; } };
let x = 10; var doWork = function(flag){ if(flag){ var x = 3; return x; } };
مثال ذکر شده، با مثال ذیل که یک بلاک را توسط {} ایجاد کردهایم، یکی است:
let x = 10; { let x = 3; console.log(x); } console.log(x);
در اینجا نیز ابتدا مقدار 3 که مرتبط با بلاک داخلی است چاپ خواهد شد و سپس مقدار 10 که مرتبط است به بلاک خارجیتر.
OpenCVSharp #9
در OpenCV با استفاده از مفهومی به نام affine transform، امکان تغییر اندازه و همچنین چرخش تصاویر میسر میشود. در اینجا، تصویر در یک ماتریس دو در سه ضرب میشود تا انتقالات یاد شده، انجام شوند.
private static void rotateImage(double angle, double scale, Mat src, Mat dst) { var imageCenter = new Point2f(src.Cols / 2f, src.Rows / 2f); var rotationMat = Cv2.GetRotationMatrix2D(imageCenter, angle, scale); Cv2.WarpAffine(src, dst, rotationMat, src.Size()); }
برای مشاهدهی بهتر تاثیر پارامترهای مختلف در اینجا، به مثال ذیل دقت کنید:
using OpenCvSharp; using OpenCvSharp.CPlusPlus; namespace OpenCVSharpSample09 { class Program { static void Main(string[] args) { using (var src = new Mat(@"..\..\Images\Penguin.Png", LoadMode.AnyDepth | LoadMode.AnyColor)) using (var dst = new Mat()) { src.CopyTo(dst); using (var window = new Window("Resize/Rotate/Blur", image: dst, flags: WindowMode.AutoSize)) { var angle = 0.0; var scale = 0.7; var angleTrackbar = window.CreateTrackbar( name: "Angle", value: 0, max: 180, callback: pos => { angle = pos; rotateImage(angle, scale, src, dst); window.Image = dst; }); var scaleTrackbar = window.CreateTrackbar( name: "Scale", value: 1, max: 10, callback: pos => { scale = pos / 10f; rotateImage(angle, scale, src, dst); window.Image = dst; }); angleTrackbar.Callback.DynamicInvoke(0); scaleTrackbar.Callback.DynamicInvoke(1); Cv2.WaitKey(); } } } private static void rotateImage(double angle, double scale, Mat src, Mat dst) { var imageCenter = new Point2f(src.Cols / 2f, src.Rows / 2f); var rotationMat = Cv2.GetRotationMatrix2D(imageCenter, angle, scale); Cv2.WarpAffine(src, dst, rotationMat, src.Size()); } } }
در این مثال، مانند مطلب قسمت قبل، ابتدا یک پنجرهی سازگار با C++ API ایجاد شده و سپس دو tracker به آن اضافه شدهاند. این trackers کار دریافت ورودی اطلاعات را از کاربر به عهده دارند (دریافت مقادیر زاویهی چرخش و مقیاس) و مقادیر دریافتی از آنها، در نهایت به متد rotateImage ارسال میشوند. این متد کار چرخش و تغییر مقیاس تصویر اصلی را انجام داده و نتیجه را به تصویر dst کپی میکند. در آخر تصویر dst در پنجره به روز شده و نمایش داده میشود.
تغییر اندازهی تصاویر
اگر صرفا قصد تغییر اندازهی تصاویر را دارید (بدون چرخش آنها)، متد ویژهای به نام Resize برای این منظور تدارک دیده شدهاست:
var resizeTrackbar = window.CreateTrackbar( name: "Resize", value: 1, max: 100, callback: pos => { Cv2.Resize(src, dst, new Size(src.Width + pos, src.Height + pos), interpolation: Interpolation.Cubic); window.Image = dst; });
اگر میخواهید مقادیر پارامترهای چرخشی تصویر نیز در اینجا اعمال شوند، میتوان به نحو ذیل عمل کرد:
var resizeTrackbar = window.CreateTrackbar( name: "Resize", value: 1, max: 100, callback: pos => { rotateImage(angle, scale, src, dst); Cv2.Resize(dst, dst, new Size(src.Width + pos, src.Height + pos), interpolation: Interpolation.Cubic); window.Image = dst; });
مات کردن تصاویر
در OpenCV با استفاده از متدهای GaussianBlur و یا medianBlur ، میتوان تصاویر را مات کرد که نمونهای از آنرا در ادامه ملاحظه میکنید:
var blurTrackbar = window.CreateTrackbar( name: "Blur", value: 1, max: 100, callback: pos => { if (pos % 2 == 0) pos++; rotateImage(angle, scale, src, dst); Cv2.GaussianBlur(dst, dst, new Size(pos, pos), sigmaX: 0); window.Image = dst; });
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
آموزش Prism #3
تفاوتهای پیاده سازی مثال پست قبلی با این پست:
- در مثال قبل پروژه به صورت Desktop و با WPF پیاده سازی شده بود ولی در این مثال با Silverlight میباشد؛
- در مثال قبل از UnityBootstrapper استفاده شده بود ولی در این مثال از MefBootstrapper؛
- در مثال قبل هر View در یک ماژول قرار داشت ولی در این مثال هر دو View را در یک ماژول قرار دادم؛
- در مثال قبل از Prism Libary 2.x استفاده شده بود ولی در این مثال از PrismLibrary 4.x؛
- و...
نکته : برای فهم بهتر مفاهیم، آشنایی اولیه با MEF و مفاهیمی نظیر Export و Import و AggregateCatalog و AssemblyCatalog نیاز است. در صورتی که با این مطالب آشنایی ندارید میتوانید از (^) شروع کنید.
برای شروع یک پروژه Silverlight ایجاد کنید. بعد از اضافه شدن دو پروژه Silverlight و Web، یک Silverlight Class Library جدید بسازید.
ابتدا یک Page ایجاد کنید و کدهای زیر را در آن کپی کنید.
<UserControl x:Class="Module1.Module1View1" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" FlowDirection="RightToLeft" FontFamily="Tahoma"> <StackPanel> <sdk:DataGrid Height="100"> <sdk:DataGrid.Columns> <sdk:DataGridTextColumn Header="کد" Width="50" /> <sdk:DataGridTextColumn Header="عنوان" Width="200" /> <sdk:DataGridTextColumn Header="نویسنده" Width="150" /> </sdk:DataGrid.Columns> </sdk:DataGrid> <Button x:Name="NextViewButton" Width="150" Height="25" Foreground="Red" Background="Blue" Content="لیست طبقه بندی ها" /> </StackPanel> </UserControl>
[Export(typeof(Module1View1))] public partial class Module1View1 : UserControl { [Import] public IRegionManager TheRegionManager { private get; set; } public Module1View1() { InitializeComponent(); NextViewButton.Click += NextViewButton_Click; } void NextViewButton_Click(object sender, RoutedEventArgs e) { TheRegionManager.RequestNavigate ( "MyRegion1", new Uri("Module1View2", UriKind.Relative), a => { } ); } }
حال یک Page دیگر برای طبقه بندی کتابها ایجاد کنید و کدهای زیر را در آن کپی کنید.
<UserControl xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" x:Class="Module1.Module1View2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" FlowDirection="RightToLeft" FontFamily="Tahoma"> <StackPanel> <sdk:DataGrid Height="100"> <sdk:DataGrid.Columns> <sdk:DataGridTextColumn Header="کد" Width="150"/> <sdk:DataGridTextColumn Header="عنوان" Width="150"/> </sdk:DataGrid.Columns> </sdk:DataGrid> <Button x:Name="NextViewButton" Width="150" Height="25" Foreground="Green" Background="Yellow" Content="لیست کتاب ها" /> </StackPanel> </UserControl>
using Microsoft.Practices.Prism.Regions; using System; using System.ComponentModel.Composition; using System.Windows; using System.Windows.Controls; namespace Module1 { [Export] public partial class Module1View2 : UserControl { IRegion _region1; [ImportingConstructor] public Module1View2( [Import] IRegionManager regionManager ) { InitializeComponent(); ViewModel viewModel = new ViewModel(); DataContext = viewModel; viewModel.ShouldNavigateFromCurrentViewEvent += () => { return true; }; _region1 = regionManager.Regions["MyRegion1"]; NextViewButton.Click += NextViewButton_Click; } void NextViewButton_Click( object sender, RoutedEventArgs e ) { _region1.RequestNavigate ( new Uri( "Module1View1", UriKind.Relative ), a => { } ); } } }
نکته: اگر یک کلاس، سازنده با پارامتر داشته باشد باید با استفاده از ImportingConstructor حتما سازنده مورد نظر را هنگام وهله سازی مشخص کنیم در غیر این صورت با Exception مواجه خواهید شد.
using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.ComponentModel.Composition; using Microsoft.Practices.Prism.Regions; namespace Module1 { public class ViewModel : IConfirmNavigationRequest { public event Func<bool> ShouldNavigateFromCurrentViewEvent; public bool IsNavigationTarget( NavigationContext navigationContext ) { return true; } public void OnNavigatedTo( NavigationContext navigationContext ) { } public void OnNavigatedFrom( NavigationContext navigationContext ) { } public void ConfirmNavigationRequest( NavigationContext navigationContext, Action<bool> continuationCallback ) { bool shouldNavigateFromCurrentViewFlag = false; if ( ShouldNavigateFromCurrentViewEvent != null ) shouldNavigateFromCurrentViewFlag = ShouldNavigateFromCurrentViewEvent(); continuationCallback( shouldNavigateFromCurrentViewFlag ); } } }
- IsNavigateTarget : برای تعیین اینکه آیا کلاس پیاده سازی کننده اینترفیس، میتواند عملیات راهبری را مدیریت کند یا نه.
- OnNavigateTo : زمانی عملیات راهبری وارد View شود(بهتره بگم View مورد نظر در Region صفحه لود شود) این متد فراخوانی میشود.
- OnNavigateFrom : زمانی که راهبری از این View خارج میشود (View از حالت لود خارج میشود) این متد فراخوانی خواهد شد.
- ConfirmNavigationRequest : برای تایید عملیات راهبری توسط کلاس پیاده سازی کننده اینترفیس استفاده میشود.
using Microsoft.Practices.Prism.MefExtensions.Modularity; using Microsoft.Practices.Prism.Modularity; using Microsoft.Practices.Prism.Regions; using System.ComponentModel.Composition; namespace Module1 { [ModuleExport(typeof(Module1Impl))] public class Module1Impl : IModule { [Import] public IRegionManager TheRegionManager { private get; set; } public void Initialize() { TheRegionManager.RegisterViewWithRegion("MyRegion1", typeof(Module1View1)); TheRegionManager.RegisterViewWithRegion("MyRegion1", typeof(Module1View2)); } } }
در متد Initialize برای RegionManager دو View ساخته شده را رجیستر کردیم. این کار باید به تعداد Viewهای موجود در ماژول انجام شود.
در پروژه اصلی بک Page به نام Shell ایجاد کنید و کدهای زیر را در آن کپی کنید.
<UserControl x:Class="NavigationViaViewModel.Shell" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:prism="http://www.codeplex.com/prism" FlowDirection="RightToLeft" FontFamily="Tahoma"> <Grid x:Name="LayoutRoot" Background="White"> <TextBlock Text="لیست کتابها به همراه طبقه بندی آن ها" FontSize="19" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Top" /> <ContentControl HorizontalAlignment="Center" VerticalAlignment="Center" prism:RegionManager.RegionName="MyRegion1" /> </Grid> </UserControl>
حال نیاز به یک Bootstrapper داریم. برای این کار یک کلاس به نام TheBootstrapper بسازید:
using Microsoft.Practices.Prism.MefExtensions; using Microsoft.Practices.Prism.Modularity; using System.ComponentModel.Composition.Hosting; using System.Windows; namespace NavigationViaViewModel { public class TheBootstrapper : MefBootstrapper { protected override void InitializeShell() { base.InitializeShell(); Application.Current.RootVisual = (UIElement)Shell; } protected override DependencyObject CreateShell() { return Container.GetExportedValue<Shell>(); } protected override void ConfigureAggregateCatalog() { base.ConfigureAggregateCatalog(); AggregateCatalog.Catalogs.Add(new AssemblyCatalog(this.GetType().Assembly)); } protected override IModuleCatalog CreateModuleCatalog() { ModuleCatalog moduleCatalog = new ModuleCatalog(); moduleCatalog.AddModule ( new ModuleInfo { InitializationMode = InitializationMode.WhenAvailable, Ref = "Module1.xap", ModuleName = "Module1Impl", ModuleType = "Module1.Module1Impl, Module1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" } ); return moduleCatalog; } } }
متد ConfigureAggregateCatalog برای مدیریت کاتالوگها و ماژولها که هر کدام در یک اسمبلی جدا وجود خواهند شد استفاده میشود. در این متد من از AssemblyCatalog استفاده کردم. AssemblyCatalog تمام کلاس هایی که ExportAttribute را به همراه دارند شناسایی میکند و آنها را در Container نگهداری خواهد کرد(^). مانند یک ServiceLocator در Microsoft unity Service Locator(^) .
متد آخر به نام CreateModuleCatalog است و باید در آن تمام ماژولهای برنامه را به کلاس ModuleCatalog اضافه کنیم. در مثال پست قبلی به دلیل استفاده از UnityBootstrapper باید این کار را از طریق BuildEvent ها مدیریت میکردیم ولی در این جا Mef به راحتی این کار را انجام خواهد داد.
تغییرات زیر را در فایل App.Xaml قرار دهید و پروژه را اجرا کنید.
public partial class App : Application { public App() { this.Startup += this.Application_Startup; InitializeComponent(); } private void Application_Startup(object sender, StartupEventArgs e) { var bootstrapper = new TheBootstrapper(); bootstrapper.Run(); } }
دریافت سورس پروژه
ادامه دارد..
اگر ویژگیهای پیشفرض مهیا، پاسخگوی اعتبارسنجی مدنظر نبودند، میتوان یک attribute سفارشی را تهیه کرد:
using System.ComponentModel.DataAnnotations; namespace CustomValidators { [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] public class EmailDomainValidator : ValidationAttribute { public string AllowedDomain { get; set; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { string[] strings = value.ToString().Split('@'); if (strings[1].ToUpper() == AllowedDomain.ToUpper()) { return null; } return new ValidationResult($"Domain must be {AllowedDomain}", new[] { validationContext.MemberName }); } } }
- کار با ارث بری از کلاس پایهی ValidationAttribute شروع میشود و باید متد IsValid آنرا بازنویسی کرد.
- اگر متد IsValid، نال برگرداند، یعنی مشکلی نیست؛ در غیراینصورت خروجی آن باید از نوع ValidationResult باشد.
- پارامتر validationContext اطلاعاتی مانند نام خاصیت در حال بررسی را ارائه میدهد.
- در اینجا متد ()ValidationContext.GetService نال را بر میگرداند؛ یعنی فعلا از تزریق وابستگیها در آن پشتیبانی نمیشود.
و در آخر روش استفادهی از آن، همانند سایر ویژگیهای اعتبارسنجی است:
public class Employee { [EmailDomainValidator(AllowedDomain = "site.com")] public string Email { get; set; } }