درختهای دودویی Binary Trees
نحوه پیمایش درخت دودویی
این درخت پیمایشهای گوناگونی دارد ولی سه تای آنها اصلیتر و مهمتر هستند:
In-order یا LVR (چپ، ریشه، راست): در این حالت ابتدا گرههای سمت چپ ملاقات (چاپ) میشوند و سپس ریشه و بعد گرههای سمت راست.
Pre-Order یا VLR (ریشه، چپ، راست) : در این حالت ابتدا گرههای ریشه ملاقات میشوند. بعد گرههای سمت چپ و بعد گرههای سمت راست.
Post_Order یا LRV (چپ، راست، ریشه ): در این حالت ابتدا گرههای سمت چپ، بعد راست و نهایتا ریشه، ملاقات میشوند.
حتما متوجه شدهاید که منظور از v در اینجا ریشه است و با تغییر و جابجایی مکان این سه حرف RLV میتوانید به ترکیبهای مختلفی از پیمایش دست پیدا کنید.
اجازه دهید روی شکل بالا پیمایش LVR را انجام دهیم: همانطور که گفتیم باید اول گرههای سمت چپ را خواند، پس از 17 به سمت 9 حرکت میکنیم و میبینیم که 9، خود والد است. پس به سمت 6 حرکت میکنیم و میبینیم که فرزند چپی ندارد؛ پس خود 6 را ملاقات میکنیم، سپس فرزند راست را هم بررسی میکنیم که فرزند راستی ندارد پس کار ما اینجا تمام است و به سمت بالا حرکت میکنیم. 9 را ملاقات میکنیم و بعد عدد 5 را و به 17 بر میگردیم. 17 را ملاقات کرده و سپس به سمت 15 میرویم و الی آخر ...
6-9-5-17-8-15-10
VLR:
17-9-6-5-15-8-10
LRV:
6-5-9-8-10-15-17
نحوه پیاده سازی درخت دودویی:
public class BinaryTree<T> { /// <summary>مقدار داخل گره</summary> public T Value { get; set; } /// <summary>فرزند چپ گره</summary> public BinaryTree<T> LeftChild { get; private set; } /// <summary>فرزند راست گره</summary> public BinaryTree<T> RightChild { get; private set; } /// <summary>سازنده کلاس</summary> /// <param name="value">مقدار گره</param> /// <param name="leftChild">فرزند چپ</param> /// <param name="rightChild">فرزند راست /// </param> public BinaryTree(T value, BinaryTree<T> leftChild, BinaryTree<T> rightChild) { this.Value = value; this.LeftChild = leftChild; this.RightChild = rightChild; } /// <summary>سازنده بدون فرزند /// </summary> /// <param name="value">the value of the tree node</param> public BinaryTree(T value) : this(value, null, null) { } /// <summary>LVR پیمایش</summary> public void PrintInOrder() { // ملاقات فرزندان زیر درخت چپ if (this.LeftChild != null) { this.LeftChild.PrintInOrder(); } // ملاقات خود ریشه Console.Write(this.Value + " "); // ملاقات فرزندان زیر درخت راست if (this.RightChild != null) { this.RightChild.PrintInOrder(); } } } /// <summary> /// نحوه استفاده از کلاس بالا /// </summary> public class BinaryTreeExample { static void Main() { BinaryTree<int> binaryTree = new BinaryTree<int>(14, new BinaryTree<int>(19, new BinaryTree<int>(23), new BinaryTree<int>(6, new BinaryTree<int>(10), new BinaryTree<int>(21))), new BinaryTree<int>(15, new BinaryTree<int>(3), null)); binaryTree.PrintInOrder(); Console.WriteLine(); // خروجی // 23 19 10 6 21 14 3 15 } }
تفاوتی که این کد با کد قبلی که برای یک درخت معمولی داشتیم، در این است که قبلا لیستی از فرزندان را داشتیم که با خاصیت Children شناخته میشدند، ولی در اینجا در نهایت دو فرزند چپ و راست برای هر گره وجود دارند. برای جست و جو هم از الگوریتم In_Order استفاده کردیم که از همان الگوریتم DFS آمدهاست. در آنجا هم ابتدا گرههای سمت چپ به صورت بازگشتی صدا زده میشدند. بعد خود گره و سپس گرههای سمت راست به صورت بازگشتی صدا زده میشدند.
برای باقی روشهای پیمایش تنها نیاز است که این سه خط را جابجا کنید:
// ملاقات فرزندان زیر درخت چپ if (this.LeftChild != null) { this.LeftChild.PrintInOrder(); } // ملاقات خود ریشه Console.Write(this.Value + " "); // ملاقات فرزندان زیر درخت راست if (this.RightChild != null) { this.RightChild.PrintInOrder(); }
درخت دودویی مرتب شده Ordered Binary Search Tree
تا این لحظه ما با ساخت درختهای پایه آشنا شدیم: درخت عادی یا کلاسیک و درخت دو دویی. ولی در بیشتر موارد در پروژههای بزرگ از اینها استفاده نمیکنیم چرا که استفاده از آنها در پروژههای بزرگ بسیار مشکل است و باید به جای آنها از ساختارهای متنوع دیگری از قبیل درختهای مرتب شده، کم عمق و متوازن و کامل و پر و .. استفاده کرد. پس اجازه دهید که مهمترین درختهایی را که در برنامه نویسی استفاده میشوند، بررسی کنیم.
همان طور که میدانید برای مقایسه اعداد ما از علامتهای <>= استفاده میکنیم و اعداد صحیح بهترین اعداد برای مقایسه هستند. در درختهای جست و جوی دو دویی یک خصوصیت اضافه به اسم کلید هویت یکتا Unique identification Key داریم که یک کلید قابل مقایسه است. در تصویر زیر ما دو گره با مقدارهای متفاوتی داریم که با مقایسهی آنان میتوانیم کوچک و بزرگ بودن یک گره را محاسبه کنیم. ولی به این نکته دقت داشته باشید که این اعداد داخل دایرهها، دیگر برای ما حکم مقدار ندارند و کلیدهای یکتا و شاخص هر گره محسوب میشوند.
خلاصهی صحبتهای بالا: در هر درخت دودویی مرتب شده، گرههای بزرگتر در زیر درخت راست قرار دارند و گرههای کوچکتر در زیر درخت چپ قرار دارند که این کوچکی و بزرگی بر اساس یک کلید یکتا که قابل مقایسه است استفاده میشود.
این درخت دو دویی مرتب شده در جست و جو به ما کمک فراوانی میکند و از آنجا که میدانیم زیر درختهای چپ مقدار کمتری دارند و زیر درختهای راست مقدار بیشتر، عمل جست و جو، مقایسههای کمتری خواهد داشت، چرا که هر بار مقایسه یک زیر درخت کنار گذاشته میشود.
برای مثال فکر کنید میخواهید عدد 13 را در درخت بالا پیدا کنید. ابتدا گره والد 19 را مقایسه کرده و از آنجا که 19 بزرگتر از 13 است میدانیم که 13 را در زیر درخت راست پیدا نمیکنیم. پس زیر درخت چپ را مقایسه میکنیم (بنابراین به راحتی یک زیر درخت از مقایسه و عمل جست و جو کنار گذاشته شد). سپس گره 11 را مقایسه میکنیم و از آنجا که 11 کوچکتر از 13 هست، زیر درخت سمت راست را ادامه میدهیم و چون 16 بزرگتر از 13 هست، زیر درخت سمت چپ را در ادامه مقایسه میکنیم که به 13 رسیدیم.
مقایسه گرههایی که برای جست و جو انجام دادیم:
19-11-16-13
درخت هر چه بزرگتر باشد این روش کارآیی خود را بیشتر نشان میدهد.
در قسمت بعدی به پیاده سازی کد این درخت به همراه متدهای افزودن و جست و جو و حذف میپردازیم.
کاهش رفت و برگشت به دیتابیس در حین ذخیره سازی اطلاعات
مثلا الان اکثر کلاسهای موجودیتها چنین تعریفی رو دارند:
public class SomeName { public virtual User User { get; set; } }
[ForeignKey("UserId")] public virtual User User { set; get; } public int? UserId { set; get; }
تقریبا اکثر کلاسهای دومین شما نکته کار با کلیدهای خارجی رو جهت کاهش رفت و برگشت به دیتابیس ندارند.
ارسال عکس به stimulsoft و ایجاد گزارش
private void ModifyItemList() { ItemList.Clear(); var AllItems = from BetaData in BetaContext.BetaDatas where BetaData.ProjectName == Globals.ProjectName && BetaData.ProjectCode == Globals.ProjectCode && (BetaData.Type == Globals.Types.Partition || BetaData.Type == Globals.Types.Door) select BetaData; foreach(BetaData data in AllItems) { byte[] imageByte = data.Image.ToArray(); MemoryStream MS = new MemoryStream(imageByte); Image img = Image.FromStream(MS); StiImage stiImg = new StiImage(); stiImg.Image = img; Items newItem = new Items { Image = img, Name = data.Name }; ItemList.Add(newItem); } } private void modiefieItemListToolStripMenuItem_Click(object sender, EventArgs e) { } private void modifiedItemListToolStripMenuItem_Click(object sender, EventArgs e) { ModifyItemList(); ModifiedItemListReport.RegBusinessObject("Items",ItemList); ModifiedItemListReport.Compile(); ModifiedItemListReport.Render(); ModifiedItemListReport.Show(); } public class Items { public string Name { get; set; } public Image Image { get; set; } }
کلیدهای مربوط به Request
ضروری؟ | نام کلید | مقدار |
بله | "owin.RequestBody" | یک Stream همراه با request body. اگر body برای request وجود نداشته باشد، Stream.Null به عنوان placeholder قابل استفاده است. |
بله | "owin.RequestHeaders" | یک دیکشنری به صورت IDictionary<string, string[]> از هدرهای درخواست. |
بله | "owin.RequestMethod" | رشتهایی حاوی نوع فعل متد HTTP مربوط به درخواست (مانند GET and POST ) |
بله | "owin.RequestPath" | path درخواست شده به صورت string |
بله | "owin.RequestPathBase" | قسمتی از path درخواست به صورت string |
بله | "owin.RequestProtocol" | نام و نسخهی پروتکل (مانند HTTP/1.0 or HTTP/1.1 ) |
بله | "owin.RequestQueryString" | رشتهای حاوی query string ؛ بدون علامت ? (مانند foo=bar&baz=quux ) |
بله | "owin.RequestScheme" | رشتهایی حاوی URL scheme استفاده شده در درخواست (مانند HTTP or HTTPS ) |
ضروری؟ | نام کلید | مقدار |
بله | "owin.ResponseBody" | یک Stream جهت نوشتن response body در خروجی |
بله | "owin.ResponseHeaders" | یک دیکشنری به صورت IDictionary<string, string[]> از هدرهای response |
خیر | "owin.ResponseStatusCode" | یک عدد صحیح حاوی کد وضعیت HTTP response ؛ حالت پیشفرض 200 است. |
خیر | "owin.ResponseReasonPhrase" | یک رشته حاوی reason phrase مربوط به status code ؛ اگر خالی باشد در نتیجه سرور بهتر است آن را مقداردهی کند. |
خیر | "owin.ResponseProtocol" | یک رشته حاوی نام و نسخهی پروتکل (مانند HTTP/1.0 or HTTP/1.1 )؛ اگر خالی باشد؛ “owin.RequestProtocol” به عنوان مقدار پیشفرض در نظر گرفته خواهد شد. |
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net461" /> <package id="Microsoft.Owin.Host.SystemWeb" version="3.0.1" targetFramework="net461" /> <package id="Owin" version="1.0" targetFramework="net461" />
using Owin; namespace SimpleOwinWebApp { public class Startup { public void Configuration(IAppBuilder app) { } } }
using Owin; namespace SimpleOwinWebApp { public class Startup { public void Configuration(IAppBuilder app) { app.Use(async (ctx, next) => { await ctx.Response.WriteAsync("Hello"); }); } }
Func<IOwinContext, Func<Task>, Task> handler
app.Use(async (ctx, next) => { var response = ctx.Environment["owin.ResponseBody"] as Stream; using (var writer = new StreamWriter(response)) { await writer.WriteAsync("Hello"); } });
using System; using Microsoft.Owin.Hosting; namespace SimpleOwinConsoleApp { class Program { static void Main(string[] args) { using (WebApp.Start<Startup>("http://localhost:12345")) { Console.WriteLine("Listening to port 12345"); Console.WriteLine("Press Enter to end..."); Console.ReadLine(); } } } }
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace SimpleOwinCoreApp { public class Startup { public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } } }
using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace SimpleOwinCoreApp.Middlewares { public class SimpleMiddleware { private readonly RequestDelegate _next; public SimpleMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext ctx) { // قبل از فراخوانی میانافزار بعدی await ctx.Response.WriteAsync("Hello DNT!"); await _next(ctx); // بعد از فراخوانی میانافزار بعدی } } }
app.UseMiddleware<SimpleMiddleware>();
"Microsoft.AspNetCore.Owin": "1.0.0"
app.UseOwin(pipeline => { pipeline(next => new MyKatanaBasedMiddleware(next).Invoke) });
using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace SimpleOwinAspNetCore.Middleware { public class IpBlockerMiddleware { private readonly RequestDelegate _next; private readonly IpBlockerOptions _options; public IpBlockerMiddleware(RequestDelegate next, IpBlockerOptions options) { _next = next; _options = options; } public async Task Invoke(HttpContext context) { var ipAddress = context.Request.Host.Host; if (IsBlockedIpAddress(ipAddress)) { context.Response.StatusCode = 403; await context.Response.WriteAsync("Forbidden : The server understood the request, but It is refusing to fulfill it."); return; } await _next.Invoke(context); } private bool IsBlockedIpAddress(string ipAddress) { return _options.Ips.Any(ip => ip == ipAddress); } } }
using System.Collections.Generic; namespace SimpleOwinAspNetCore.Middleware { public class IpBlockerOptions { public IpBlockerOptions() { Ips = new[] { "192.168.1.1" }; } public IList<string> Ips { get; set; } } }
using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; namespace SimpleOwinAspNetCore.Middleware { public static class IpBlockerExtensions { public static IApplicationBuilder UseIpBlocker(this IApplicationBuilder builder, IConfigurationRoot configuration, IpBlockerOptions options = null) { return builder.UseMiddleware<IpBlockerMiddleware>(options ?? new IpBlockerOptions { Ips = configuration.GetSection("block_list").GetChildren().Select(p => p.Value).ToArray() }); } } }
{ "block_list": [ "192.168.1.1", "localhost", "127.0.0.1", "172.16.132.151" ] }
public IConfigurationRoot Configuration { set; get; } public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("blockedIps.json"); Configuration = builder.Build(); }
app.UseIpBlocker(Configuration);
بررسی Async Streams در C# 8.0
fields: { "Price": { type: "number", validation: { required: true, min: 1 } } }
Kendo UI Validation و HTML 5
در HTML 5 امکان تعریف نوعهای خاص کنترلهای ورودی کاربر مانند email، url، number، range، date، search و color وجود دارد. برای مثال در اینجا اگر کاربر تاریخ غیرمعتبری را وارد کند، مرورگر پیام اعتبارسنجی متناظری را به او نمایش خواهد داد. همچنین در HTML 5 امکان افزودن ویژگی required نیز به کنترلهای ورودی پیش بینی شدهاست. اما باید درنظر داشت که مرورگرهای قدیمی از این امکانات پشتیبانی نمیکنند. در این حالت Kendo UI با تشویق استفاده از روش معرفی شده در HTML 5، با آن یکپارچه شده و همچنین این قابلیتهای اعتبارسنجی HTML 5 را در مرورگرهای قدیمی نیز میسر میکند. Kendo UI Validation جزو نسخهی سورس باز Kendo UI با مجوز Apache نیز میباشد.
نمونهای از امکانات اعتبارسنجی توکار HTML 5 را در اینجا مشاهده میکنید:
<input type="text" name="firstName" required /> <input type="text" name="twitter" pattern="https?://(?:www\.)?twitter\.com/.+i" /> <input type="number" name="age" min="1" max="42" /> <input type="number" name="age" min="1" max="100" step="2" /> <input type="url" name="url" /> <input type="email" name="email" />
یکپارچه سازی اعتبارسنجی Kendo UI با اعتبارسنجی HTML 5
در اینجا یک فرم تشکیل شده با ساختار HTML 5 را ملاحظه میکنید. هر دو فیلد ورودی، با ویژگی استاندارد required مزین شدهاند. همچنین توسط ویژگی type، ورودی دوم جهت دریافت آدرس ایمیل معرفی شدهاست.
چون فیلد دوم دارای دو اعتبارسنجی تعریف شده است، دارای دو ویژگی *-data برای تعریف پیامهای اعتبارسنجی متناظر نیز میباشد. الگوی تعریف آنها data-[rule]-msg است.
<div class="k-rtl"> <form id="testView"> <label for="firstName">نام</label> <input id="firstName" name="firstName" type="text" class="k-textbox" required validationmessage="لطفا نامی را وارد کنید"> <br> <label for="emailId">آدرس پست الکترونیک</label> <input id="emailId" name="emailId" type="email" dir="ltr" required class="k-textbox" data-required-msg="لطفا ایمیلی را وارد کنید." data-email-msg="ایمیل وارد شده معتبر نیست."> <br> <input type="submit" class="k-button" value="ارسال"> </form> </div> <script type="text/javascript"> $(function () { $("form#testView").kendoValidator(); }); </script>
تعیین محل نمایش پیامهای اعتبارسنجی
پیامهای اعتبارسنجی Kendo UI به صورت خودکار در کنار فیلد متناظر با آن نمایش داده میشوند. اما اگر نیاز به تعیین مکان دستی آنها وجود داشت (جهت خوانایی بهتر) باید به نحو ذیل عمل کرد:
<input type="text" id="name" name="name" required> <span class="k-invalid-msg" data-for="name"></span>
تعریف سراسری پیامهای اعتبارسنجی
در مثال فوق، به ازای تک تک فیلدهای ورودی، پیام اعتبارسنجی متناظر با required وارد شد. میتوان این پیامها را حذف کرد و در قسمت messages متد kendoValidator قرار داد:
<script type="text/javascript"> $(function () { $("form#testView").kendoValidator({ messages: { // {0} would be replaced with the input element's name required: '{0} را تکمیل کنید.', email: 'ایمیل وارد شده معتبر نیست.' } }); }); </script>
- در این پیامها {0} با مقدار ویژگی name فیلد ورودی متناظر جایگزین میشود.
- اگر هم در markup و هم در تعاریف kendoValidator، پیامهای اعتبارسنجی تعریف شوند، حق تقدم با تعاریف markup خواهد بود.
اعتبارسنجی سفارشی سمت کاربر
علاوه بر امکانات استاندارد HTML 5، امکان تعریف دستورهای اعتبارسنجی سفارشی نیز وجود دارد:
<script type="text/javascript"> $(function () { $("form#testView").kendoValidator({ rules: { customRule1: function (input) { if (!input.is("[id=firstName]")) return true; var re = /^[A-Za-z]+$/; return re.test(input.val()); } //, customRule1: …. }, messages: { // {0} would be replaced with the input element's name required: '{0} را تکمیل کنید.', email: 'ایمیل وارد شده معتبر نیست.', customRule1: 'اعداد مجاز نیستند.' } }); }); </script>
- همانطور که ملاحظه میکنید، برای تعریف منطق اعتبارسنجی سفارشی، باید از خاصیت rules ورودی متد kendoValidator شروع کرد. در اینجا نام یک متد callback دلخواهی را وارد کرده و سپس بر اساس منطق اعتبارسنجی مورد نظر، باید true/false را بازگشت داد. برای نمونه در این مثال اگر کاربر در فیلد نام، عدد وارد کند، ورودی او مورد قبول واقع نخواهد شد.
- باید دقت داشت که اگر بررسی input.is صورت نگیرد، منطق تعریف شده به تمام کنترلهای صفحه اعمال میشود.
- پیام متناظر با این دستور سفارشی جدید، در قسمت messages، دقیقا بر اساس نام callback method تعریف شده در قسمت rules باید تعریف شود.
فراخوانی دستی اعتبارسنجی یک فرم
در حالت پیش فرض، با کلیک بر روی دکمهی ارسال، اعتبارسنجی کلیه عناصر فرم به صورت خودکار انجام میشود. اگر بخواهیم در این بین یک پیام سفارشی را نیز نمایش دهیم میتوان به صورت زیر عمل کرد:
<script type="text/javascript"> $(function () { $("form#testView").submit(function (event) { event.preventDefault(); var validator = $("form#testView").data("kendoValidator"); if (validator.validate()) { alert("validated!"); } else { alert("There is invalid data in the form."); } }); $("form#testView").kendoValidator(); }); </script>
اعتبارسنجی سفارشی در DataSource
در تعریف فیلدهای مدل DataSource، امکان تعریف اعتبارسنجیهای پیش فرضی مانند rquired، min، max و امثال آن وجود دارد که نمونهای از آنرا در بحث فعال سازی CRUD در Kendo UI Grid مشاهده کردید:
fields: { "serviceName": { type: "string", defaultValue: "Inspection", editable: true, nullable: false, validation: { /*...*/ } }, // ... }
schema: { model: { id: "ProductID", fields: { ProductID: { editable: false, nullable: true }, ProductName: { validation: { required: true, custom1: function (input) { if (input.is("[name='ProductName']") && input.val() != "") { input.attr("data-custom1-msg", "نام محصول باید با حرف بزرگ انگلیسی شروع شود"); return /^[A-Z]/.test(input.val()); } return true; } // ,custom2: ... } }, UnitPrice: { type: "number", validation: { required: true, min: 1} }, Discontinued: { type: "boolean" }, UnitsInStock: { type: "number", validation: { min: 0, required: true} } } } }
متد ()input.val مقدار کنترل جاری را بر میگرداند. برای دسترسی به مقدار سایر کنترلها میتوان از روش ()fieldName").val#")$ استفاده کرد.
مقدمات راهبری (Navigation) در سیلورلایت را در اینجا میتوانید مطالعه نمائید : +
مطلبی را که در فصل فوق نخواهید یافت در مورد نحوهی بکارگیری الگوی MVVM جهت پیاده سازی Navigation در یک برنامهی سیلورلایت است؛ علت آن هم به این بر میگردد که این فصل پیش از مباحث Binding مطرح شد.
صورت مساله:
یکی از اصول MVVM این است که در ViewModel نباید ارجاعی از View وجود داشته باشد (ViewModel باید در بیخبری کامل از وجود اشیاء UI و ارجاع مستقیم به آنها طراحی شود)، اما برای پیاده سازی مباحث Navigation نیاز است به نحوی به شیء Frame قرار داده شده در صفحهی اصلی یا قالب اصلی برنامه دسترسی یافت تا بتوان درخواست رهنمون شدن به صفحات مختلف را صادر کرد. اکنون چکار باید کرد؟
راه حل:
یکی از راه حلهای جالبی که برای این منظور وجود دارد استفاده از امکانات کلاس Messenger مجموعهی MVVM Light toolkit است. از طریق ViewModel برنامه، آدرس صفحهی مورد نظر را به صورت یک پیغام به View مورد نظر ارسال میکنیم و سپس View برنامه که به این پیغامها گوش فرا میدهد، پس از دریافت آدرس مورد نظر، نسبت به فراخوانی تابع Navigate شیء Frame رابط کاربری برنامه اقدام خواهد کرد. به این صورت ViewModel برنامه به View خود جهت اعمال راهبری برنامه، گره نخواهد خورد.
روش پیاده سازی:
ابتدا ساختار پروژه را در نظر بگیرید (این شکل دگرگون شدهی Solution explorer مرتبط است با productivity tools نصب شده):
در پوشهی Views ، دو صفحه اضافه شدهاند که توسط user control ایی به نام menu لیست شده و راهبری خواهند شد. مونتاژ نهایی هم در MainPage.xaml صورت میگیرد.
کدهای XAML مرتبط با منوی ساده برنامه به شرح زیر هستند (Menu.xaml) :
<UserControl x:Class="MvvmLight6.Views.Menu"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:MvvmLight6.ViewModels" mc:Ignorable="d"
FlowDirection="RightToLeft" d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<vm:MenuViewModel x:Key="vmMenuViewModel" />
</UserControl.Resources>
<StackPanel DataContext="{Binding Source={StaticResource vmMenuViewModel}}">
<HyperlinkButton Content="صفحه یک" Margin="5"
Command="{Binding DoNavigate}"
CommandParameter="/Views/Page1.xaml"
/>
<HyperlinkButton Content="صفحه دو" Margin="5"
Command="{Binding DoNavigate}"
CommandParameter="/Views/Page2.xaml"
/>
</StackPanel>
</UserControl>
کدهای ViewModel مرتبط با این View که کار Command گردانی را انجام خواهد داد به شرح زیر است:
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
namespace MvvmLight6.ViewModels
{
public class MenuViewModel
{
public RelayCommand<string> DoNavigate { set; get; }
public MenuViewModel()
{
DoNavigate = new RelayCommand<string>(doNavigate);
}
private static void doNavigate(string url)
{
Messenger.Default.Send(url, "MyNavigationService");
}
}
}
تمام آیتمهای منوی فوق یک روال را صدا خواهند زد : DoNavigate . تنها تفاوت آنها در CommandParameter ارسالی به RelayCommand ما است که حاوی آدرس قرارگیری فایلهای صفحات تعریف شده است. این آدرسها با کمک امکانات کلاس Messenger مجموعهی MVVM light toolkit به View اصلی برنامه ارسال میگردند.
کدهای XAML مرتبط با MainPage.xaml به شرح زیر هستند:
<UserControl x:Class="MvvmLight6.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sdk="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:usr="clr-namespace:MvvmLight6.Views"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="268" />
</Grid.ColumnDefinitions>
<usr:Menu Grid.Column="1" />
<sdk:Frame Margin="5"
Name="frame1"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Grid.Column="0" />
</Grid>
</UserControl>
و کار دریافت پیغامها (یا همان آدرس صفحات جهت انجام راهبری) و عکس العمل نشان دادن به آنها توسط کدهای ذیل صورت خواهد گرفت:
using System;
using GalaSoft.MvvmLight.Messaging;
namespace MvvmLight6
{
public partial class MainPage
{
public MainPage()
{
registerMessenger();
InitializeComponent();
}
private void registerMessenger()
{
Messenger.Default.Register<string>(this, "MyNavigationService", doNavigate);
}
private void doNavigate(string uri)
{
frame1.Navigate(new Uri(uri, UriKind.Relative));
}
}
}
ابتدا یک Messenger در اینجا رجیستر میشود و سپس به ازای هر بار دریافت پیغامی با token مساوی MyNavigationService ، متد doNavigate فراخوانی خواهد گردید.
کدهای این مثال را از اینجا میتوانید دریافت کنید.
EF Code First #2
در بخشی گفتید که
[Column("MyTableKey")] public int Id { set; get; }