مطالب
کار با مجموعه‌ها ( الگوی طراحی Composite)
یکی از پیچیدگی‌های معمول در کد، کلاسی است که دارای مجموعه‌ای باشد. مشکل اصلی با چنین طراحی این است که تمام عملیات باید از وضعیت مجموعه آگاه باشند. چرا مجموعه‌ها خیلی پیچیده هستند؟
داشتن مجموعه، خود با بسیاری از سوالات همراه است. آیا مجموعه حاوی اشیایی است یا خالی است؟ برخی از توابع تجمعی را نمی‌توان در مجموعه‌های خالی محاسبه کرد. به عنوان مثال Maximum در یک مجموعه خالی تعریف نشده است. بعضی دیگر از توابع تجمعی به این مشکل اهمیت نمی‌دهند، مانند sum و count که هر دوی آنها مقدار صفر را بر میگردانند.
 وقتی یک کلاس مجموعه‌‌ای را کنترل می‌کند، چیزهای زیادی برای فکر کردن وجود دارد. آیا عملیاتی که فراخوانی می‌کنیم ایمن است؟ آیا باید نتیجه قبل از ادامه به نحوی اصلاح شود؟ آیا آن را باید بر روی کل مجموعه تکرار کند و یا بر روی یک عنصر؟ 

با مجموعه‌های موجود چه کاری را باید انجام دهیم؟
کلاس‌هایی که دارای مجموعه هستند، تمایل به رشد دارند. این رشد‌ها هیچ ارتباطی با مسئولیت‌های کلاس ندارند و تنها هدفشان این است که کلاس کار کند. راه حل طبیعی برای این مشکل این است که کلاس جدیدی را تعریف کنیم تا هدف آن نگهداری از مجموعه باشد. این کلاس مسئول فیلتر کردن عناصر، شمارش و اعمال عملیات بر روی عناصر و جمع آوری نتایج هست. هدف نهایی این refactoring، ساده سازی کلاس اصلی و تمرکز بر روی domain model هست.

الگوی طراحی Composite
در بسیاری از موارد، عملیاتی را که بر روی یک شیء قابل تعریف هستند می‌توان بر روی مجموعه‌ای از اشیاء نیز تعریف کرد؛ مانند یک تابع تجمعی که نتیجه‌ای را بر می‌گرداند. این عمل می‌تواند بر روی یک شیء و یا گروهی از اشیاء فراخوانی شود. اگر بتوانیم یک اینترفیس مشترک را بر روی یک عنصر و مجموعه تعریف کنیم، آنگاه می‌توانیم الگوی Composite را بر روی آن اعمال نمائیم.

یک مثال
فرض کنید می‌خواهیم یک نقاش را برای رنگ آمیزی یک خانه استخدام کنیم. نقاش به تعدادی روز نیاز دارد تا کار را تمام کند. اکنون فرض کنید که ما می‌خواهیم چندین نقاش را برای همکاری با هم استخدام کنیم. درنتیجه زمان لازم برای پایان دادن به کار، کوتاه‌تر می‌شود.
پیاده سازی نقاش به صورت زیر است: 
class Painter
{
    private readonly float daysPerHouse;

    public Painter(float daysPerHouse)
    {
        this.daysPerHouse = daysPerHouse;
    }

    public float EstimateDaysToPaint(int houses)
    {
        return houses * daysPerHouse;
    }
}
نقاش فقط خانه‌ها را رنگ می‌کند. برآورد کار نقاشی به این صورت است که تعداد خانه‌ها را با زمانیکه برای هر خانه صرف می‌کند، بدست می‌آوریم.
ما می‌توانیم یک صاحب زمین را معرفی کنیم که این فرد چندین خانه را دارد:
class LandOwner
{
    private readonly Painter painter;
    private readonly int housesCount;

    public LandOwner(Painter painter, int housesCount)
    {
        this.painter = painter;
        this.housesCount = housesCount;
    }

    public void ManageHouses()
    {
        float daysToPaint = this.painter.EstimateDaysToPaint(this.housesCount);
        Console.WriteLine("Painting houses for {0:0.0} day(s).", daysToPaint);
    }
}
صاحب زمین، اشاره‌ای به یک نقاش دارد. هنگام مدیریت خانه‌ها، مالک به نقاش می‌گوید که چقدر زمان لازم است تا تمام خانه‌ها را رنگ کند و مشکلات زمانی آغاز می‌شوند که نقاش نمی‌تواند تمام کارها را در زمان معقولی انجام دهد.به این صورت مالک زمین، نقاشان بیشتری را استخدام می‌کند:
class LandOwner
{
    private readonly IEnumerable<Painter> painters;
    private readonly int housesCount;

    public LandOwner(IEnumerable<Painter> painters, int housesCount)
    {
        this.painters = new List<Painter>(painters);
        this.housesCount = housesCount;
    }
    ...
}
زمان لازم برای رنگ کردن خانه‌ها در شکل زیر نشان داده شده است: 

اکنون مالک زمین مسئولیت انجام این محاسبه را برعهده گرفته است؛ ولی این پیاده سازی کمی پیچیده‌تر می‌شود: 

class LandOwner
{
    private readonly IEnumerable<Painter> painters;
    private readonly int housesCount;
    public LandOwner(IEnumerable<Painter> painters, int housesCount)
    {
        this.painters = new List<Painter>(painters);
        this.housesCount = housesCount;
    }

    private float GetVelocity(Painter painter)
    {
        return painter.EstimateDaysToPaint(1);
    }

    private float GetTotalVelocity()
    {
        float sum = 0;
        foreach (Painter painter in this.painters)
            sum += 1  this.GetVelocity(painter);
        return   sum;
    }

    public void ManageHouses()
    {
        float daysToPaint = this.GetTotalVelocity() * this.housesCount;
        Console.WriteLine("Painting houses for {0:0.0} day(s).", daysToPaint);
    }
}

این پیاده سازی کمی پیچیده‌است؛ اما کار می‌کند و همچنین دارای مشکلاتی است. فرض کنید یکی از نقاشان صاحب شرکت، نقاشی است که نقاشان دیگر را استخدام می‌کند. حتی بدتر از آن این است که اگر شرکت نقاشی، شرکت دیگری را نیز همراه با نقاشان خود، استخدام کند. مالک زمین با سلسله مراتبی چندسطحی از نقاشان روبرو می‌شود. برآورد زمان لازم برای نقاشی خانه‌ها در چنین شرایطی برای مالک زمین دشوار خواهد شد. 


پیاده سازی  Composite

اگر تنها بتوانیم یک اینترفیس عمومی را از یک نقاش، بیرون بکشیم، سازماندهی نقاش‌ها راحت‌تر می‌شود:

interface IPainter
{
    float EstimateDaysToPaint(int houses);
}

مالک زمین دیگر کاری با مجموعه نقاش‌ها ندارد و در حال حاضر تنها یک نقاش انتزاعی را کنترل می‌کند: 

class LandOwner
{
    private readonly IPainter painter;
    private readonly int housesCount;
    public LandOwner(IPainter painter, int housesCount)
    {
        this.painter = painter;
        this.housesCount = housesCount;
    }

    public void ManageHouses()
    {
        float daysToPaint = this.painter.EstimateDaysToPaint(this.housesCount);
        Console.WriteLine("Painting houses for {0:0.0} day(s).", daysToPaint);
    }
}

اینبار مالک زمین فقط ارجاعی را به یک نقاش انتزاعی دارد. از سوی دیگر، کلاس نقاش دست نخورده باقی می‌ماند و تنها رابط IPainter را پیاده سازی می‌کند: 

class Painter: IPainter
{
    ...
}

حالا می‌توانیم نتیجه آن را ببینیم. ما آماده تعریف یک عنصر Composite هستیم که خود و عناصرش، اینترفیس IPainter را پیاده سازی کرده‌اند. 

class PaintingCompany: IPainter
{
    private readonly IEnumerable<IPainter> painters;

    public PaintingCompany(IEnumerable<IPainter> painters)
    {
        this.painters = new List<IPainter>(painters);
    }

    private float GetVelocity(Painter painter)
    {
        return painter.EstimateDaysToPaint(1);
    }

    private float GetTotalVelocity()
    {
        float sum = 0;
        foreach (Painter painter in this.painters)
            sum += 1  this.GetVelocity(painter);
        return   sum;
    }

    public float EstimateDaysToPaint(int houses)
    {
        return this.GetTotalVelocity() * houses;
    }
}

این پیاده سازی شرکت نقاشی است. کد کلاس LandOwner قبلی که وظیفه آن کنترل نقاش‌ها بود، به این کلاس منتقل شده‌است. تفاوت این است که شرکت نقاشی اکنون تعدادی نقاش انتزاعی را مدیریت می‌کند. از انتزاعات می‌توان دو حالت را در نظر گرفت: به صورت تک و یا به صورت گروه. این مورد قدرت نوع انتزاعی است در برنامه نویسی شیء گرا که در اینجا خودش را به صورت یک نقاش و یا گروهی از افراد که با هم کار می‌کنند، نشان می‌دهد.


نتیجه گیری

در این مقاله ما به یک نمونه از الگوی طراحی Composite پرداختیم. با استفاده از الگوی Composite، شیوه‌ای که کلاس‌ها با مجموعه‌ها برخورد می‌کنند، بسیار ساده‌تر شده‌است. کار با مجموعه‌ها، کد را پیچیده‌تر کرده و باعث می‌شود کلاس، کاری بیشتر از مسئولیت‌های خود را انجام دهد که ربطی به آن ندارد.

مطالب
نکاتی توصیه ای برای برنامه نویسی اندروید : قسمت دوم
در ادامه‌ی قسمت اول، ده مورد دیگر از نکات کاربردی را بیان می‌کنیم.

  یازده. در جاوا رویدادها با استفاده از اینترفیس‌ها پیاده سازی می‌شوند. برای نامگذاری یک رویداد، قاعده آن در جاوا بدین شکل است که نام‌ها به صورت (+ ) Camel نوشته شده و آخرین عبارت هم Listener باشد و نیازی هم به حرف I در نامگذاری اینترفیس نیست؛ چون همه می‌دانند که این Listener آخری یعنی رویدادی که با اینترفیس پیاده سازی شده است و استفاده از I بی معنی است. هر چند بر خلاف دات نت، در اینجا استفاده از قاعده I چندان متداول نیست.
 public interface CopyFileListener
    {
        void PublishProgress(long fileSize,long copiedSize);
    }

دوازده. گوگل اینترفیس‌هایی را که برای رویدادها میسازد، داخل کلاس اصلی تعریف میکند. پس بهتر هست که شما هم همین روند را ادامه بدید و از این قاعده خارج نشوید. اگر خوب دقت کرده باشید، در برنامه نویسی اندروید تمام اینترفیس‌ها داخل کلاس اصلی هستند:
 textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        });
در کد بالا رویداد OnclickListener در خود کلاس View تعریف شده است. پس ما هم بهتر هست همین کار را بکنیم:
public class MemoryWare {

    public interface CopyFileListener
    {
        void PublishProgress(long fileSize,long copiedSize);
    }
....
}
یک نکته دیگر اینکه موقعی که یک رویداد را به یک پراپرتی set انتساب می‌دهیم، رسم این است که نام آن پراپرتی با عبارت SetOn آغاز شود و با نام اینترفیس پایان یابد:
SetOnClickListener
البته اگر کلاس شما لیستی از رویدادها را درست میکند بهتر است از عبارت Add به جای SetOn استفاده کنید و برای آن یک Remove هم قرار دهید. نمونه آن را می‌توانید در کد زیر ببینید:
 editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });

سیزده
. آداپتورها و آداپتور ویوها (چون لیست) قسمت مهمی از برنامه‌های اندرویدی به شمار می‌آیند؛ تا حدی که در بیشتر برنامه‌های ساده هم حضور پررنگی دارند. ولی برای استفاده از این آداپتورها باید بدانید که نحوه کار آن‌ها چگونه است. بسیاری از کاربران در این قسمت اشتباهات زیادی می‌کنند. اگر در stackOverflow هم در اینباره نگاه کنید، با حجم انبوهی از سوالات روبرو می‌شوید و فقط به خاطر اینکه نحوه کارکرد آن را نمی‌دانند، به مشکل برخورده‌اند.
کلاس BaseAdapter اصلی‌ترین کلاس آداپتور هاست که بقیه از آن مشتق شده‌اند و معروفترین مشتقات آن، کلاس‌های CursorAdapter و ArrayAdapter هستند که امکانات بیس آداپتور را افزایش داده‌اند.به عنوان مثال در کد پایین از ArrayAdapter استفاده شده است.


نحوه کار یک آداپتور بدین صورت است که متدی را به نام GetView با قابلیت override دارد که با هر تعداد آیتم موجود صدا زده می‌شود. ولی اگر تصور کنیم فقط چند صدهزار آیتم هم داشته باشیم، آیا واقعا اجرا می‌شود؟ جواب این سوال این است که با هر بار اسکرولی که شما میکنید آیتم‌های بعدی ایجاد می‌شوند ولی باز این سوال پیش می‌آید که هر آیتم برای خود جداگانه تشکیل می‌شود؟ مطمئنا جواب خیر است. آداپتورها از سیستمی به نام ViewRecycler برای کش کردن آیتم‌های ایجاد شده استفاده می‌کنند و با هر اسکرولی که انجام می‌شود آیتم‌های بعدی از روی آیتم‌های قبلی که قبلا از صفحه خارج شده‌اند، ساخته می‌شوند و آیتم‌های کش شده قبلی را با پارامتری با نام convertView به دست شما می‌رساند.
کد زیر را ببینید:
  @Override
    public View getView(int position, View rowView, ViewGroup parent) {

        ViewHolder viewHolder=null;
        if(rowView==null)
        {
            // 1. Create inflater
            LayoutInflater inflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            // 2. Get rowView from inflater
            rowView = inflater.inflate(R.layout.row_bank_group_list, parent, false);
            viewHolder=new ViewHolder();
            viewHolder.txtGroupName=(TextView) rowView.findViewById(R.id.text_groupName);
            rowView.setTag(viewHolder);
        }
        else
        {
            viewHolder=(ViewHolder)rowView.getTag();
        }
        viewHolder.txtGroupName.setText(getItem(position).getName());
        viewHolder.txtGroupName.setTypeface(new FontSystem().get_General_Font(context));
        viewHolder.txtGroupName.setTextColor(context.getResources().getColor(R.color.black));

        return rowView;
    }
کد بالا ابتدا بررسی میکند که آیا convertView نال است یا خیر. اگر نال بود به این معنا است که کش برای شما چیزی نداشته است و باید آیتم جدیدی را بسازید. پس اشیاء موجود در آن را در حافظه می‌آورید و مقداردهی می‌کنید. ولی اگر برابر نال نباشد، یعنی کش حاوی یک سری آیتم است که قبلا ایجاد شده‌اند. پس نیاز به inflate کردن مجدد ندارد و میتوانید  همان را مستقیما مورد استفاده قرار دهید و با مقادیر جدید آن را ست کنید.
کلاس داخلی ViewHolder هم یک الگو برای عدم بررسی Viewهای داخل آن است که نیازی به یافتن و تبدیل مجدد آن‌ها نداشته باشید. در این روش شیء، داخل خصوصیت tag آیتم قرار گرفته است و وقتی از کش برداشته شود، خاصیت تگ آن را می‌خوانیم و مستقیما مورد استفاده قرار می‌دهیم. در این حالت شما بهترین استفاده را از پردازش‌ها و حافظه، می‌کنید.

چهارده. یکی از کارهایی را که قبل از کار کردن در یک مسیر فیزیکی باید انجام دهید این است که مطمئن باشید اجازه نوشتن در آن ناحیه را دارید یا خیر. در غیر اینصورت برنامه شما با خطای FC روبرو می‌شود و اجرای آن خاتمه می‌یابد. به همین دلیل اکثر برنامه نویسان از متد CanWrite در کلاس File استفاده می‌کنند. ولی در هنگام استفاده از این متد باید دقت داشته باشید که کلاس File فقط باید حاوی مسیر باشد و اسمی از فایل مربوطه در آن نباشد. دلیل هم آن است که این احتمال می‌رود اگر فایلی هم وجود نداشته باشد، مقدار false را به شما برگرداند. مثال زیر قرار است فایلی را در کارت حافظه بنویسید، ولی بررسی اجازه نوشتن در مسیر، اشتباه است:
File file=new File(sdcardPath,fileName);

if(file.CanWrite())
{
  .....
}
کد بالا را به طور صحیح بازنویسی می‌کنیم:
File file=new File(sdcardPath);

if(file.CanWrite())
{
file=new File(sdcardPath,filePath);
  .....
}
اگر هنگام تست این کد مشکلی نداشتید، دلیل بر صحت کد نیست. بلکه بنابر تجربه شخصی من، زمانی این مشکل پیش آمده بود که روی چند گوشی تست شده و بعدها مشکل در گوشی پیش آمده بود که هم مدل و دقیقا مشابه یکی از گوشی‌های تستی بود.

پانزده. کارت حافظه خارجی: همه برنامه نویسان اندروید حداقل یکبار از کد زیر استفاده کرده اند:
Environment.getExternalStorageDirectory()
بررسی‌ها در استک اورفلو نشان می‌دهد که برنامه نویسان، گزارش عملکرد اشتباهی را از این متد دارند. ولی حقیقت این است که این متد به هیچ عنوان مقدار اشتباهی را بر نمی‌گرداند. بلکه منطق آن متفاوت از چیزی است که شما فکر می‌کنید. وقتی ما صحبت از حافظه خارجی برای یک گوشی میکنیم، منظور همان کارت حافظه‌ای است که به طور جداگانه داخل گوشی قرار داده‌ایم و انتظار داریم متد بالا آدرس و مدخل همین کارت را برای ما فراهم کند. ولی در کمال تعجب می‌بینیم که آدرس حافظه داخلی برگردانده می‌شود. پس باید ببینیم اندروید از  آن چه انتظاری دارد؟
هر برنامه‌ای که در اندروید نصب می‌شود در مسیر
/Data/Data
یک دایرکتوری با نام خود دارد مثل:
/Data/Data/Info.Dotnettips.MyApp
این دایرکتوری تنها متعلق به این برنامه بوده و کسی جز Root به آن دسترسی ندارد. اندروید این دایرکتوری را به عنوان حافظه داخلی در نظر میگیرد که برای کار با آن نیاز به هیچ آدرس دهی نیست. ولی در کنار این دایرکتوری حافظه داخلی خود گوشی که در آن انبوه فایل‌های خود را ذخیره می‌کنید هم هست که اندروید آن را حافظه خارجی می‌پندارد. حال آن حافظه‌ای را که شما جداگانه به صورت یک کارت یا USB OTG متصل میکنید، به عنوان حافظه خارجی در نظر نمیگیرد. در صورتی که شما چنین انتظاری را دارید، برای حل این مشکل بهتر است از کدهای موجود مثل کد زیر استفاده کنید:
    /**
     * it will returns sd path for you
     *  <p>
     *  <b>Required Permission: </b>android.permission.READ_EXTERNAL_STORAGE<br/>
     * </p>
     * @return
     */
    public  List<String> GetExternalMounts() {
        final List<String> out = new ArrayList<>();
        String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";
        String s = "";
        try {
            final Process process = new ProcessBuilder().command("mount")
                    .redirectErrorStream(true).start();
            process.waitFor();
            final InputStream is = process.getInputStream();
            final byte[] buffer = new byte[1024];
            while (is.read(buffer) != -1) {
                s = s + new String(buffer);
            }
            is.close();
        } catch (final Exception e) {
            e.printStackTrace();
        }

        // parse output
        final String[] lines = s.split("\n");
        for (String line : lines) {
            if (!line.toLowerCase(Locale.US).contains("asec")) {
                if (line.matches(reg)) {
                    String[] parts = line.split(" ");
                    for (String part : parts) {
                        if (part.startsWith("/"))
                            if (!part.toLowerCase(Locale.US).contains("vold"))
                                if(new File(part).canWrite())
                                    out.add(part);
                    }
                }
            }
        }
        return out;
    }

شانزده.
یکی از روش‌های انتقال اطلاعات بین اکتیویتی‌ها مختلف استفاده از Extras هاست که شما با تعیین یک نام یا کلید، اطلاعات مربوطه را ارسال و توسط همان کلید؛ اطلاعات را در اکتیویتی مقصد دریافت میکنید:
notesIntent.putExtra("PartyId", PartyId);
startActivity(notesIntent);
و در مقصد:
PartyId=getIntent().getLongExtra("PartyId",0);
در این حالت بهتر است با این رشته‌ها نیز همانند مورد شماره دو در قسمت اول رفتار شود تا نیازی به نوشتن و تکرار این نام‌ها نباشد. ولی با یک نگاه به استانداردهای نوشته شده در خود اندروید و بسیاری از کتابخانه‌های ثالث در می‌یابیم که بهترین روش این است که این کلید‌ها را به صورت متغیرهای ایستا در خود اکتیویتی ذخیره کنیم؛ در این حالت هر کلید در جایگاه واقعی خودش قرار گرفته است. نمونه‌ای از این استفاده را می‌توانید در کتابخانه FilePicker مشاهده کنید:
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, false);
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_FILE);

هفده.
قواعد نامگذاری: برای نامگذاری متغیرها از قانون CamelCase استفاده میکنیم. ولی برای حالات زیر از روش‌های دیگر استفاده می‌شود:
  • برای ثابت‌ها از حروف بزرگ و _ استفاده کنید.
  • برای متغیرهای خصوصی از حرف m در ابتدای نام متغیر استفاده کنید.
  • برای متغیرهای استاتیک از حرف s در ابتدای نام متغیر استفاده کنید.
نمونه ای از این نامگذاری که توسط مستندات گوگل سفارش شده است:
public class MyClass {
    public static final int SOME_CONSTANT = 42;
    public int publicField;
    private static MyClass sSingleton;
    int mPackagePrivate;
    private int mPrivate;
    protected int mProtected;
}

هجده:
قاعده نظم و ترتیب در import‌ها توسط مستندات گوگل بدین شکل تعریف شده است:
  1. نام پکیج‌های ارائه شده توسط گوگل
  2. نام پکیج‌های ثالث
  3. نام پکیج‌های موجود در java و javax
  4. پکیج‌های موجود در پکیج اصلی
البته رعایت این قاعده کمی سخت و عموما بدون اجراست ولی نگران نباشید. از آنجایی که ادیتور از طرف jet brains ارائه شده‌است و عمل import به طور خودکار صورت میگیرد و با ابزارهای دیگری که در دل این ادیتور قرار گرفته است، این عمل به طور خودکار انجام می‌گیرد.

نوزدهم. مرتب سازی متدهای دسترسی یک کلاس: بسیار خوب است که همیشه کدهای ما نظم خاصی را داشته باشد تا پیگیری‌های شخصی و تیمی در آن راحت‌تر صورت بگیرد. برای مثال در یک کلاس ابتدا متدهای public و سپس private قرار گیرند و الی آخر.
الگوی عمومی که برای کار با جاوا صورت گرفته است به شکل زیر می‌باشد:
public, protected, private,abstract, static, transient, volatile, synchronized, final, native.
البته این متدهای دسترسی بسته به فیلد بودن و متد بودن نیز تغییر میکند. به عنوان مثال ابتدا فیلدها در نظر گرفته می‌شوند و سپس متدها و ...
ادیتور intelij شامل تنظمیاتی برای مرتب سازی کدهاست که در این مورد بسیار سودمند است. با طی کردن مسیر زیر می‌توانید برای آن ترتیب اینگونه موارد را مشخص کنید.
Settings>Editor>Code Style>Arrangement
در اینجا شما امکان تعاریف این ترتیب‌ها را دارید. البته بهتر هست در حالت پیش فرض باشد تا حالتی عمومی بین افراد مختلف برقرار باشد.

در تصویر بالا متدها به ترتیب متدهای دستری بین بلوک‌های کامنت method start و method end قرار گرفته اند.

 همچنین شامل گزینه‌های دیگری نیز میباشد که به نظرم فعال کردنشان بسیار خوب است. گزینه keep overridden methods together به شما کمک می‌کند تا متدهایی را که رونویسی می‌شوند، در کنار یکدیگر قرار بگیرند که برای کلاس‌های اندرویدی مثل اکتیویتی‌ها و فرگمنت‌ها و ... بسیار خوب است. گزینه مفید دیگر Keep dependent methods together است که در دو حالت عمقی یا خطی متدهای وابسته (متدهایی که متدهای دیگر را در آن کلاس صدا میزنند) در کنار یکدیگر قرار میدهد و مابقی گزینه‌ها، که بسیار سودمند هست. به هر حال هر قاعده‌ای را که برای خود انتخاب میکنید اگر در حالت پیش فرض نیست بهتر است در مستندات پروژه ذکر شود تا افراد دیگر سریعتر به موضوع پی ببرند.

قسمت بیستم. این مورد برای افراد تازه کار می‌باشد که تازه اندروید استادیو را باز کرده‌اند و مشغول کدنویسی می‌باشند. یکی از مواردی که در همان مرحله اول به آن برمیخورید این است که intellisense  ادیتور به بزرگی و کوچکی حروف حساس است و تنها با حرف اول سازگاری دارد. برای تغییر این مسئله باید مسیر زیر را طی کنید:
Settings>Editor>Completion>Case-sensitive Completion>None
حالا با تایپ هر کلمه، دستورات و آبجکت‌هایی را که شامل آن کلمات باشند، به شما نمایش داده خواهند شد.

نظرات مطالب
ASP.NET Web API - قسمت اول
دوست عزیز، فکر کنم سوال من خیلی واضح باشه
مسئله اول این هستش که مواردی از OData هست که در WCF Data Services وجود داره، ولی در Web API خیر، OData یک سری استاندارد هستش، بالاخره باید یک جایی پیاده سازی بشه، مثل HTML 5، که قسمت‌های مختلفش در درصدهای متفاوت در مرورگرهای متفاوت پیاده سازی شده، در این میان Chrome بهتر از IE هستش، چرا ؟ چون استانداردهای بیشتری رو پیاده سازی کرده
دوم این که آیا شما به صورت عملی از Breeze js و Jay Data و WCF Data Services Client استفاده کرده اید ؟ درسته که اینها به OData وصل می‌شوند، ولی میزان امکانات اینها برای WCF Data Services قابل قیاس با Web API نیست.
سوال اصلی من با این تفاسیر این است :
اگر قبول کنیم که راهی برای دسترسی به Web API وجود ندارد، الا استفاده از jQuery Ajax و Http Client، شما به چه صورت یک پروژه بزرگ رو با Web API می‌نویسید ؟
Change Tracking رو چه جوری پیاده سازی می‌کنید ؟
به چه صورت در کلاینت هایی مانند اندروید، و یا Win RT و ... از Linq برای دسترسی به سرویس هاتون استفاده می‌کنید ؟
اگر فرض کنیم که می‌خواهیم یک سرویس عمومی بنویسیم که همه جا به سادگی قابل استفاده باشه، آیا از Web API استفاده می‌کنید ؟
خلاصه : مزیت واقعی Web API چیست و چه زمانی پروژه ای رو با Web API شروع می‌کنید ؟
موفق و پایدار باشید
نظرات مطالب
Portable Class Library چیست و چگونه از آن استفاده کنیم؟
دوستان، اصلا مطلب من رو مطالعه کردید ؟
بله، مسلما وقتی کدی در سیلورلایت کار نکنه، یعنی کار نمی‌کنه، حالا به هر روشی، مگه این که شما بفرمایید Portable Library قابلیت جدیدی رو برای مثال به سیلورلایت اضافه می‌کنه که "در حالت عادی" در دسترس نیست.
وقتی پروژه شما Silverlight و NET. اش جدا باشد، Silverlight ای اون هم از بقیه Silverlight ای‌های موجود در اینترنت می‌تونه استفاده کنه ( برای مثال WCF Data Services Client Library )، و هم می‌تونه از Portable‌های که Silverlight شون تیک خورده باشه استفاده کنه، مثل Post Sharp
اما وقتی شما به جای Add As Link از Portable استفاده می‌کنید، با این که در تعامل با WCF Data Services یک دست خط کد کاملا یکسان دارید، نمی‌تونید تو پروژه تون از WCF Data Services استفاده کنید.
یک وقت هست، شما از MVVM Light Toolkit دارید استفاده می‌کنید، کد WPF و Silverlight تون هم کاملا مشابه هستش، در این جا کار شما با Add As Link راه می‌افته، ولی با Portable نه
در حالت Portable شما System.Linq رو دارید، خوبه، در Add As Link هم اون رو دارید، ولی Expression.Interactivity رو فقط در Add As Link دارید، با این که کد می‌تونه 100% یک دست باشه
موفق و پایدار باشید
اشتراک‌ها
رفع خطای "NuGet package(s) that are missing"

بطور معمول این مشکل باید از طریق فعال کردن گزینه Package Restore همانطورکه در ادامه این ارور Enable NuGet Package Restore to download them. به آن اشاره می‌کند حل شود، ولی زمانیکه این اتفاق به دلیل بروزرسانی ویژوال استدیو و nuget ویا افزودن یک پروژه از قبل نوشته شده به Solution ای جدید رخ بدهد، مشکل از طریق فعال کردن Package Restore حل نمی‌شود.

رفع خطای "NuGet package(s) that are missing"
نظرات مطالب
فعالسازی Windows Authentication در برنامه‌های ASP.NET Core 2.0
سلام حقیقت من شخصا اعتقادی به استفاده از Windows-authentication برای اعتبار سنجی نداشتم ولی پروژه ای الان در دست دارم که ارتباط زیادی با اکتیو دایرکتوری داره.. به همین دلیل میخواستم بدونم:
 بخش دسترسی‌های کاربر رو هم میشه سمت اکتیودایرکتوری پیاده سازی کرد و برنامه از اون اطلاعات استفاده کنه یا چنین چیزی در Windows-authentication و اکتیودایرکتوری پیشبینی نشده. اگر قابل پیاده سازی بود ممنون میشم منبع معرفی کنید.
نظرات مطالب
نحوه ایجاد یک تصویر امنیتی (Captcha) با حروف فارسی در ASP.Net MVC
با سلام خدمت کلیه دوستان و همکاران گرامی
از اینکه نمی‌تونم جواب نظرات رو بدم عذرخواهی می‌کنم. (به دلیل خدمت سربازی :دی)
بنا به پیشنهاد جناب نصیری و دیگر دوستان یک مخزن کد Github برای این تصویر امنیتی درست کردم. اصلاح باگ‌های برطرف شده اون هم تا اینجا طبق نظرات همین مطلب اعمال شدن. هر کی می‌خواد تو این پروژه مشارکت داشته باشه PR بفرسته .
نظرات مطالب
ASP.NET MVC #18
سلام؛بنده هم همین مشکل رو دارم.
برای اینکه قسمت‌های مختلف که شما مطمئنا از ان اگاه نیستید و شاید بران تاثیر بگذاره رو نداشته باشید.
یک پروژه تازه رو باز کردم یک راست رفتم سراغ web.config این کد رو در قسمت system.web وارد کردم  
<authorization>
     <deny users="?" />
</authorization>
بعد بلافاصله یک کنترلر به نام Home ایجاد کردم و یک view برای index اد کردم...
قبل از اکشن index  هم  [AllowAnonymous] رو قرار دادم
ولی بعد از اجرا هنوز هم دسترسی ندارم ...
Access is denied.  

آیا دلیل و نکته است که رعایت نکرده ام؟
نظرات مطالب
معماری لایه بندی نرم افزار #1
من هم وقتی کار بر روی این معماری رو شروع کردم با مشکلات زیادی روبرو بودم و خیلی از مسائل برای من هم نامانوس و غیر قابل هضم بود. ولی بعد از اینکه چند پروژه نرم افزاری رو با این معماری پیاده سازی کردم فهم بیشتری نسبت به اون پیدا کردم و خیلی از مشکلات موجود رو با دقت بالا و با در نظر گرفتن تمامی الگوها رفع کردم. امیدوارم این حس مشترک بوجود بیاد. ولی دلیل اصلی ایجاد تکنولوژی‌ها و معماری‌های جدید اختلاف نظر بین علماست. این اختلاف نظر در اکثر مواقع میتونه مفید باشه. ممنون دوست عزیز
مطالب
معرفی یک ابزار گزارشگیری رایگان مخصوص WPF

تا صحبت از گزارشگیری به میان بیاید احتمالا معرفی ابزارهای تجاری مانند Reporting services ، کریستال ریپورت، stimulsoft.com ، fast-report.com و امثال آن درصدر لیست توصیه کنندگان و مشاوران قرار خواهند داشت. اما خوب برای ایجاد یک گزارشگیری ساده حتما نیازی نیست تا به این نوع ابزارهای تجاری مراجعه کرد. ابزار رایگان و سورس باز جالبی هم در این باره جهت پروژه‌های WPF در دسترس است:



در ادامه در طی یک مثال قصد داریم از این کتابخانه استفاده کنیم:

1) تنظیم وابستگی‌ها
پس از دریافت کتابخانه فوق، ارجاعات زیر باید به پروژه شما اضافه شوند:
CodeReason.Reports.dll (از پروژه فوق) و ReachFramework.dll (جزو اسمبلی‌های استاندارد دات نت است)

2) تهیه منبع داده‌ گزارش
کتابخانه‌ی فوق به صورت پیش فرض با DataTable‌ کار می‌کند. بنابراین کوئری‌های شما یا باید خروجی DataTable داشته باشد یا باید از یک سری extension methods برای تبدیل IEnumerable به DataTable استفاده کرد (در پروژه پیوست شده در پایان مطلب، این موارد موجود است).
برای مثال فرض کنید می‌خواهیم رکوردهایی را از نوع کلاس Product زیر در گزارش نمایش دهیم:

namespace WpfRptTests.Model
{
public class Product
{
public string Name { set; get; }
public int Price { set; get; }
}
}
3) تعریف گزارش
الف) اضافه کردن فایل تشکیل دهنده ساختار و ظاهر گزارش
گزارش‌‌های این کتابخانه مبتنی است بر اشیاء FlowDocument استاندارد WPF . بنابراین از منوی پروژه گزینه‌ی Add new item در قسمت WPF آن یک FlowDocument جدید را به پروژه اضافه کنید ( باید دقت داشت که Build action این فایل باید به Content تنظیم گردد). ساختار ابتدایی این FlowDocument به صورت زیر خواهد بود که به آن FlowDirection و FontFamily مناسب جهت گزارشات فارسی اضافه شده است. همچنین فضای نام مربوط به کتابخانه‌ی گزارشگیری CodeReason.Reports نیز باید اضافه گردد.
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FlowDirection="RightToLeft" FontFamily="Tahoma"
xmlns:xrd="clr-namespace:CodeReason.Reports.Document;assembly=CodeReason.Reports"
PageHeight="29.7cm" PageWidth="21cm" ColumnWidth="21cm">

</FlowDocument>

مواردی که در ادامه ذکر خواهند شد محتوای این گزارش را تشکیل می‌دهند:
ب) مشخص سازی خواص گزارش

<xrd:ReportProperties>
<xrd:ReportProperties.ReportName>SimpleReport</xrd:ReportProperties.ReportName>
<xrd:ReportProperties.ReportTitle>گزارش از محصولات</xrd:ReportProperties.ReportTitle>
</xrd:ReportProperties>
در اینجا ReportName و ReportTitle باید مقدار دهی شوند (دو dependency property که در کتابخانه‌ی CodeReason.Reports تعریف شده‌اند)

ج) مشخص سازی Page Header و Page Footer
اگر می‌خواهید عباراتی در بالا و پایین تمام صفحات گزارش تکرار شوند می‌توان از SectionReportHeader و SectionReportFooter این کتابخانه به صورت زیر استفاده کرد:
    <xrd:SectionReportHeader PageHeaderHeight="2" Padding="10,10,10,0" FontSize="12">
<Table CellSpacing="0">
<Table.Columns>
<TableColumn Width="*" />
<TableColumn Width="*" />
</Table.Columns>
<TableRowGroup>
<TableRow>
<TableCell>
<Paragraph>
<xrd:InlineContextValue PropertyName="ReportTitle" />
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Right">
<xrd:InlineDocumentValue PropertyName="PrintDate" Format="dd.MM.yyyy HH:mm:ss" />
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>
</Table>
</xrd:SectionReportHeader>

<xrd:SectionReportFooter PageFooterHeight="2" Padding="10,0,10,10" FontSize="12">
<Table CellSpacing="0">
<Table.Columns>
<TableColumn Width="*" />
<TableColumn Width="*" />
</Table.Columns>
<TableRowGroup>
<TableRow>
<TableCell>
<Paragraph>
نام کاربر:
<xrd:InlineDocumentValue PropertyName="RptBy" Format="dd.MM.yyyy HH:mm:ss" />
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Right">
صفحه
<xrd:InlineContextValue PropertyName="PageNumber" FontWeight="Bold" /> از
<xrd:InlineContextValue PropertyName="PageCount" FontWeight="Bold" />
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>
</Table>
</xrd:SectionReportFooter>

دو نکته در اینجا حائز اهمیت هستند: xrd:InlineDocumentValue و xrd:InlineContextValue
InlineDocumentValue را می‌توان در کد‌های برنامه به صورت سفارشی اضافه کرد. بنابراین هر جایی که نیاز بود مقدار ثابتی از طریق کد نویسی به گزارش تزریق و اضافه شود می‌توان از InlineDocumentValue استفاده کرد. برای مثال در کدهای ViewModel برنامه که در ادامه ذکر خواهد شد دو مقدار PrintDate و RptBy به صورت زیر تعریف و مقدار دهی شده‌اند:
data.ReportDocumentValues.Add("PrintDate", DateTime.Now);
data.ReportDocumentValues.Add("RptBy", "وحید");
برای مشاهده مقادیر مجاز مربوط به InlineContextValue به فایل ReportContextValueType.cs سورس کتابخانه مراجعه کنید که شامل PageNumber, PageCount, ReportName, ReportTitle است و توسط CodeReason.Reports به صورت پویا تنظیم خواهد شد.

د) مشخص سازی ساختار تولیدی گزارش

<Section Padding="80,10,40,10" FontSize="12">
<Paragraph FontSize="24" TextAlignment="Center" FontWeight="Bold">
<xrd:InlineContextValue PropertyName="ReportTitle" />
</Paragraph>
<Paragraph TextAlignment="Center">
گزارش از لیست محصولات در تاریخ:
<xrd:InlineDocumentValue PropertyName="PrintDate" Format="dd.MM.yyyy HH:mm:ss" />
توسط:
<xrd:InlineDocumentValue PropertyName="RptBy" Format="dd.MM.yyyy HH:mm:ss" />
</Paragraph>
<xrd:SectionDataGroup DataGroupName="ItemList">
<Table CellSpacing="0" BorderBrush="Black" BorderThickness="0.02cm">
<Table.Resources>
<!-- Style for header/footer rows. -->
<Style x:Key="headerFooterRowStyle" TargetType="{x:Type TableRowGroup}">
<Setter Property="FontWeight" Value="DemiBold"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Background" Value="LightGray"/>
</Style>

<!-- Style for data rows. -->
<Style x:Key="dataRowStyle" TargetType="{x:Type TableRowGroup}">
<Setter Property="FontSize" Value="12"/>
</Style>

<!-- Style for data cells. -->
<Style TargetType="{x:Type TableCell}">
<Setter Property="Padding" Value="0.1cm"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="0.01cm"/>
</Style>
</Table.Resources>

<Table.Columns>
<TableColumn Width="0.8*" />
<TableColumn Width="0.2*" />
</Table.Columns>
<TableRowGroup Style="{StaticResource headerFooterRowStyle}">
<TableRow>
<TableCell>
<Paragraph TextAlignment="Center">
<Bold>نام محصول</Bold>
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Center">
<Bold>قیمت</Bold>
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>

<TableRowGroup Style="{StaticResource dataRowStyle}">
<xrd:TableRowForDataTable TableName="Product">
<TableCell>
<Paragraph>
<xrd:InlineTableCellValue PropertyName="Name" />
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Center">
<xrd:InlineTableCellValue PropertyName="Price" AggregateGroup="Group1" />
</Paragraph>
</TableCell>
</xrd:TableRowForDataTable>
</TableRowGroup>

<TableRowGroup Style="{StaticResource headerFooterRowStyle}">
<TableRow>
<TableCell>
<Paragraph TextAlignment="Right">
<Bold>جمع کل</Bold>
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Center">
<Bold>
<xrd:InlineAggregateValue AggregateGroup="Group1"
AggregateValueType="Sum"
EmptyValue="0"
FontWeight="Bold" />
</Bold>
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>

</Table>

<Paragraph TextAlignment="Center" Margin="5">
در این گزارش
<xrd:InlineAggregateValue AggregateGroup="Group1"
AggregateValueType="Count"
EmptyValue="هیچ"
FontWeight="Bold" /> محصول با جمع کل قیمت
<xrd:InlineAggregateValue AggregateGroup="Group1"
AggregateValueType="Sum"
EmptyValue="0"
FontWeight="Bold" /> وجود دارند.
</Paragraph>
</xrd:SectionDataGroup>
</Section>
برای اینکه بتوان این قسمت‌ها را بهتر توضیح داد، نیاز است تا تصاویر مربوط به خروجی این گزارش نیز ارائه شوند:




در ابتدا توسط دو پاراگراف، عنوان گزارش و یک سطر زیر آن نمایش داده شده‌اند. بدیهی است هر نوع شیء و فرمت مجاز در FlowDocument را می‌توان در این قسمت نیز قرار داد.
سپس یک SectionDataGroup جهت نمایش لیست آیتم‌ها اضافه شده و داخل آن یک جدول که بیانگر ساختار جدول نمایش رکوردهای گزارش می‌باشد، ایجاد گردیده است.
سه TableRowGroup در این جدول تعریف شده‌اند.
TableRowGroup های اولی و آخری دو سطر اول و آخر جدول گزارش را مشخص می‌کنند (سطر عناوین ستون‌ها در ابتدا و سطر جمع کل در پایان گزارش)
از TableRowGroup میانی برای نمایش رکوردهای مرتبط با نام جدول مورد گزارشگیری استفاده شده است. توسط TableRowForDataTable آن نام این جدول باید مشخص شود که در اینجا همان نام کلاس مدل برنامه است. به کمک InlineTableCellValue، خاصیت‌هایی از این کلاس را که نیاز است در گزارش حضور داشته باشند، ذکر خواهیم کرد. نکته‌ی مهم آن AggregateGroup ذکر شده است. توسط آن می‌توان اعمال جمع، محاسبه تعداد، حداقل و حداکثر و امثال آن‌را که در فایل InlineAggregateValue.cs سورس کتابخانه ذکر شده‌اند، به فیلدهای مورد نظر اعمال کرد. برای مثال می‌خواهیم جمع کل قیمت را در پایان گزارش نمایش دهیم به همین جهت نیاز بود تا یک AggregateGroup را برای این منظور تعریف کنیم.
از این AggregateGroup در سومین TableRowGroup تعریف شده به کمک xrd:InlineAggregateValue جهت نمایش جمع نهایی استفاده شده است.
همچنین اگر نیاز بود در پایان گزارش اطلاعات بیشتری نیز نمایش داده شود به سادگی می‌توان با تعریف یک پاراگراف جدید، اطلاعات مورد نظر را نمایش داد.

4) نمایش گزارش تهیه شده
نمایش این گزارش بسیار ساده است. View برنامه به صورت زیر خواهد بود:
<Window x:Class="WpfRptTests.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:CodeReason.Reports.Controls;assembly=CodeReason.Reports"
xmlns:vm="clr-namespace:WpfRptTests.ViewModel"
Title="MainWindow" WindowState="Maximized" Height="350" Width="525">
<Window.Resources>
<vm:ProductViewModel x:Key="vmProductViewModel" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource vmProductViewModel}}">
<c:BusyDecorator IsBusyIndicatorHidden="{Binding RptGuiModel.IsBusyIndicatorHidden}">
<DocumentViewer Document="{Binding RptGuiModel.Document}" />
</c:BusyDecorator>
</Grid>
</Window>

تعریف ابتدایی RptGuiModel به صورت زیر است (جهت مشخص سازی مقادیر IsBusyIndicatorHidden و Document در حین بایندینگ اطلاعات):

using System.ComponentModel;
using System.Windows.Documents;

namespace WpfRptTests.Model
{
public class RptGuiModel
{
public IDocumentPaginatorSource Document { get; set; }
public bool IsBusyIndicatorHidden { get; set; }
}
}
و این View اطلاعات خود را از ViewModel زیر دریافت خواهد نمود:
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using CodeReason.Reports;
using WpfRptTests.Helper;
using WpfRptTests.Model;

namespace WpfRptTests.ViewModel
{
public class ProductViewModel
{
#region Constructors (1)

public ProductViewModel()
{
RptGuiModel = new RptGuiModel();
if (Stat.IsInDesignMode) return;
//انجام عملیات نمایش گزارش در یک ترد دیگر جهت قفل نشدن ترد اصلی برنامه
showReportAsync();
}

#endregion Constructors

#region Properties (1)

public RptGuiModel RptGuiModel { set; get; }

#endregion Properties

#region Methods (3)

// Private Methods (3)

private static List<Product> getProducts()
{
var products = new List<Product>();
for (var i = 0; i < 100; i++)
products.Add(new Product { Name = string.Format("Product{0}", i), Price = i });

return products;
}

private void showReport()
{
try
{
//Show BusyIndicator
RptGuiModel.IsBusyIndicatorHidden = false;

var reportDocument =
new ReportDocument
{
XamlData = File.ReadAllText(@"Report\SimpleReport.xaml"),
XamlImagePath = Path.Combine(Environment.CurrentDirectory, @"Report\")
};

var data = new ReportData();

// تعریف متغیرهای دلخواه و مقدار دهی آن‌ها
data.ReportDocumentValues.Add("PrintDate", DateTime.Now);
data.ReportDocumentValues.Add("RptBy", "وحید");

// استفاده از یک سری اطلاعات آزمایشی به عنوان منبع داده
data.DataTables.Add(getProducts().ToDataTable());

var xps = reportDocument.CreateXpsDocument(data);
//انقیاد آن به صورت غیر همزمان در ترد اصلی برنامه
DispatcherHelper.DispatchAction(
() => RptGuiModel.Document = xps.GetFixedDocumentSequence()
);
}
catch (Exception ex)
{
//وجود این مورد ضروری است زیرا بروز استثناء در یک ترد به معنای خاتمه آنی برنامه است
//todo: log errors
}
finally
{
//Hide BusyIndicator
RptGuiModel.IsBusyIndicatorHidden = true;
}
}

private void showReportAsync()
{
var thread = new Thread(showReport);
thread.SetApartmentState(ApartmentState.STA); //for DocumentViewer
thread.Start();
}

#endregion Methods
}
}

توضیحات:
برای اینکه حین نمایش گزارش، ترد اصلی برنامه قفل نشود، از ترد استفاده شد و استفاده ترد به همراه DocumentViewer کمی نکته دار است:
- ترد تعریف شده باید از نوع STA باشد که در متد showReportAsync مشخص شده است.
- حین بایندیگ Document تولید شده توسط کتابخانه‌ی گزارشگیری به خاصیت Document کنترل، حتما باید کل عملیات در ترد اصلی برنامه صورت گیرد که سورس کلاس DispatcherHelper را در فایل پیوست خواهید یافت.

کل عملیات این ViewModel در متد showReport رخ می‌دهد، ابتدا فایل گزارش بارگذاری می‌شود، سپس متغیرهای سفارشی مورد نظر تعریف و مقدار دهی خواهند شد. در ادامه یک سری داده آزمایشی تولید و به DataTables گزارش ساز اضافه می‌شوند. در پایان XPS Document متناظر آن تولید شده و به کنترل نمایشی برنامه بایند خواهد شد.

دریافت سورس این مثال