اصول نمایش Popover در Twitter bootstrap
PopOverها نیز یکی دیگر از کامپوننتهای جاوا اسکریپتی مجموعه بوت استرپ هستند. بسیار شبیه به Tooltip بوده، اما ماندگارتر هستند. PopOverها با کلیک بر روی یک عنصر باز شده و تنها با کلیک مجدد بر روی آن المان، بسته میشوند (البته این موارد نیز قابل تنظیم هستند).
<a rel="popover" data-content="محتوایی برای نمایش" data-original-title="عنوان" href="#">اطلاعات</a> <script type="text/javascript"> $(document).ready(function () { $("[rel='popover']").popover({ placement: 'left' }) .click(function (e) { e.preventDefault(); }); }); </script>
تبدیل خطاهای اعتبارسنجی ASP.NET MVC به PopOver
هدف ما در اینجا نهایتا رسیدن به شکل زیر میباشد:
همانطور که ملاحظه میکنید، اینبار بجای نمایش خطاها در یک برچسب، مقابل کنترل متناظر، این خطا صرفا در حالت فوکوس کنترل، به شکل یک PopOver در کنار آن ظاهر شده است.
کدهای کامل View برنامه
@model Mvc4TwitterBootStrapTest.Models.User @{ ViewBag.Title = "Index"; } @using (Html.BeginForm()) { @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" }) <fieldset class="form-horizontal"> <legend>تعریف کاربر جدید</legend> <div class="control-group"> @Html.LabelFor(model => model.Name, new { @class = "control-label" }) <div class="controls"> @Html.EditorFor(model => model.Name) @*@Html.ValidationMessageFor(model => model.Name, null, new { @class = "help-inline" })*@ </div> </div> <div class="control-group"> @Html.LabelFor(model => model.LastName, new { @class = "control-label" }) <div class="controls"> @Html.EditorFor(model => model.LastName) @*@Html.ValidationMessageFor(model => model.LastName, null, new { @class = "help-inline" })*@ </div> </div> <div class="form-actions"> <button type="submit" class="btn btn-primary"> ارسال</button> </div> </fieldset> } @section JavaScript { <script type="text/javascript"> $.validator.setDefaults({ showErrors: function (errorMap, errorList) { this.defaultShowErrors(); //اگر المانی معتبر است نیاز به نمایش پاپ اور ندارد $("." + this.settings.validClass).popover("destroy"); //افزودن پاپ اورها for (var i = 0; i < errorList.length; i++) { var error = errorList[i]; $(error.element).popover({ placement: 'left' }) .attr("data-original-title", "خطای اعتبارسنجی") .attr("data-content", error.message); } }, // همانند قبل برای رنگی کردن کل ردیف در صورت عدم اعتبار سنجی و برعکس highlight: function (element, errorClass, validClass) { if (element.type === 'radio') { this.findByName(element.name).addClass(errorClass).removeClass(validClass); } else { $(element).addClass(errorClass).removeClass(validClass); $(element).closest('.control-group').removeClass('success').addClass('error'); } $(element).trigger('highlited'); }, unhighlight: function (element, errorClass, validClass) { if (element.type === 'radio') { this.findByName(element.name).removeClass(errorClass).addClass(validClass); } else { $(element).removeClass(errorClass).addClass(validClass); $(element).closest('.control-group').removeClass('error').addClass('success'); } $(element).trigger('unhighlited'); } }); //برای حالت پست بک از سرور عمل میکند $(function () { $('form').each(function () { $(this).find('div.control-group').each(function () { if ($(this).find('span.field-validation-error').length > 0) { $(this).addClass('error'); } }); }); }); </script> }
توضیحات
- با توجه به اینکه دیگر نمیخواهیم خطاها به صورت برچسب در مقابل کنترلها نمایش داده شوند، کلیه Html.ValidationMessageFor به صورت کامنت درآورده شدهاند.
- تغییر دوم مطلب جاری، اضافه شدن متد showErrors به تنظیمات پیش فرض jQuery Validator است. در این متد، اگر المانی معتبر بود، Popover آن حذف میشود یا در سایر حالات، المانهایی که نیاز به اعتبارسنجی سمت کلاینت دارند، یافت شده و سپس ویژگی data-content با مقداری معادل خطای اعتبارسنجی متناظر، به این المان افزوده و سپس متد popover بوت استرپ بر روی آن فراخوانی میگردد.
به عبارتی زمانیکه یک input box در ASP.NET MVC به همراه مقادیر مرتبط با اعتبارسنجی آن رندر میشود، چنین شکلی را خواهد داشت:
<input class="text-box single-line" data-val="true" data-val-required="لطفا نام را تکمیل کنید" id="Name" name="Name" type="text" value="" />
<input class="text-box single-line input-validation-error" data-val="true" data-val-required="لطفا نام را تکمیل کنید" id="Name" name="Name" type="text" value="" data-original-title="خطای اعتبارسنجی" title="" data-content="لطفا نام را تکمیل کنید">
البته این موارد را در صورت نیاز به صورت دستی نیز میتوان تعریف و اضافه کرد:
@Html.TextBoxFor(x => x.Name, new { data_content = "Name is required", data_original_title = "Error", rel="popover" })
نحوه ساخت Pipe سفارشی
علاوه بر استفاده از Pipeهای از پیش ساخته شده Angular، شما میتوانید Pipeهای سفارشی خود را نیز تعریف و استفاده کنید. به عنوان مثال میخواهیم Pipe ای را با نام perNumber تعریف کنیم، تا تمامی اعداد موجود در عبارت ورودی Pipe را به صورت اعداد فارسی نمایش دهد. یعنی با اعمال این Pipe به عدد 123456 خروجی ۱۲۳۴۵۶ مورد انتظار است. برای ایجاد Pipe سفارشی مراحل زیر را انجام دهید.
قدم اول - ساخت یک فایل با نام دلخواه
طبق Style Guide در Angular.io نام این فایل را per-number.pipe.ts انتخاب میکنیم.
قدم دوم – افزودن ماژولهای مورد نیاز
داخل فایل ایجاد شده ماژولهای Pipe و PipeTransform را با استفاده از دستور import از angular/core@ اضافه میکنیم.
import { Pipe, PipeTransform } from '@angular/core';
قدم سوم – ساخت کلاس و مزین کردن آن به Pipe@
یک کلاس با نام دلخواه را مثلا به نام PerNumberPipe ایجاد میکنیم. این کلاس علاوه بر اینکه PipeTransform را پیاده سازی خواهد کرد، مزین به متادیتای Pipe@ نیز میباشد. متادیتای Pipe@ هنگام تزئین کلاس، یک نام را دریافت میکند. این نام قرار است به عنوان نام نهایی Pipe برای اعمال بر روی Template expressions مورد استفاده قرار بگیرد.
import { Pipe, PipeTransform } from '@angular/core'; @Pipe({name: 'perNumber'}) export class PerNumberPipe implements PipeTransform { }
قدم چهارم – پیاده سازی متد transform
به واسطه اعمال اینترفیس PipeTransform، این کلاس باید متد transform را پیاده سازی کند. این متد در پارامتر اول خود، عبارت ورودی را که قرار است Pipe بر روی آن اعمال شود، دریافت میکند و در ادامه تعداد دلخواهی پارامتر ورودی Pipe را که میخواهد، میتواند دریافت کند.
import { Pipe, PipeTransform } from '@angular/core'; @Pipe({name: 'perNumber'}) export class PerNumberPipe implements PipeTransform { transform(value: any, ...args: any[]): any { } }
نکته ۱: نام انتخابی برای Pipe در آذینگر Pipe@ بایستی یک شناسه معتبر در JavaScript باشد.
نکته ۲: متد transform برای Pipe اجباری است و حتما بایستی پیاده سازی شود. اینترفیس PipeTransform این متد را برای کلاس اجباری میکند؛ هرچند استفاده از این اینترفیس برای کلاس Pipe کاملا اختیاری است.
قدم آخر – نوشتن کد تبدیل اعداد
Pipe مورد نظر ما قرار است یک رشته عددی را از ورودی دریافت کند و تمامی اعداد لاتین آن را به فارسی تبدیل کند. همچنین این Pipe هیچگونه پارامتری را دریافت نمیکند. کد زیر نحوه پیاده سازی متد transform را نمایش میدهد.
import { Pipe, PipeTransform } from '@angular/core'; @Pipe({name: 'perNumber'}) export class PerNumberPipe implements PipeTransform { transform(input: string): string{ if (input == undefined) return ''; var str = input.toString().trim(); if (str === "") return ""; str = str.replace(/0/g, '۰'); str = str.replace(/1/g, '۱'); str = str.replace(/2/g, '۲'); str = str.replace(/3/g, '۳'); str = str.replace(/4/g, '۴'); str = str.replace(/5/g, '۵'); str = str.replace(/6/g, '۶'); str = str.replace(/7/g, '۷'); str = str.replace(/8/g, '۸'); str = str.replace(/9/g, '۹'); return str; } }
نحوه معرفی Pipe سفارشی به برنامه
حالا جهت استفاده از Pipe سفارشی در کامپوننتهای خود کافی است آنرا یکبار در قسمت declarations در AppModule خود تعریف کنید.
import { PerNumberPipe } from './pipes/per-number.pipe.ts' ... declarations: [PerNumberPipe]
نحوه استفاده از Pipeهای سفارشی
نحوه استفاده از Pipeهای سفارشی، دقیقا مشابه Pipeهای از قبل ساخته شده Angular میباشد.
<h3>{{'12345679' | perNumber}}</h3>
یکسری از Pipeهای مربوط به زبان فارسی در گیتهاب بنده پیاده سازی شده است که نیازمند همکاری دوستان است. لطفا جهت همکاری برای ساخت یک ابزار جامع برای زبان فارسی در Angular به این لینک مراجعه کنید.
Pipeها و تشخیص تغییرات
Angular برای اعمال Pipe بر روی Template expressions بایستی تمامی رخدادهای برنامه را تحت نظر قرار داده و با مشاهده هر تغییری بر روی عبارت ورودی Pipe، فراخوانی Pipe را آغاز کند. از جمله این رخدادها میتوان به رخداد mouse move، timer tick، server response و فشرده شدن کلیدهای ماوس و یا کیبورد اشاره کرد. واضح است که بررسی تغییرات عبارت در این همه رخداد میتواند مخرب باشد و بر روی کارآئی (Performance) تاثیر منفی خواهد گذاشت. اما Angular برای حل این مشکل و همچنین هنگام مشاهده سریع تغییرات هنگام استفاده از Pipeها، الگوریتمهای سریع و سادهای در نظر گرفته است.
در قسمت بعد با انواع Pipeها در Angular و همچنین نحوه پیاده سازی آنها، آشنا خواهیم شد.
<link href="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Content/css")"rel="stylesheet" type="text/css" /> <link href="@System.Web.Optimization.BundleTable. Bundles.ResolveBundleUrl("~/Content/themes/base/css")" rel="stylesheet" type="text/css" /> <script src="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Scripts/js")"></script>
<link href="/Content/css?v=ji3nO1pdg6VLv3CVUWntxgZNf1z" rel="stylesheet" type="text/css" /> <link href="/Content/themes/base/css?v=UM624qf1uFt8dYti" rel="stylesheet" type="text/css" /> <script src="/Scripts/js?v=4h5lPNUsLiFoa0vqrItjS2Jp"></script>
BundleTable.Bundles.RegisterTemplateBundles();
public static void EnableBootstrapBundle(this BundleCollection bundles) { var bootstrapCss = new Bundle("~/bootstrap/css", new CssMinify()); bootstrapCss.AddFile("~/css/bootstrap.css"); bootstrapCss.AddFile("~/css/bootstrap-responsive.css"); bootstrapCss.AddFile("~/css/application.css"); bootstrapCss.AddFile("~/css/prettify.css"); bundles.Add(bootstrapCss); var bootstrapJs = new Bundle("~/bootstrap/js", new JsMinify()); bootstrapJs.AddFile("~/js/jquery-1.7.1.js"); bootstrapJs.AddFile("~/js/bootstrap.js"); bootstrapJs.AddFile("~/js/prettify.js"); bundles.Add(bootstrapJs); }
BundleTable.Bundles.EnableBootstrapBundle();
<link href="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/bootstrap/css")" rel="stylesheet"> <script src="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/bootstrap/js")"></script>
<link href="/bootstrap/css?v=uBlJsuIAGAF93nUTTez8" rel="stylesheet"> <script src="/bootstrap/js?v=O6HaHC8QqtB"></script>
قبل از شروع، فرض را بر آن میگیریم که حداقل نیازهای یک پروژهی Angular را آماده کرده اید. سپس یک پوشهی جدید را به نام layout میسازیم و layoutهای مربوطه را در آن ایجاد میکنیم. با دستور زیر یک کامپوننت جدید را که layout ما خواهد شد، با نام دلخواهی ایجاد میکنیم:
ng g c Loginlayout
ng g c homelayout
در ادامه Loginlayout را باز کرده و تنظیمات زیر را لحاظ کنید:
<div style="width: 100%;height: 250px;background-color: aquamarine"> <h1>Header</h1> </div> <router-outlet></router-outlet> <div style="width: 100%;height: 250px;background-color: brown"> <h1>Foother</h1> </div>
اکنون وارد کامپوننت home layout شوید و دقیقا مانند قبل، تنظیمات دلخواه خود را انجام داده و همچنین <router-outlet></router-outlet> راهم درون جائیکه میخواهید به صورت پویا باشد بگذارید.
تا اینجا ما فقط layoutها را طراحی کردیم. در ادامه در ریشهی پروژه، سه کامپوننت را به نامهای Home , Login, About میسازیم. Home و About دارای یک قالب و Login هم داری قالب مخصوص به خود میباشد.
سپس وارد کامپوننت آغازین برنامه (app.component.html) شوید و در آن <router-outlet></router-outlet> را وارد کنید. در اینجا دیگر نیازی به نوشتن تگهای خاص دیگری نیست.
در ادامه به اصلیترین قسمت، یعنی مسیریابی میرسیم. وارد app.module.ts شوید و آن را به صورت زیر تنظیم کنید:
export const routes: Routes = [ { path: 'Loginlayout', component: LoginlayoutComponent , children: [ { path: 'Login', component: LoginComponent, pathMatch: 'full'} ] }, { path: 'Homelayout', component: HomelayoutComponent, children: [ { path: 'About', component: AbouComponent, pathMatch: 'full'}, { path: 'Home', component: HomeComponent, pathMatch: 'full'} ] } ];
همچنین برای اینکه مشخص شود کدام کامپوننت به عنوان کامپوننت پیشفرض نمایش داده شود، به صورت زیر عمل میکنیم:
path: '', component: HomelayoutComponent, children: [ { path: '', component:HomeComponent, pathMatch: 'full'} ]
کدهای کامل این مطلب را میتوانید از اینجا دریافت و یا به صورت آنی آزمایش کنید.
دریافت و نصب پیشنیازها
برای نمایش loading bar در بالای صفحه، از کامپوننت ng2-slim-loading-bar استفاده خواهیم کرد. دمویی از آنرا در اینجا میتوانید مشاهده کنید.
برای نصب آن، ابتدا دستور ذیل را در ریشهی پروژه اجرا کنید:
npm install ng2-slim-loading-bar --save
"styles": [ "../node_modules/ng2-slim-loading-bar/style.css", "styles.css" ],
import {SlimLoadingBarModule} from 'ng2-slim-loading-bar'; @NgModule({ imports: [ //... SlimLoadingBarModule.forRoot() ] }) export class AppModule { }
پس از این مقدمات، هرجایی که نیاز به استفادهی از آن با کدنویسی باشد، ابتدا سرویس SlimLoadingBarService آنرا به سازندهی کلاس کامپوننت مدنظر تزریق میکنیم و سپس از متدهای start ،stop و complete آن میتوان استفاده کرد.
import {SlimLoadingBarService} from 'ng2-slim-loading-bar';
<ng2-slim-loading-bar></ng2-slim-loading-bar>
تدارک یک LoaderInterceptor برای استفاده از ng2-slim-loading-bar
در ادامه میخواهیم هر زمانیکه در سراسر برنامه، درخواست HTTP ایی شروع شد، این کامپوننت نمایش داده شود و در پایان درخواست و یا درصورت بروز خطایی، پایان یابد و مخفی شود. برای این منظور یک Interceptor جدید را به صورت ذیل به پوشهی Core برنامه اضافه میکنیم:
ng g s core/interceptors/LoaderInterceptor --spec false
import { Injectable } from "@angular/core"; import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from "@angular/common/http"; import { Observable } from "rxjs/Observable"; import "rxjs/add/operator/do"; import { SlimLoadingBarService } from "ng2-slim-loading-bar"; @Injectable() export class LoaderInterceptorService implements HttpInterceptor { constructor(private loadingBar: SlimLoadingBarService) { } intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // start our loader here this.loadingBar.start(); return next.handle(req).do( (event: HttpEvent<any>) => { if (event instanceof HttpResponse) { this.loadingBar.complete(); } }, (err: any) => { this.loadingBar.complete(); }); } }
- برای پیاده سازی یک interceptor جدید، نیاز است کلاس سرویسی را که HttpInterceptor را پیاده سازی میکند، ایجاد کنیم. برای تکمیل این پیاده سازی نیاز است متد intercept را با امضایی که مشاهده میکنید، تعریف کنیم:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
- کار پارامتر next تنظیم و بازگشت یک HttpEvent observable توسط متد handle آن است.
برای نمونه در اینجا ابتدا سرویس SlimLoadingBarService به سازندهی کلاس interceptor تزریق شدهاست. سپس توسط آن میتوان به متدهای start و complete این کامپوننت دسترسی یافت. برای مثال در ابتدای کار گوش فرادادن به درخواست جاری، متد start فراخوانی شدهاست و سپس زمانیکه پاسخی از سرور دریافت شده و یا خطایی رخداده، متد complete آن فراخوانی شدهاست.
ثبت و معرفی LoaderInterceptorService به سیستم
برای معرفی interceptor تهیه شده، به فایل app.module.ts مراجعه کرد و قسمت providers آنرا به نحو ذیل تکمیل میکنیم:
@NgModule({ providers: [ { provide: HTTP_INTERCEPTORS, useClass: LoaderInterceptorService, multi: true } ] }) export class AppModule {}
آزمایش برنامه
اکنون اگر قسمتهای مختلف برنامه را که با HttpClient جدید کار میکنند بررسی کنید، متوجه خواهید شد که با شروع هر درخواست، loading bar قرمزی در بالای صفحه ظاهر میشود و در پایان درخواست، به صورت خودکار مخفی میگردد. نکتهی مهم این روش عدم نیاز به تغییری در قسمتهای مختلف برنامه است. این interceptor سراسری است و به صورت یکسانی بر روی کل برنامه تاثیر میگذارد.
مبانی TypeScript؛ ماژولها
در WPF و Silverlight میتوان با استفاده از مقید سازی (DataBinding) کنترلها را به منبعهای داده متصل کرد. این منابع به چند شیوه مختلف مانند استفاده مستقیم از خصوصیتSource قابل دسترسی هستند. یکی از این روش ها، ارث بری از DataContext نزدیکترین والد است.
همانطور که گفته شدDataContext هر کنترل، توسط تمامی فرزندان آن قابل دسترسی است. اما در بعضی مواقع، زمانیکه کنترل فرزند، بخشی از visual یا logical tree نباشند، دسترسی به DataContext وجود ندارد.
برای مثال زمانی که نیاز است خصوصیت ItemsSource مربوط به یک به لیستی خارج از ItemsSource کنترل DataGrid DataGridTemplateColumn مثلا به لیستی درون ViewModel مربوط به Window در مثال زیر مقید شود، به صورت معمول باید به این صورت عمل کرد:
ViewModel :
public List<People> ComboBoxDataSource{get; set;}
: XAML
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" x:Name="this"> <Grid> <DataGrid ItemsSource="{Binding DataCollection}"> <DataGrid.Columns> <DataGridComboBoxColumn ItemsSource="{Binding DataContext.ComboBoxDataSource, ElementName=this}"/> </DataGrid.Columns> </DataGrid> </Grid> </Window>
با اینکه همه چیز درست به نظر میرسد اما در عمل هیچ اتصالی صورت نمیگیرد و در پنجره Output ویژوال استادیو خطای زیر مشاهده میشود:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=ComboBoxDataSource; DataItem=null; target element is 'DataGridComboBoxColumn' (HashCode=17334644); target property is 'ItemsSource' (type 'IEnumerable')
این خطا مشخص میکند که WPF نمیتواند تشخیص بدهد که کدام FrameWorkElement قرار است از DataContext استفاده کند؛ چرا که همانطور که قبلا عنوان شد DataGridTemplateColumn بخشی از visual یا logical treeنیست.
برای مشکل فوق در صورتیکه خصوصیت مورد نظر، یک خصوصیت از فرزندان کنترل باشد، از طریق استایلها میتوان مشکل را حل کرد. برای مثال به جای ItemSource مربوط به DataGridComboBoxColumn میتوان خصوصیت ItemSource کنترل ComboBox درون آن را تنظیم کرد.
<DataGridComboBoxColumn DisplayMemberPath="FirstName"> <DataGridComboBoxColumn.EditingElementStyle> <Style TargetType="ComboBox"> <Setter Property="ItemsSource" Value="{Binding DataContext.ComboBoxDataSource , ElementName=this}"/> </Style> </DataGridComboBoxColumn.EditingElementStyle> </DataGridComboBoxColumn>
اما در صورتیکه نیاز باشد یک خصوصیت از خود DataGridComboBoxColumn مانند Visibility مقید سازی شود، روش بالا کارساز نخواهد بود. برای حل مشکل فوق میتوان از کلاسهای Freezable استفاده کرد؛ چرا که این کلاسها میتوانند از DataContext ارث بری کنند حتی زمانیکه بخشی از visual یاlogical tree نباشند. برای این کار میتوان کلاس زیر را ایجاد کرد:
public class DataBindingHelper : Freezable { protected override Freezable CreateInstanceCore() { return new DataBindingHelper(); } public object Data { get { return (object)GetValue(DataProperty); } set { SetValue(DataProperty, value); } } public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(object), typeof(DataBindingHelper), new UIPropertyMetadata(null)); }
<DataGrid.Resources> <local:DataBindingHelper x:Key="bindingHelper"Data="{Binding}"/> </DataGrid.Resources>
و هنگام مقید سازی خصوصیت Visibility مربوط به DataGridComboBoxColumn، از نمونه ساخته شده به عنوان Source استفاده نمود.
<DataGridComboBoxColumn Visibility="{Binding Data.IsVisible,Converter={StaticResource visibilityConverter},Source={StaticResource bindingHelper}}"/>
تنظیم اول: تغییر نحوهی نمایش پیش فرض فایلهای XAML
اگر فایل XAML شما اندکی حجیم شود نمایش آن در VS.NET کمی طولانی خواهد شد و حالت پیش فرض نمایش در VS.NET هم split view mode است (نمایش XAML و پیش نمایش آن با هم). این مورد هم پس از مدتی تبدیل به عذاب میشود. برای رفع آن میتوان حالت پیش فرض نمایش یک فایل XAML را به XAML View تنها تغییر داد.
برای این منظور به منوی Tools ، گزینهی Options و سپس قسمت تنظیمات Text editor مراجعه کنید. در اینجا در قسمت XAML ، گزینهی Miscellaneous را انتخاب کرده و سپس "Always open documents in full XAML view" را تیک بزنید.
حتی ممکن است این مورد هم رضایت بخش نباشد. در این حالت میتوان ویرایشگر پیش فرض را کلا تغییر داد. Design tab را در پایین صفحه از دست میدهیم اما هنوز intellisense کار میکند و اگر نیاز به designer بود فقط کافی است کلیک راست کرده و گزینهی View designer را انتخاب کرد:
روی یک فایل XAML دلخواه کلیک راست کرده و گزینهی Open with را انتخاب کنید. سپس "Source Code (Text) Editor" را انتخاب کرده و روی دکمهی Set as Default کلیک کنید. تمام!
هر چند Blend این مشکلات را ندارد و با فایلهای حجیم XAML به خوبی کاری میکند.
تنظیم دوم: تغییر نحوهی نمایش مشکلات ناشی از Binding
عموما اگر مشکلاتی در حین عملیات Binding در WPF یا Silverlight وجود داشته باشند، خطاها در Debugger Output Window نمایش داده میشوند. حالت پیش فرض هم فقط روی Error تنظیم شده است به این معنا که warning ها را مشاهده نخواهید کرد. برای تغییر این مورد باید به صورت زیر عمل کرد:
به منوی Tools ، گزینهی Options و سپس قسمت تنظیمات Debugging مراجعه کنید. گزینهی Output Window -> WPF Trace Settings را انتخاب نمائید. سپس در اینجا قسمت WPF trace settings را یافته و مقدار پیش فرض Data binding را که به Error تنظیم شده است، به Warning تنظیم نمائید.