با رعایت نکات چک لیست تهیه یک برنامه ASP.NET MVC و ساخت favicon.ico و apple-touch-icon-XxY.png
ساختار دادهها چیست؟
نوع داده انتزاعی Abstraction Data Type -ADT
- خطی یا Linear: شامل ساختارهایی چون لیست و صف و پشته است: List ,Queue,Stack
- درختی یا Tree-Like: درخت باینری ، درخت متوازن و B-Trees
- Dictionary : شامل یک جفت کلید و مقدار است در جدول هش
- بقیه: گرافها، صف الویت، bags, Multi bags, multi sets
(void Add(object | افزودن المان به آخر لیست |
(void Remove(object | حذف یک المان خاص از لیست |
()void Clear | حذف کلیه المانها |
( bool Contains(object | شامل این داده میشود یا خیر؟ |
( void RemoveAt(int | حذف یک المان بر اساس جایگاه یا اندیسش |
(void Insert(int, object | افزودن یک المان در جایگاهی (اندیس) خاص بر اساس مقدار position |
(int IndexOf(object | اندیس یا جایگاه یک عنصر را بر میگرداند |
[this[int | ایندکسر ، برای دستریس به عنصر در اندیس مورد نظر |
لیستهای ایستا static Lists
public class CustomArrayList<T> { private T[] arr; private int count; public int Count { get { return this.count; } } private const int INITIAL_CAPACITY = 4; public CustomArrayList(int capacity = INITIAL_CAPACITY) { this.arr = new T[capacity]; this.count = 0; }
public void Add(T item) { GrowIfArrIsFull(); this.arr[this.count] = item; this.count++; } public void Insert(int index, T item) { if (index > this.count || index < 0) { throw new IndexOutOfRangeException( "Invalid index: " + index); } GrowIfArrIsFull(); Array.Copy(this.arr, index, this.arr, index + 1, this.count - index); this.arr[index] = item; this.count++; } private void GrowIfArrIsFull() { if (this.count + 1 > this.arr.Length) { T[] extendedArr = new T[this.arr.Length * 2]; Array.Copy(this.arr, extendedArr, this.count); this.arr = extendedArr; } } public void Clear() { this.arr = new T[INITIAL_CAPACITY]; this.count = 0; }
برای پیاده سازی آن به دو کلاس نیاز داریم. کلاس ListNode برای نگهداری هر المان و اطلاعات المان بعدی به کار میرود که از این به بعد به آن Node یا گره میگوییم و دیگری کلاس <DynamicList<T برای نگهداری دنباله ای از گرهها و متدهای پردازشی آن.
public class DynamicList<T> { private class ListNode { public T Element { get; set; } public ListNode NextNode { get; set; } public ListNode(T element) { this.Element = element; NextNode = null; } public ListNode(T element, ListNode prevNode) { this.Element = element; prevNode.NextNode = this; } } private ListNode head; private ListNode tail; private int count; // … }
از آن جا که نیازی نیست کاربر با کلاس ListNode آشنایی داشته باشد و با آن سر و کله بزند، آن را داخل همان کلاس اصلی به صورت خصوصی استفاده میکنیم. این کلاس دو خاصیت دارد؛ یکی برای المان اصلی و دیگر گره بعدی. این کلاس دارای دو سازنده است که اولی تنها برای عنصر اول به کار میرود. چون اولین بار است که یک گره ایجاد میشود، پس باید خاصیت NextNode یعنی گره بعدی در آن Null باشد و سازندهی دوم برای گرههای شماره 2 به بعد به کار میرود که همراه المان داده شده، گره قبلی را هم ارسال میکنیم تا خاصیت NextNode آن را به گره جدیدی که میسازیم مرتبط سازد. سه خاصیت کلاس اصلی به نامهای Count,Tail,Head به ترتیب برای اشاره به اولین گره، آخرین گره و تعداد گرهها، به کار میروند که در ادامه کد آنرا در زیر میبینیم:
public DynamicList() { this.head = null; this.tail = null; this.count = 0; } public void Add(T item) { if (this.head == null) { this.head = new ListNode(item); this.tail = this.head; } else { ListNode newNode = new ListNode(item, this.tail); this.tail = newNode; } this.count++; }
سازنده مقدار دهی پیش فرض را انجام میدهد. در متد Add المان جدیدی باید افزوده شود؛ پس چک میکند این المان ارسالی قرار است اولین گره باشد یا خیر؟ اگر head که به اولین گره اشاره دارد Null باشد، به این معنی است که این اولین گره است. پس اولین سازندهی کلاس ListNode را صدا میزنیم و آن را در متغیر Head قرار میدهیم و چون فقط همین گره را داریم، پس آخرین گره هم شناخته میشود که در tail نیز قرار میگیرد. حال اگر فرض کنیم المان بعدی را به آن بدهیم، اینبار دیگر Head برابر Null نخواهد بود. پس دومین سازندهی ListNode صدا زده میشود که به غیر از المان جدید، باید آخرین گره قبلی هم با آن ارسال شود و گره جدیدی که ایجاد میشود در خاصیت NextNode آن نیز قرار بگیرد و در نهایت گره ایجاد شده به عنوان آخرین گره لیست در متغیر Tail نیز قرار میگیرد. در خط پایانی هم به هر مدلی که المان جدید به لیست اضافه شده باشد متغیر Count به روز میشود.
public T RemoveAt(int index) { if (index >= count || index < 0) { throw new ArgumentOutOfRangeException( "Invalid index: " + index); } int currentIndex = 0; ListNode currentNode = this.head; ListNode prevNode = null; while (currentIndex < index) { prevNode = currentNode; currentNode = currentNode.NextNode; currentIndex++; } RemoveListNode(currentNode, prevNode); return currentNode.Element; } private void RemoveListNode(ListNode node, ListNode prevNode) { count--; if (count == 0) { this.head = null; this.tail = null; } else if (prevNode == null) { this.head = node.NextNode; } else { prevNode.NextNode = node.NextNode; } if (object.ReferenceEquals(this.tail, node)) { this.tail = prevNode; } }
برای حذف یک گره شماره اندیس آن گره را دریافت میکنیم و از Head، گره را بیرون کشیده و با خاصیت nextNode آنقدر به سمت جلو حرکت میکنیم تا متغیر currentIndex یا اندیس داده شده برابر شود و سپس گره دریافتی و گره قبلی آن را به سمت تابع RemoveListNode ارسال میکنیم. کاری که این تابع انجام میدهد این است که مقدار NextNode گره فعلی که قصد حذفش را داریم به خاصیت Next Node گره قبلی انتساب میدهد. پس به این ترتیب پیوند این گره از لیست از دست میرود و گره قبلی به جای اشاره به این گره، به گره بعد از آن اشاره میکند. مابقی کد از قبیل جست و برگردان اندیس یک عنصر و ... را به خودتان وگذار میکنم.
در روشهای بالا ما خودمان 2 عدد ADT را پیاده سازی کردیم و متوجه شدیم برای دخیره دادهها در حافظه روشهای متفاوتی وجود دارند که بیشتر تفاوت آن در مورد استفاده از حافظه و کارآیی این روش هاست.
لیستهای پیوندی دو طرفه Doubly Linked_List
لیستهای پیوندی بالا یک طرفه بودند و اگر ما یک گره را داشتیم و میخواستیم به گره قبلی آن رجوع کنیم، اینکار ممکن نبود و مجبور بودیم برای رسیدن به آن از ابتدای گره حرکت را آغاز کنیم تا به آن برسیم. به همین منظور مبحث لیستهای پیوندی دو طرفه آغاز شد. به این ترتیب هر گره به جز حفظ ارتباط با گره بعدی از طریق خاصیت NextNode، ارتباطش را با گره قبلی از طریق خاصیت PrevNode نیز حفظ میکند.
این مبحث را در اینجا میبندیم و در قسمت بعدی آن را ادامه میدهیم.
پیاده سازی یک فرم لاگین با استفاده از چندین useState Hook
در ابتدا، یک مثال کاربردیتر را به کمک useState Hookها پیاده سازی میکنیم. در اینجا هر المان فرم را به یک useState Hook مجزا، متصل کردهایم. کدهای کامل این کامپوننت را در ادامه مشاهده میکنید:
import React, { useState } from "react"; export default function Login() { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [user, setUser] = useState(null); const handleSubmit = event => { event.preventDefault(); const userData = { username, password }; setUser(userData); setUsername(""); setPassword(""); }; return ( <> <h2 className="mt-3">Login</h2> <form onSubmit={handleSubmit}> <div className="form-group"> <label htmlFor="username">Username</label> <input type="text" name="username" id="username" onChange={event => setUsername(event.target.value)} value={username} className="form-control" /> </div> <div className="form-group"> <label htmlFor="password">Password</label> <input type="password" name="password" id="password" onChange={event => setPassword(event.target.value)} value={password} className="form-control" /> </div> <button type="submit">Submit</button> </form> {user && JSON.stringify(user, null, 2)} </> ); }
- اگر دقت کرده باشید، اینبار این کامپوننت تابعی را به صورت متداول ()function Login تعریف کردهایم. مزیت یک چنین تعریفی، امکان export در محل آن میباشد:
export default function Login() {
به علاوه وجود واژهی default در اینجا سبب میشود که برای import آن، بتوان از هر نام دلخواهی استفاده کرد و در اینجا اجباری به استفادهی از نام Login وجود ندارد که نمونهی استفادهی از آن در فایل index.js، میتواند به صورت زیر باشد:
import App from "./components/part02/Login";
- فرم لاگین تعریف شده، از یک فیلد نام کاربری و یک فیلد کلمهی عبور تشکیل شدهاست.
- اکنون میخواهیم اطلاعات دریافت شدهی از کاربر را در state کامپوننت جاری منعکس کنیم. به همین جهت، کار با import متد useState شروع میشود. سپس به ازای هر فیلد در فرم، یک state مجزا را تعریف میکنیم:
const [username, setUsername] = useState(""); const [password, setPassword] = useState("");
<input type="text" name="username" id="username" onChange={event => setUsername(event.target.value)} value={username} className="form-control" />
- پس از به روز رسانی state، میخواهیم در حین submit فرم، این اطلاعات را برای مثال به صورت یک شیء، به سمت سرور ارسال کنیم. به همین جهت نیاز است رویداد onSubmit فرم را مدیریت کرد. در این متد ابتدا از post back معمول آن به سمت سرور جلوگیری میشود و سپس بر اساس متغیرهای تعریف شدهی در state، یک شیء را ایجاد کردهایم:
const handleSubmit = event => { event.preventDefault(); const userData = { username, password }; setUser(userData); setUsername(""); setPassword(""); };
{user && JSON.stringify(user, null, 2)}
const [user, setUser] = useState(null);
- دو سطر بعدی را که در انتهای handleSubmit مشاهده میکنید، روشی است برای خالی کردن المانهای فرم، پس از ارسال اطلاعات فرم، برای مثال به backend server. البته این حالت فقط برای حالتی نیاز است که فرم قرار نباشد به آدرس دیگری Redirect شود. برای خالی کردن المانهای فرم، المانهای آنرا باید تبدیل به controlled elements کرد که اینکار با مقدار دهی value آنها توسط value={username} صورت گرفتهاست. به این ترتیب محتوای این المانها با اطلاعاتی که در state داریم، قابل کنترل میشوند.
مثال دوم این مطلب نیز در مورد مدیریت المانهای یک فرم توسط useState Hook است؛ با این تفاوت که در اینجا تنها یک شیء، کل state را تشکیل میدهد. کدهای کامل این مثال را در ادامه مشاهده میکنید:
import React, { useState } from "react"; const initialFormState = { username: "", email: "", password: "" }; export default function Register() { const [form, setForm] = useState(initialFormState); const [user, setUser] = useState(null); const handleChange = event => { setForm({ ...form, [event.target.name]: event.target.value }); }; const handleSubmit = event => { event.preventDefault(); setUser(form); setForm(initialFormState); }; return ( <> <h2 className="mt-3">Register</h2> <form onSubmit={handleSubmit}> <div className="form-group"> <label htmlFor="username">Username</label> <input type="text" name="username" id="username" onChange={handleChange} value={form.username} className="form-control" /> </div> <div className="form-group"> <label htmlFor="email">Email</label> <input type="email" name="email" id="email" onChange={handleChange} value={form.email} className="form-control" /> </div> <div className="form-group"> <label htmlFor="password">Password</label> <input type="password" name="password" id="password" onChange={handleChange} value={form.password} className="form-control" /> </div> <button type="submit" className="btn btn-primary"> Submit </button> </form> {user && JSON.stringify(user, null, 2)} </> ); }
- فرم ثبت نام فوق از سه فیلد نام کاربری، ایمیل و کلمهی عبور تشکیل شدهاست.
- اینبار نحوهی تشکیل state مرتبط با این سه فیلد را بسیار شبیه به حالت مدیریت state در کامپوننتهای کلاسی، تعریف کردهایم؛ که تنها با یک تک شیء، انجام میشود و نام آنرا form در نظر گرفتهایم:
const [form, setForm] = useState({ username: "", email: "", password: ""});
<input type="text" name="username" id="username" onChange={handleChange} value={form.username} className="form-control" />
const handleChange = event => { setForm({ ...form, [event.target.name]: event.target.value }); };
- علت وجود spread operator تعریف شدهی در اینجا یعنی form...، این است که در حالت استفادهی از useState، برخلاف حالت کار با کامپوننتهای کلاسی، خواص اضافه شدهی به state، به شیء نهایی به صورت خودکار اضافه نمیشوند و باید کار یکی سازی را توسط spread operator انجام داد. برای مثال فرض کنید که کاربر، فیلد نام کاربری را ابتدا ثبت میکند. بنابراین در این لحظه، شیء ارسالی به setForm، فقط دارای خاصیت username خواهد شد. اکنون اگر در ادامه، کاربر فیلد ایمیل را تکمیل کند، اینبار فقط خاصیت ایمیل در این شیء قرار خواهد گرفت (یا مقدار قبلی را به روز رسانی میکند) و از سایر خواص صرفنظر میشود؛ مگر اینکه توسط spread operator، سایر خواص پیشین موجود در شیء form را نیز در اینجا لحاظ کنیم، تا اطلاعاتی را از دست نداده باشیم.
بنابراین به صورت خلاصه در روش سنتی کار با کامپوننتهای کلاسی، فراخوانی متد this.setState کار merge خواص را انجام میدهد؛ اما در اینجا فقط کار replace صورت میگیرد و باید کار merge خواص یک شیء را به صورت دستی و توسط یک spread operator انجام دهیم. البته در قسمت قبل چون تمام خواص شیء تعریف شدهی در state را با هم به روز رسانی میکردیم:
setMousePosition({ x: event.pageX, y: event.pageY });
- سایر فیلدهای فرم نیز به همین روش onChange={handleChange}، به متد رویدادگردان فوق متصل میشوند.
- در پایان برای مدیریت رخداد ارسال فرم، handleSubmit را به صورت زیر تعریف کردهایم:
const handleSubmit = event => { event.preventDefault(); setUser(form); setForm(initialFormState); };
- همچنین چون در پایین فرم نیز میخواهیم این اطلاعات را به صورت JSON نمایش دهیم:
{user && JSON.stringify(user, null, 2)}
const [user, setUser] = useState(null);
- برای پاک کردن المانهای فرم، پس از submit آن، ابتدا نیاز است این المانها را تبدیل به controlled elements کرد که اینکار با مقدار دهی value آنها توسط برای مثال value={form.username} صورت گرفتهاست. به این ترتیب محتوای این المانها با اطلاعاتی که در state داریم، قابل کنترل میشوند. اکنون اگر setForm را با یک شیء خالی مقدار دهی کنیم، به صورت خودکار المانهای فرم را پاک میکند. برای اینکار بجای تعریف شیء موجود در state به صورت inline:
const [form, setForm] = useState({ username: "", email: "", password: ""});
const initialFormState = { username: "", email: "", password: "" }; export default function Register() { const [form, setForm] = useState(initialFormState);
مقایسهی روشهای مختلف مدیریت state توسط useState Hook
using System.Drawing; using System.Windows.Forms; namespace Navasser.Theme.VisualStudio { public class BlueMenuStrip:ToolStripProfessionalRenderer { protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e) { var borderColor = ColorTranslator.FromHtml("#E5C365");//Menu Item Border var selectedMenuBackColor = ColorTranslator.FromHtml("#FDF4BF");//Menu Item Background var menuOpenedBackColor = ColorTranslator.FromHtml("#EAF0FF"); var borderPen = new Pen(borderColor); if (e.Item.Selected)//اگر آیتمی از منوها انتخاب شد { var selectedMenuBrush = new SolidBrush(selectedMenuBackColor); var selectedItemBounds = new Rectangle(Point.Empty, e.Item.Size);//اقدام به پر کردن یک مستطیل به اندازه ابعاد آیتم انتخاب شده میکند e.Graphics.FillRectangle(selectedMenuBrush,selectedItemBounds); e.Graphics.DrawRectangle(borderPen,0,0,selectedItemBounds.Width-1,selectedItemBounds.Height-1);// بوردر آیتم را رسم میکند e.Item.BackColor = menuOpenedBackColor; } else { base.OnRenderMenuItemBackground(e); e.ToolStrip.BackColor = ColorTranslator.FromHtml("#EAF0FF"); } } } }
using System.Drawing; using System.Windows.Forms; namespace Navasser.Theme.VisualStudio { public class BlueToolStrip : ToolStripProfessionalRenderer { protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e) { var toolStripBackColor = ColorTranslator.FromHtml("#D6DBE9"); var toolStripBrush = new SolidBrush(toolStripBackColor); e.Graphics.FillRectangle(toolStripBrush,0,0,e.AffectedBounds.Width + 10,e.AffectedBounds.Height); } protected override void OnRenderButtonBackground(ToolStripItemRenderEventArgs e) { var borderColor = ColorTranslator.FromHtml("#E5C365");//For border var selectedToolItemBackColor = ColorTranslator.FromHtml("#FDF4BF"); var selectedItemBrush = new SolidBrush(selectedToolItemBackColor); var pressedItemBackColor = ColorTranslator.FromHtml("#FFF29D"); var pressedItemBrush = new SolidBrush(pressedItemBackColor); var borderPen = new Pen(borderColor); if (e.Item.Selected) { e.Graphics.FillRectangle(selectedItemBrush,0,0,e.Item.Width,e.Item.Height); e.Graphics.DrawRectangle(borderPen,0,0,e.Item.Width-1,e.Item.Height-1); } if (e.Item.Pressed) { e.Graphics.FillRectangle(pressedItemBrush, 0, 0, e.Item.Width, e.Item.Height); e.Graphics.DrawRectangle(borderPen, 0, 0, e.Item.Width - 1, e.Item.Height - 1); } } } }
همچنین میتوانید برای انتخاب رنگهای دلخواه خودتان از ابزار ColorSchemer Studio استفاده کنید.
استفاده از Froala WYSIWYG Editor در ASP.NET
fullscreen: { title: 'FullScreen', icon: { type: 'font', value: 'fa fa-external-link' }, callback: function (editor) { if (editor.$box.hasClass("froala-editor-full-screen")) { editor.$box.removeClass("froala-editor-full-screen"); editor.$box.appendTo(editor.editorParent); } else { if (editor.editorParent === undefined) editor.editorParent = editor.$box.parent(); editor.$box.addClass("froala-editor-full-screen"); editor.$box.appendTo("body"); } editor.focus(); } },
.froala-editor-full-screen { position: fixed !important; z-index: 10000; top: 0; bottom: 0; left: 0; right: 0; background-color: #fff; } .froala-editor-full-screen div.f-placeholder { height: 100% !important; }
همینطور برای ایجاد یک View گزینه (DataWindow (WPF with Catel را انتخاب نمایید. ViewModelها در Catel از کلاس پایه ViewModelBase و Viewها نیز از کلاس DataWindow مشتق میشوند.
public class MainWindowViewModel : ViewModelBase { public MainWindowViewModel() : base() { ShowPleaseWait = new Command(OnShowPleaseWaitExecute); } public override string Title { get { return "View model title"; } } public Command ShowPleaseWait { get; private set; } private void OnShowPleaseWaitExecute() { var pleaseWaitService = GetService<IPleaseWaitService>(); pleaseWaitService.Show(() => { Thread.Sleep(3000); }); } }
<Button Margin="6" Command="{Binding ShowPleaseWait}" Content="Show PleaseWait!" />
var pleaseWaitService = GetService<IPleaseWaitService>(); pleaseWaitService.Show(() => { Thread.Sleep(3000); });
var uiService = GetService<IUIVisualizerService>(); var viewModel = new AnotherWindowViewModel(); uiService.Show(viewModel);
var openFileService = GetService<IOpenFileService>(); openFileService.Filter = "ZIP files (*.zip)|*.zip"; openFileService.IsMultiSelect = false; openFileService.Title = "Open file"; if (openFileService.DetermineFile()) { // ? }
var saveFileService = GetService<ISaveFileService>(); saveFileService.Filter = "ZIP files (*.zip)|*.zip"; saveFileService.FileName = "test"; saveFileService.Title = "Save file"; if (saveFileService.DetermineFile()) { // ? }
var processService = GetSetvice<IProcessService>(); processService.StartProcess(@"C:\Windows\System32\calc.exe");
var splashScreenService = GetService<ISplashScreenService>(); splashScreenService.Enqueue(new ActionTask("Creating the shell", OnCreateShell)); splashScreenService.Enqueue(new ActionTask("Initializing modules", OnInitializeModules)); splashScreenService.Enqueue(new ActionTask("Starting application", OnStartApplication));
var messageService = GetService<IMessageService>(); if (messageService.Show("Are you sure?", "?", MessageButton.YesNo, MessageImage.Warning) == MessageResult.Yes) { // ? }