Quantcast
Channel: ‫فید مطالب .NET Tips
Viewing all 1980 articles
Browse latest View live

‫اجزاء معماری سیستم عامل اندروید (قسمت اول رمزنگاری اندروید) :: بخش پنجم

$
0
0
پیاده سازی رمزنگاری کجا و به چه صورتی انجام می‌شود؟

ما داده‌ها را قبل از اینکه آن‌ها را به کارت SD ارسال کنیم، نگهداری و رمز می‌کنیم. به این ترتیب داده‌های ما در کارت SD در فرمتی که می‌تواند توسط هر کسی خوانده شود نوشته شده و هرگز اجازه دسترسی به آنها مقدور نمی‌باشد. یک مهاجم که اطلاعات رمزنگاری شده شما را جمع‌آوری می‌کند باید ابتدا از رمز عبور برای رمزگشایی داده‌ها قبل از دسترسی به آن‌ها استفاده کند که در این مرحله دچار سردرگمی خواهد شد؛ چرا که فرمت هر نوع از داده‌ها یکسان نخواهند بود! ما از الگوریتم AESبرای رمزکردن داده‌ها با استفاده از یک گذرواژه یا کلید استفاده خواهیم کرد. در این صورت یک کلید برای رمزگذاری و رمزگشایی داده‌ها مورد نیاز است. این رمزگذاری کلید متقارن (Symmetric-key) نیز نامیده می‌شود. 
برخلاف رمزنگاری کلید عمومی، این کلید تنها کلید استفاده‌شده برای رمزگذاری و رمزگشایی داده‌ها است. این کلید باید به طور ایمن ذخیره شود؛ چون اگر از دست رفته یا فاش شده باشد، یک مهاجم می‌تواند از آن برای رمزگشایی استفاده کند. کدهای زیر روشی از رمزگذاری را نشان می‌دهد:
privatestaticbyte[] encrypt(byte[] key, byte[] data) {
 SecretKeySpec sKeySpec = new SecretKeySpec(key, "AES");
 Cipher cipher;
 byte[] ciphertext = null;
 try {
  cipher = Cipher.getInstance("AES");
  cipher.init(Cipher.ENCRYPT_MODE, sKeySpec);
  ciphertext = cipher.doFinal(data);
 } catch (NoSuchAlgorithmException e) {
  Log.e(TAG, "NoSuchAlgorithmException");
 } catch (NoSuchPaddingException e) {
  Log.e(TAG, "NoSuchPaddingException");
 } catch (IllegalBlockSizeException e) {
  Log.e(TAG, "IllegalBlockSizeException");
 } catch (BadPaddingException e) {
  Log.e(TAG, "BadPaddingException");
 } catch (InvalidKeyException e) {
  Log.e(TAG, "InvalidKeyException");
 }
 return ciphertext;
}
اجازه دهید این قسمت را بخش به بخش توضیح دهم. اولین سطر کد از کلاس SecretKeySpec استفاده کرده و یک نمونه جدید از کلاس Cipher را برای تهیه یک کلید مخفی AES به وجود می‌آورد. 
SecretKeySpec sKeySpec = new SecretKeySpec(key,"AES");
Cipher cipher;
byte[] ciphertext = null;
همچنین کد بالا یک آرایه از بایت‌ها را برای ذخیره متن رمزی در ciphertext ایجاد می‌کند. بخش بعدی این کد برای استفاده از الگوریتم AES استفاده شده است که مشاهده می‌کنید.
cipher = Cipher.getI nstance ( " AES " ) ;
cipher.init ( Cipher.ENCR ypt _ MODE , sKeySpec ) ;
 تابع cipher.init شئ ای از تابع Cipher است؛ بنابراین می‌تواند با استفاده از کلید رمز تولید شده، رمزنگاری را انجام دهد. خط بعدی کد، داده‌های متنی را تغییر می‌دهد و محتویات رمز شده را در آرایه‌ای از بایت کدها ذخیره می‌کند که در کد زیر مشاهده می‌شود:
ciphertext = cipher.doFinal(data);
مهم است که ما از همان کلید برای جریان رمزگشایی نیز استفاده کنیم. در غیر این صورت، شکست خواهیم خورد و کلید، عمومی می‌شود. به طور کلی بهتر است که والد کلید اصلی را بنویسید و یک عدد تصادفی را تولید کند. این کار باعث می‌شود که فرد مهاجم به چیزی بیش از یک گذرواژه عادی فکر کند! برای نمونه کدهای زیر، کلید تولید شده با استفاده از الگوریتم را نشان می‌دهند:
publicstaticbyte[] generateKey(byte[] randomNumberSeed) {
 SecretKey sKey = null;
 try {
  KeyGenerator keyGen = KeyGenerator.getInstance("AES");
  SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
  random.setSeed(randomNumberSeed);
  keyGen.init(256, random);
  sKey = keyGen.generateKey();
 } catch (NoSuchAlgorithmException e) {
  Log.e(TAG, "No such algorithm exception");
 }
 return sKey.getEncoded();
}
در کد بالا، اعداد تصادفی با استفاده از SHA1 کدگذاری می‌شوند. الگوریتم SHA1 یا دیگر الگوریتم‌های هش، توابع رمزنگاری را مدیریت می‌کنند. این الگوریتم بر روی یک قطعه از داده‌ها عمل می‌کند که دارای یک طول غیریکسان هستند و یک رشته کوتاه با اندازه ثابت را تولید می‌کند. اگر هر قطعه‌ای از داده‌ها تغییر کند، آنگاه نتایج نهایی در آن مجموعه دچار تغییر خواهند شد و رمزنگاری، نتایج دیگری خواهد داشت! این نشانه‌ای است از اینکه یک قطعه از داده‌ها دستکاری شده‌است!

Vue Lifecycle hooks

$
0
0
هر وهله از Vue از یک‌سری مراحل یا (initialization steps) عبور خواهد کرد به عنوان مثال مراحلی از قبیل کامپایل شدن تمپلیت، mount شدن وهله به DOM و یا بروزرسانی DOM زمانیکه داده‌ها تغییر پیدا می‌کنند و ... در حین طی کردن این مسیر یکسری توابع ویژه با نام lifecycle hooks فراخوانی خواهند شد. بنابراین درون این توابع می‌توانیم در هر مرحله کدهای موردنیازمان را قرار دهیم:

همانطور که مشاهده می‌کنید این چرخه حیات با وهله‌سازی شیء Vue شروع می‌شود. در این مرحله اولین تابع یعنی beforeCreate فراخوانی می‌شود. در این مرحله کار مقداردهی اولیه (initialization) ایونت‌های پاس داده شده به Vue instance انجام خواهد گرفت. در مرحله بعد تابع created فراخوانی و در ادامه تمپلیت (اگر تعیین شده باشد) کامپایل و بعد از آن تابع beforeMount فراخوانی خواهد شد. این تابع دقیقاً قبل از اینکه تمپلیت به DOM اضافه شود فراخوانی می‌شود. در این‌حالت المنت تعیین شده در قسمت el با محتوای تمپلیت مقداردهی می‌شود. البته تا اینجا هنوز خروجی به DOM اضافه نشده است؛ تنها اعمال بایندینگ، string interpolation بر روی تمپلیت صورت خواهند گرفت تا خروجی به صورت یک HTML آماده تحویل به مرحله بعد گردد. در ادامه خروجی تهیه شده به DOM اضافه (mount) خواهد شد. در این مرحله اگر دیتایی تغییر کند، تابع beforeUpdate فراخوانی خواهد شد. بعد از اینکه تغییری توسط Vue مشاهده شد، تابع updated فراخوانی خواهد شد. در نهایت توابع beforeDestroyed و destroyed را داریم. درون این توابع فرصت آزادسازی منابع استفاده شده را خواهیم داشت. 
به عنوان مثال بعد از اینکه یک وهله از Vue ایجاد شد، تابعی با نام created فراخوانی خواهد شد: 
new Vue({
    el: '#app',
    data() {
        return {
            a: 1
        };
    },
    created: function () {
        // `this` points to the vm instance
        console.log('a is: ' + this.a)
    }
});

در حالت کلی می‌توانیم hookها را به چهار دسته‌بندی زیر تقسیم کنیم:
(Creation (Initialization
این نوع hook در واقع اولین توابعی هستند که درون یک کامپوننت فراخوانی خواهند شد. در اینجا می‌توانیم قبل از اینکه کامپوننت به DOM اضافه شود، اکشن مورد نیازمان را قرار دهیم. باید دقت داشته باشید که درون این توابع، به target element (همان المنت‌ی که وهله‌ی Vue به آن متصل خواهد شد) و همچنین DOM دسترسی ندارید و مقادیر آن‌ها در این فاز، undefined خواهند بود: 
new Vue({
    el: '#app',
    data() {
        return {
            name: 'Sirwan'
        };
    },
    beforeCreate: function () {
        console.log('name is: ' + this.name);
        console.log(`We don't have access to target element at this point: ${this.$el}`);
    },
    created: function () {
        // `this` points to the vm instance
        console.log('name is: ' + this.name);
        console.log(`We don't have access to target element at this point: ${this.$el}`);
    }
});

(Mounting (DOM Insertion
در این مرحله می‌توانیم بلافاصله قبل و بعد از اولین رندر، به کامپوننت دسترسی داشته باشیم. می‌توانیم از این hookها برای تغییر کامپوننت، به محض رندر شدن استفاده کنیم. بنابراین در این فاز به target element نیز دسترسی خواهیم داشت: 
beforeMount: function () {
    console.log(`this.$el doesn't exist yet, but it will soon!`);
},
mounted: function () {
    console.log(this.$el);
}

(Updating (Diff & Re-render
این توابع هنگامیکه داده‌های تعریف شده در قسمت data تغییر پیدا کنند، فراخوانی خواهند شد. یعنی دقیقاً قبل از اینکه DOM به‌روزرسانی شود، فراخوانی خواهند شد. در مثال زیر به محض ایجاد شدن وهله‌ی Vue، یک تایمر ایجاد شده است. این تایمر هر ثانیه یکبار مقدار دیتای counter را یک واحد افزایش خواهد داد. در حالت عادی اگر دیتایی درون DOM استفاده نشده باشد، درون کنسول خروجی را دریافت نخواهید کرد. در نتیجه این توابع تنها در صورتیکه از دیتا درون DOM استفاده شده باشد، فراخوانی خواهند شد: 
new Vue({
    el: '#app',
    data() {
        return {
            name: 'Sirwan',
            counter: 0
        };
    },
    created: function () {
        setInterval(() => {
            this.counter++
        }, 1000)
    },
    beforeUpdate: function () {
        console.log(this.counter); // Logs the counter value every second, before the DOM updates.
    },
    updated: function () {
        console.log(`new value: ${this.counter}`);
    }
});

(Destruction (Teardown
این توابع، قبل و بعد از تخریب شدن (حذف شدن از DOM) کامپوننت فراخوانی خواهند شد:
new Vue({
    el: '#app',
    data() {
        return {
            name: 'Sirwan',
            counter: 0
        };
    },
    beforeDestroy: function () {
    },
    destroyed() {
        console.log(this) // There's practically nothing here!
    }
});
 
 
Vue.js چگونه تغییرات داده‌ها را متوجه خواهد شد؟
شاید تصور کنید که Vue.js به صورت مداوم تغییرات را مشاهده کرده و در صورت وجود تغییری بر روی دیتا، این تغییرات را مستقیماً به DOM اعمال خواهد کرد. هر پراپرتی دارای یک watcher است؛ یعنی وقتی یک شیء را به سازنده Vue ارسال می‌کنیم، یک watcher برای تمامی پراپرتی‌های تعریف شده، درون پراپرتی data ایجاد خواهد کرد. این watcher در واقع مسئول کنترل تغییرات پراپرتی‌ها می‌باشد و اگر تغییری صورت بگیرد، آن را به DOM اعمال خواهد کرد. اما نکته اینجاست که این تغییرات بلافاصه بر روی DOM اعمال نخواهند شد؛ زیرا از لحاظ کارآیی، اینکار بهینه نیست. فرض کنید یک پراپرتی با نام message درون قسمت data داریم که مقدار آن Hello است. اکنون همین مقدار توسط UI مجدداً به Hello ست خواهد شد. در این‌حالت اعمال تغییرات نباید به DOM منعکس شود، زیرا مقدار message به همان مقدار قبلی تنظیم شده است. در عوض Vue.js از مفهومی با نام Virtual DOM استفاده می‌کند که در واقع یک کپی از DOM اصلی است؛ با این تفاوت که دسترسی به آن خیلی سریعتر از DOM اصلی می‌باشد. بنابراین Vue.js تغییرات صورت گرفته را مستقیماً به Virtual DOM اعمال می‌کند. در نهایت آن را با DOM اصلی مقایسه خواهد کرد و در صورت وجود تغییر، آن را با DOM اصلی ادغام خواهد کرد. 

‫پشتیبانی از IE در برنامه‌های Angular

$
0
0
اگر یک برنامه‌ی Angular را به صورت پیش‌فرض در IE اجرا کنیم، یک چنین تصویری مشاهده خواهد شد:


برای اجرای برنامه توسط نگارش‌های مختلف IE می‌توانید برنامه‌ی IE Testerرا نصب کنید.


مشکل چیست؟

مشکل عدم اجرای برنامه‌های Angular در IE، به قدیمی بودن موتور JavaScript آن بر می‌گردد؛ خصوصا در مورد توابع کار با آرایه‌ها. برای مثال در مورد کار با for..ofهیچ نوع پشتیبانی از آن در IE وجود ندارد (و نخواهد داشت؛ با توجه به پایان دوره‌ی پشتیبانی آن):



چگونه پشتیبانی از ویژگی‌های جدید JavaScript را به مرورگر IE اضافه کنیم؟

Angular امکان افزودن کمبودهای موتور JavaScript پیش‌فرض IE را توسط کتابخانه‌ی core-jsمیسر کرده‌است که اصطلاحا به آن polyfills گفته می‌شود. برای این منظور فایل src\polyfills.ts را گشوده و تغییرات زیر را به آن اعمال کنید:
الف) در این فایل، هرجایی  import // وجود دارد، آن‌را تبدیل به import کنید (تمام importهایی که کامنت شده‌اند را از حالت کامنت خارج کنید).
ب) دو بسته‌ی زیر را نیز باید نصب کنید:
npm install --save classlist.js
npm install --save web-animations-js
البته اگر به کامنت‌های این فایل دقت کنید، نیاز به نصب این بسته‌ها نیز در آن عنوان شده‌است.


نتیجه‌ی نهایی پس از افزودن polyfills مخصوص IE

اکنون اگر مجددا برنامه را کامپایل و اجرا کنید، برنامه‌ی Angular بدون مشکل در IE اجرا خواهد شد:

‫راه اندازی Docker swarm

$
0
0
مقاله‌های زیادی درباره‌ی مزایای استفاده‌ی از داکر در اینترنت وجود دارند. در این مقاله قصد دارم طریقه‌ی راه اندازی یک سرور Production را برای داکر، توضیح دهم.
یکی از مزایای مهم داکر، امکان Scale در سریعترین زمان ممکن هست. یعنی اگر در محیط Production میزان بار بر روی یکی از اجزای محصول شما بیشتر بود (در صورتیکه معماری صحیحی برای سرویس‌های مجزا رعایت شده باشد)، می‌توانید آن قسمت را Scale کنید. می‌دانید که وجود بیشتر از یک Instance از یک سرویس، نگرانی‌های معمولی مثل Load Balancing  و غیره را به همراه دارد و اینکه کانتینر داکر شما قرار هست بر روی چه سروری نصب شود و بر روی چه سرور یا سرور‌هایی Scale شود.
ابزار‌های خوبی برای مدیریت کردن سرورهایی که Container‌های داکر بر روی آنها اجرا می‌شوند وجود دارند که یکی از مهمترین آنها kubernetes است که یکی از بهترین ابزارها، برای Management، Scaling و Deploy اتوماتیک نرم افزارهای Containerized می‌باشد.
در این مقاله از ابزاری به نام Swarm استفاده می‌کنم که راه اندازی آن راحت‌تر هست و همینطور وقتی احتیاج به Scaling وجود داشت، فقط یک PublishedPort به سرویس اختصاص داده می‌شود و  Load Balancing کاملا اتوماتیک انجام خواهد شد.
برای نصب Swarm، من دو سرور لینوکسی را آماده می‌کنم. یکی از سرور‌ها نقش Master را ایفا می‌کند و دیگری نقش Worker را (به راحتی می‌شود تعداد Worker‌ها را افزایش داد). 
از مزایای Swarm هم می‌شود به این نکته اشاره کرد که وقتی به سخت افزار بیشتری احتیاج باشد، کافی است یک ماشین دیگر را راه اندازی و آن را به عنوان یک Worker به Master معرفی کنیم و لازم نیست نگرانی درباره‌ی اینکه چه کانتینری بر روی چه سروری اجرا می‌شود داشته باشیم.
مشخصات سرور‌ها:
DocerMaster :
OS : CentOS7
IP: 192.168.64.3

DockerWorker:
OS: CentOS7
IP: 192.168.64.4
مطمئن شوید که هر دو در یک شبکه هستند و همدیگر را پینگ می‌کنند.
سپس بر روی هر دو سرور، داکر را نصب می‌کنم:
sudo yum install docker
و در ادامه سرویس داکر را  بر روی هر دو سیستم استارت می‌کنیم: 
$ sudo service docker start
$ sudo systemctl start docker.service
 مطمئن شوید که داکر در هر دو سرور نصب و اجرا شده‌است. با دستور service status docker می‌توانید نتیجه را ببینید.
بعد از نصب داکر، به صورت پیشفرض Swarm هم نصب می‌شود و لازم به نصب ابزار دیگری نیست.
برای ارتباط بین Master و Worker‌ها باید بعضی از پورت‌ها در این سرور‌ها باز شود. برای این کار در کامپیوتر Master دستورات زیر را اجرا کنید تا پورت‌ها به فایروال اضافه شوند: 
firewall-cmd --permanent --add-port=2376/tcp
firewall-cmd --permanent --add-port=2377/tcp
firewall-cmd --permanent --add-port=7946/tcp
firewall-cmd --permanent --add-port=7946/udp
firewall-cmd --permanent --add-port=4789/udp
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --reload
و سپس دستور زیر را اجرا کنید: 
systemctl restart docker
به کامپیوتر Worker رفته و با دستورات زیر پورت‌های لازم را به فایروال اضافه کنید: 
~]# firewall-cmd --permanent --add-port=2376/tcp
~]# firewall-cmd --permanent  --add-port=7946/tcp
~]# firewall-cmd --permanent --add-port=7946/udp
~]#  firewall-cmd --permanent --add-port=4789/udp
~]# firewall-cmd --permanent --add-port=80/tcp
~]#  firewall-cmd --reload
~]#  systemctl restart docker
در سرور Master باید Swarm را راه اندازی کنیم. برای این کار از دستور زیر استفاده می‌کنیم: 
sudo docker swarm init –advertise-addr 192.168.64.3
 بعد از اجرای موفقیت آمیز دستور فوق، عبارت زیر نمایش داده می‌شود: 

همانطور که مشاهده می‌کنید، پس از راه اندازی، اعلانی مبنی بر اینکه این نود به عنوان Manager شناخته شده و اینکه برای اضافه کردن یک نود Worker چه دستوری را باید اجرا کرد، نمایش داده شده‌است.

اکنون کافی‌است این خط کد را در نود Worker کپی کنیم: 

بعد از موفقیت آمیز بودن اجرای آن، می‌توانید در کامپیوتر Master، با دستور زیر تمام نود‌ها را مشاهده کنید: 

$ sudo docker node ls

همانطور که مشاهده می‌کنید، دو نود وجود دارد که یکی به عنوان Leader شناخته می‌شود. هر زمانی که نیاز بود، می‌شود به راحتی یک Worker دیگر را اضافه کرد.

برای راه اندازی یک کانتینر، swarm از CLI کاملی برخوردار هست؛ اما مایلم اینجا از یک ابزار خوب، برای مدیریت Swarm استفاده کنم. Portainer به عنوان یه ابزار عالی برای مدیریت Image‌ها و Container‌های داکر محسوب می‌شود که کاملا swarm را پشتیبانی می‌کند.

برای راه اندازی portainer کافی است کد زیر را در سیستم Master اجرا کنید: 

$ docker volume create portainer_data
$ docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer

البته به دلیل عدم دسترسی به داکر هاب از کشور ایران، عملا امکان pull کردن این image، مستقیما از داکر هاب و بدون وی پی ان وجود ندارد. 

بعد از موفقیت آمیز بودن راه اندازی portainer می‌توانید از طریق آدرس http://192.168.64.3:9000 به آن دسترسی داشته باشید. در اولین ورود، پسورد ادمین را تنظیم می‌کنید و بعد از وارد شدن، صفحه‌ای مطابق شکل زیر را خواهید دید:  

اگر بر روی منوی swarm کلیک کنید، همه‌ی نود‌ها را مشاهده خواهید کرد و در صورتیکه بر روی Containers کلیک کنید، همه‌ی Container هایی را که بر روی این سرور وجود دارند، خواهید دید. مهمترین قسمت، بخش Service هاست که مشخصات Container هایی که روی swarm توزیع شدن را نشان می‌دهد و همینطور تعداد Container هایی از این image که Scale شدند. همینطور که می‌بینید فعلا فقط همین Portainer در حال اجراست. 


اجازه دهید یک مثال کاربردی‌تر بزنیم و یک سرویس را ایجاد کنیم.

من بر روی کامپیوتر شخصی‌ام و نه سرورها، با دستور زیر یک پروژه‌ی MVC را با دات نت Core ایجاد می‌کنم:

dotnet new mvc

و سپس دستور dotnet publish را اجرا می‌کنم و به پوشه‌ای که محتویات پابلیش شده در آن قرار دارند رفته و یک فایل بدون پسوند را به نام dockerfile ایجاد می‌کنم و متن زیر را در آن می‌نویسم:

همینطور که می‌بینید من از image مخصوص اجرای دات نت Core در این container استفاده می‌کنم. پوشه‌ی کانتینر را تنظیم می‌کنم و همه‌ی فایل‌هایی که در پوشه‌ی جاری سیستم خودم وجود دارند را به پوشه‌ی جاری کانتینر منتقل می‌کنم و سپس دستور دات نت را با پارامتر اسم dll پروژه‌ام اجرا می‌کنم. این کل محتویات فایل داکر من هست.

ترمینال را در همین پوشه‌ی publish باز می‌کنم و دستور زیر را اجرا می‌کنم:

docker build –t swarmtest:dev .
با این دستور یک image از روی داکر فایلی که ایجاد کردیم درست می‌شود:

حالا باید این image را به سرور master منتقل کنیم. برای این کار راه‌های مختلفی وجود دارند که معمول‌ترین آن‌ها push کردن این image به یک registery و سپس pull کردن آن در کامپیوتر Master است که من از این راه استفاده نمی‌کنم. من این image را بر روی کامپیوترم ذخیره می‌کنم و سپس فایل ذخیره شده را به سرور master منتقل می‌کنم و آنجا آن فایل را load می‌کنم.
در ادامه بر روی کامپیوتر خودم دستور زیر را اجرا می‌کنم:
docker save swarmtest:dev –o swarmtest.tar

طبق شکل زیر یک فایل tar که حاوی image برنامه من هست، ایجاد شد:

حالا با دستور زیر این فایل رو به سرور Master منتقل می‌کنم:

scp –r swarmtest.tar root@192.168.64.3:/srv/images

همانطور که می‌بینید، فایل tar به پوشه‌ای که قبلا در سرور ایجاد کردم، منتقل شد.

حالا به سرور و پوشه‌ای که فایل tar آنجا قرار دارد رفته و با دستور زیر این image را بر روی سیستم load می‌کنم:

sudo docker load –i swarmtest.tar

همانطور که در تصویر می‌بینید، بعد از load شدن، image مورد نظرمان به داکر اضافه شده‌است.

حالا برای اجرا کردن این سرویس بر روی swarm، آدرس portainer را باز می‌کنیم و به قسمت  services می‌رویم و بر روی دکمه‌ی add service کلیک می‌کنیم:

در قسمت نام، نام سرویس و در قسمت imageConfiguration از منوی image‌ها، ایمیجی را که ایجاد کردیم، انتخاب می‌کنیم. در قسمت Replicas تعداد instance‌های container ای را که می‌خواهیم از روی image ایجاد شوند، مشخص می‌کنیم. (این قسمت را بر روی هر وضعیتی می‌توانیم قرار دهیم و زیاد و کم کنیم) و در قسمت port mapping، پورت درون Container و پورتی را که می‌خواهیم بر روی هاست به نمایش درآید، وارد می‌کنیم.

همانطور که می‌بینید من به راحتی می‌توانم تعداد Container‌ها را Scale کنم و نگرانی‌ای بابت load balancing و اینکه کدام container بر روی کدوم سرور ایجاد می‌شود، نخواهم داشت.

برای نمایش برنامه کافی است پورتی را که برای هاست وارد کردیم، با آی پی Master وارد کنیم:


‫کار با اسناد در RavenDb 4، ثبت و ویرایش

$
0
0
اگر تا بحال با بانک‌های NoSql کار کرده و لذت برده‌اید، به شما پیشنهاد میکنم حتما RavenDb را هم امتحان کنید، تا لذت استفاده از NoSql را چندین برابر حس کنید! RavenDb یک بانک اطلاعاتی NoSql از نوع DocumentStore است که به‌صورت متن باز توسعه داده می‌شود و مخزن کد آن در Github موجود است. از ویژگی‌های بارز RavenDb نسبت به سایر DocumentStoreها، Transactional بودن میباشد و در نسخه‌ی 4 بصورت کامل از Net Core. پشتیبانی میکند. برای آشنایی بیشتر با NoSql میتوانید از مقالات موجود در گروه NoSql استفاده کنید و برای آشنایی با RavenDb از دوره ای که در سایت وجود دارد استفاده نمایید(دوره مربوط به نسخه‌ی 3.5 می‌باشد).
از بارز‌ترین ویژگی‌های NoSqlها توانایی آن‌ها در ذخیره‌ی اطلاعات، بدون توجه به اسکیمای آن هاست؛ پس هر نوع مدلی که ما برای ذخیره اطلاعات نرم افزار تعریف میکنیم، فقط برای درک بهتر ما هست و بس!

با این مقدمه مدل‌های زیر را برای شروع کار داریم:
Public Class User
{
        public string Id { get; set; }
        public string PhoneNumber { get; set; }
        public Dictionary<string, App> Apps { get; set; }
}
public class App
{
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string UserName { get; set; }
        public List<string> Roles { get; set; }
        public List<String> Messages { get; set; }
        public String AdressId { get; set; }
        public bool IsActive { get; set; } = true;
        [JsonIgnore]
        public string DisplayName => $"{FirstName} {LastName}";
}

در این مدل، هر کاربر با یک شماره تماس میتواند در چندین برنامه ثبت شود و اطلاعات او در هر برنامه هم میتواند متفاوت باشد.
برای اتصال به RavenDb، به DocumentStore و برای ارسال درخواست‌ها به سمت سرور، به DocumentSession نیاز داریم. نمونه سازی DocumentStore هزینه‌بر بوده و باید در طی اجرای نرم افزار فقط یکبار(Singleton) نمونه سازی شود. DocumentSession بسیار سبک بوده و باید به ازای هر درخواست که به سمت سرور RavenDb ارسال میگردد یک بار نمونه سازی شده و بعد از آن نابود شود. پس برای استفاده در ASP.NET Core به این پیاده سازی در Startup میرسیم:
services.AddSingleton<IDocumentStore>(serviceProvider =>
{
      var store = new DocumentStore()
      {
            Urls = new[] { "http://192.168.1.10:8080" },
            Database = "AccountingSystem",
      }.Initialize();
      return store;
});

services.AddScoped<IAsyncDocumentSession>(serviceProvider =>
{
      var store = serviceProvider.GetRequiredService<IDocumentStore>();
      return store.OpenAsyncSession();
});

حال در تمام بخش‌های نرم افزار می‌توانیم DocumentSession استفاده کنیم.
برای ذخیره سازی مدل در RavenDb از کد زیر استفاده می‌کنیم:
var user = new User
{
      PhoneNumber = user.PhoneNumber
};
user.Apps.Add(appCode, new ActiveApp
{
       FirstName = "عبدالصالح",
       LastName = "کاشانی",
       UserName = abdossaleh,
       IsActive = true,
       RolesId = new List<string>{"Admin"}
});
await _documentSession.StoreAsync(user);
await _documentSession.SaveChangesAsync()

این ساده‌ترین کاری هست که میتوانیم انجام دهیم. بلافاصله بعد از استفاده از متد StoreAsync و بدون رفت و برگشتی به سرور، ویژگی Id برای user مقداردهی می‌شود و توضیح این رفتار هم پیشتر گفته شده است. با فراخوانی متد SaveChangesAsync تغییرات اتفاق افتاده در DocumentSession برای ذخیره سازی به سمت سرور ارسال می‌شوند. بله! الگوی Repository و UnitOfWork.
حال برای دریافت همین مدل، در صورتیکه Id آن را در اختیار داشته باشیم، از متد LoadAsync استفاده میکنیم.
var user = await _documentSession.LoadAsync<User>("Users/131-A");
با لود شدن کاربر، این Entity تحت نظر قرار میگیرد و اگر تغییری در هر کدام از ویژگی‌های آن صورت گیرد و متد SaveChangesAsync فراخوانی شود، کل مدل برای به‌روزرسانی به سمت سرور ارسال میشود. کل مدل و این به معنای بار اضافی در شبکه هست. البته در مدل‌های کوچک بهتر است که همین کار را انجام دهیم. ولی در اینجا به عمد مدلی را انتخاب کرده‌ایم که اطلاعات زیادی را در خود نگهداری میکند و ارسال تمام آن به ازای یک تغییر کوچک به صرفه نیست! خوشبختانه RavenDb برای حل این مشکل امکانات جالبی را در اختیار ما قرار داده که در ادامه آن‌ها را بررسی می‌کنیم.

Patching
به معنای تغییر دادن قسمتی از سند که شامل تغییر مقادیر، اضافه یا حذف یک ویژگی، ایجاد تغییرات در لیست و ... می‌باشد. با استفاده از متدهای Patch سند، میتوانیم بدون نیاز به لود سند و تغییر و ذخیره آن، قسمتی از سند را ویرایش کنیم. عملیات Patch، سمت سرور اجرا می‌شوند. برای مثال برای تغییر شماره تماس، از متد زیر استفاده می‌کنیم:
_documentSession.Advanced.Patch<User, string>("Users/131-A",
      u => u.PhoneNumber
      , "09131110000");
که مدلی را که میخواهیم تغییر دهیم، به همراه نوع ویژگی مورد نظر برای تغییر، دریافت میکند و بعد از آن، به ترتیب Id سند مورد نظر، ویژگی مورد نظر برای اعمال تغییر و مقدار را میگیرد و با فراخوانی SaveChangesAsync این تغییرات اعمال می‌شوند. نکته‌ای که باید توجه کنید این است که اگر مدلی را لود کردید و در فیلدهای آن تغییری ایجاد نموده‌اید، دیگر نمیتوانید از Patch یا Defer (توضیح داده میشود) استفاده کنید. به عبارت دیگر در هر درخواست یا باید از سیستم Tracking خود RavenDb استفاده کنید و یا از Patching!
برای اضافه کردن یک آیتم به لیست،  از Patch بصورت زیر استفاده میکنیم:
_documentSession.Advanced.Patch<User, string>("Users/131-A",
      u => u.Apps["59"].RolesId
      , r => r.Add("Admin"));

برای اضافه کردن مقداری به یک مقدار عددی در RavenDb، از متد Increment بصورت زیر استفاده میکنیم:
 _documentSession.Advanced.Increment<User, int>("Users/131-A", x => x.TestProp, 10);
متد Patch برای کارهای ساده‌ی اینچنین بسیار کاربردی می‌باشد؛ ولی برای کارهای پیشرفته‌تر کارآیی ندارد. به همین دلیل متد Defer در کنار آن معرفی شده‌است که فوق العاده کاربردی ولی اصطلاحا non-typed است و تحت نظارت Compiler نیست. برای مثال اضافه کردن یک مقدار به Dictionary ما، از طریق Patch امکان ندارد. اما اینکار با استفاده از متد Defer و کدهای JavaScript به‌سادگی زیر می‌باشد:
_documentSession.Advanced.Defer(new PatchCommandData("Users/131-A", null,
                              new PatchRequest()
                              {
                                    Script = $@"this.Apps[args.appCode] = args.app",
                                    Values =
                                         {
                                              {"appCode", appCode},
                                              {"app", new ActiveApp
                                                   {
                                                        FirstName = "عبدالصالح",
                                                        LastName = "کاشانی",
                                                        UserName = abdossaleh,
                                                        RolesId = new List<string>{"Admin"}
                                                    }
                                              }
                                          }
                              }, null));
متد Defer شناسه‌ی سند مورد نظر را گرفته و اسکریپت ما را با آرگومان‌های ارسالی، بر روی سند اعمال میکند. Defer دسترسی کاملی را به ما برای تغییر در سند میدهد. برای نمونه میتوانیم آیتمی را به مکان خاصی از لیست اضافه کنیم (برای کوتاه‌تر شدن اسکریپت‌ها فقط بخش Script و Value را ذکر میکنم):
Script = "this.Apps[args.app].Roles.splice(args.index,0,args.role)",
Values =
        {
            {
                "index": 1 // مکانی که میخواهیم عملیات انجام شود
                "app", 59
                "role", "User"
            }
        }
this در اینجا به سند جاری اشاره میکند.
از همین روش میتوانیم برای ویرایش کردن یک آیتم هم استفاده کنیم. برای مثال اگر مقدار 0 را در متد splice به یک تغییر دهیم، عملیات ویرایش صورت می‌گیرد (در واقع حذف آیتم در مکان index و درج آیتم جدید در همان مکان):
splice(args.index,1,args.role)
و برای حذف تمام آیتم‌های لیست جز یک آیتم خاص، از کد زیر استفاده میکنیم:
Script = @"this.Roles= this.Apps[args.app].Roles.filter(role=> role != args.role);",
        Values =
        {
            {"app", 59}
            {"role", "User"}
        }
همانطور که مشاهده می‌کنید به راحتی می‌توانیم کدهای جاوا اسکریپتی خود را در Defer استفاده کنیم. اما این قدرت زیاد، امکان اشتباه در کدهای ما را زیاد میکند چرا که تحت کنترل کامپایلر نیست.

‫کار با اسناد در RavenDb 4، بازیابی اسناد

$
0
0
در قسمت قبلعملیات ثبت و ویرایش اسناد را بررسی کردیم. همچنین نحوه‌ی کار متد LoadAsync (و یا Load) را دیدیم. برای بازیابی یک سند، به همرا اسناد مرتبط با آن، از Load به همراه متد Include استفاده می‌کنیم.
در این مثال میخواهیم آدرس شخص مورد نظر در برنامه با کد 59 بازیابی شود.
var user = _documentSession
    .Include<User>(x => x.Apps[59].AddressId)
    .Load("Users/131-A");
var address = _documentSession.Load<Address>(user.Apps[59].AddressId)

و در صورتیکه بخواهیم تمام آدرس‌های او در تمام برنامه‌های ثبت شده را داشته باشیم، به کد زیر می‌رسیم:
var user = _documentSession
    .Include<User>(x => x.Apps.Values.Select(app => app.AddressId))
    .Load("Users/131-A");
var addresses = List<Address>();
foreach(app in user.Apps)
{
    addresses.Add(_documentSession.Load<Address>(app.AddressId)); //query‌سمت کلاینت انجام اجرا می‌شود
}

 متد Load بسیار سریع کل سند ما را بازیابی میکند اما:
  • حتما باید Id سند(ها) را داشته باشیم.
  • کل سند را بازیابی میکند.
برای رفع این دو مشکل میتوانیم از امکانات Query نویسی در RavenDb استفاده کنیم. به دلیل ذخیره سازی (ظاهرا) فله‌ای اطلاعات در NoSqlها، Query گرفتن از حجم بسیار زیاد این اطلاعات، کار زمان بری است و اجرای Query بدون Index گذاری، کار بیهوده‌ای می‌شود. به همین دلیل با هر Query که اجرا می‌شود، به صورت خودکار یک Index برای آن توسط RavenDb ایجاد شده و Query بر روی Index ایجاد شده، اجرا می‌شود. عملیات Index کردن اطلاعات بصورت اتوماتیک در اولین بار اجرای Query با توجه به حجم داده‌ها می‌تواند بسیار کند باشد. همچنین ما کنترلی بر روی مدیریت ایندکس‌های ایجاد شده نداریم.
Queryها در RavenDb به چند صورت نوشته می‌شوند:

Query
متد Query برای ایجاد Query با استفاده از Linq کاربرد دارد. به مثال زیر توجه کنید:
List<User> users = await _documentSession
    .Query<Users>()
    .Where(u => u.PhoneNumber.StartsWith("915"))
    .ToListAsync();
اجرای Query بالا ابتدا باعث ایجاد یک Index بر روی ویژگی PhoneNumber می‌شود و سپس لیست کاربران را بر می‌گرداند.
برای بازیابی اطلاعات کاربران یک برنامه میتوانیم از Dictionary خود Query بگیریم:
var users = await _documentSession.Query<AppUser>()
    .Where(u => u.Id.Equals("915"))
    .Select(u => new
    {
        u.Apps[appCode].FirstName,
        u.Apps [appCode].LastName,
    })
    .ToListAsync();
این Query در RQL که زبان پرس و جوی مخصوص RavenDb است، چیزی شبیه کد زیر می‌شود:
from Users as user
where startsWith(user.PhoneNumber, "915")
select  {
    FirstName : user.Apps ["59"].FirstName,
    LastName : user.Apps ["59"].LastName
}
مشکلی که در این Query وجود دارد این‌است که کاربرانی که شماره تماس آن‌ها با 915 شروع شده است ولی در برنامه‌ای با کد 59 ثبت نشده‌اند هم در Query بازگشت داده می‌شوند و مقادیر بازگشتی برای فیلدها هم null خواهد بود. اگر بجای ذکر صریح عبارت u.Apps[appCode].FirstName به صورت زیر عمل کنیم:
from u in _documentSession.Query<User>()
                where u.PhoneNumber.StartsWith("915")
                let app = u.Apps["59"]
                select new
                {
                    app.FirstName,
                    app.LastName,
                };
عبارت let app = u.Apps["59"] در RQL تبدیل به یک متد جاوااسکریپتی می‌شود و به کدی شبیه به کد زیر می‌رسیم:
declare function output(u) {
var app = u.Apps["59"];
return { FirstName : app.FirstName, LastName : app.LastName};
}
from Users as user
where startsWith(user.PhoneNumber, "915")
select output(user)
حالا میتوانیم Key مورد نظر در دیکشنری را هم در Query به شکل زیر دخیل کنیم:
app.FirstName,
app.LastName,
*key = u.ActiveInApps.Select(a => a.Key)
و در ادامه با استفاده از متد Search، این فیلد را که به کلید دیکشنری اشاره می‌کند، محدود کرده و بعد از آن Query خود را اجرا میکنیم:
query = query.Search(u => u.key, "59");
در صورتیکه بجای دیکشنری از آرایه استفاده کرده باشیم هم کدهای ما به همین صورت می‌باشد با کمی تغییرات مربوط به تفاوت List و Dictionary!
اما هنوز Query ما بدرستی کار نمیکند چرا که ویژگی Key در RavenDb ایندکس نشده‌است و نمیتواند این ایندکس را هم تشخیص دهد. دلیل آن هم این است که تنها ویژگی‌هایی که در مرتب سازی (Sort) و یا فیلتر مورد استفاده قرار گیرند، به ایندکس‌ها اضافه می‌شوند. برای حل این مشکل باید بصورت دستی Index خود را در RavenDb بسازیم. این کار با ارث بری از کلاس پایه‌ی AbstractIndexCreationTask شروع می‌شود و مدلی را که میخواهیم Index بر روی آن اعمال شود نیز ذکر میکنیم و بعد از آن در سازنده‌ی کلاس، Index خود را می‌سازیم:
public class User_MyIndex : AbstractIndexCreationTask<User>
{
    Map = users => 
                           from u in users
                           from app in u.Apps
                           select new
                           {
                                 Id = u.Id,
                                 PhoneNumber = u.PhoneNumber,
                                 UserName = app.Value.UserName,
                                 FirstName = app.Value.FirstName,
                                 LastName = app.Value.LastName,
                                 IsActive = app.Value.IsActive,
                                 key = app.Key
     };
}
در این ایندکس به ازای هر کاربر، تمام برنامه‌هایی که ثبت شده، بررسی شده و ایندکس می‌شوند. نکته‌ای که باید به آن توجه کنید این است که ویژگی‌های ذکر شده فقط به RavenDb نحوه‌ی بازیابی فیلدهای سند را برای Index گذاری می‌گوید و همچنان خروجی این Index از نوع User بوده و تمام سند را بازگشت میدهد و باید از متد Select در صورت نیاز استفاده کنیم. برای اعمال این ایندکس به سمت سرور از متد:
new User_MyIndex().Execute(store);
و برای ارسال چندین Index به سمت سرور از متد:
IndexCreation.CreateIndexes(typeof(User_MyIndex).Assembly, store);
استفاده می‌کنیم. اکنون اگر به Query خود این ایندکس را معرفی کنیم، خروجی ما به‌درستی فقط کاربران برنامه مورد نظر را بر می‌گرداند:
from u in _documentSession.Query<User, User_MyIndex>() ...
کلاس AbstractIndexCreationTask متدهای زیادی برای کنترل دقیق Indexها در اختیار ما قرار میدهد که پرکاربردترین آن‌ها میتوانند متدهای زیر باشند: 
Index : نحوه‌ی Index کردن هر یک از پراپرتی‌ها را مشخص می‌کند.
Store : برای مواقعی کاربرد دارد که شما می‌خواهید مقدار Index شده را برای دسترسی سریع‌تر همرا با Index ذخیره کنید.
LoadDocument: این متد Id یا لیستی از Idها را به عنوان ورودی گرفته و سند مورد نظر را بازیابی می‌کند. زمانیکه میخواهیم اسناد مرتبط را همراه با سند، Index کنیم کاربرد دارد. برای مثال وقتی میخواهیم Addressهای کاربر را که در سندی جداگانه قرار دارند، به همراه اطلاعات او در Index شرکت دهیم:
select new
{
      ...
      key = aia.Key,
      Address = LoadDocument<Address>(aia.Value.AddressId),
      // City = LoadDocument<Address>(aia.Value.AddressId).City,
};
و برای Indexکردن لیستی از اسناد مرتبط به صورت زیر از LoadDocument استفاده میکنیم:
Message = app.Messages.Select(m => LoadDocument<Message>(m).Content)
* زمانی که میخواهید کلید یک Dictionary را Index کنید و میخواهید نام فیلد آن را key قرار دهید باید از k کوچک استفاده کنید؛ چرا که Key، جزء کلمات رزرو شده‌ی RavenDb می‌باشد.

DocumentQuery
دسترسی بیشتری را بر روی Query ارسالی به سمت سرور به ما می‌دهد؛ اما  strongly typed نیست. برای مثال Query بالا را به این صورت میتوانیم با DocumentQuery پیاده کنیم:
var users = _documentSession.Advanced.AsyncDocumentQuery<User, User_MyIndex>()
      .WhereStartsWith(nameof(AppUser.PhoneNumber), "915")
      .WhereEquals("key", appCode, exact: true)
      .SelectFields<AppUserModel>(new[] { $"Apps[{appCode}].FirstName", $"Apps[{appCode}].LastName" })
      .ToListAsync();
متدهای DocumentQuery بسیار متنوع هستند و میتوانید لیست آن‌ها را در اینجا مشاهده کنید.

MoreLikeThis (اسناد شبیه)
از رایج‌ترین کارهایی که در وب سایت‌های مطرح دیده می‌شود نمایش مطالب مرتبط با مطلب جاری می‌باشد و از آنجایی که RavenDb از Lucene.NET برای ایندکس کردن اسناد استفاده می‌کند، میتواند براحتی از MoreLikeThis موجود در پروژه‌ی Contrib آن استفاده نماید.
مدل زیر را در نظر بگیرید:
public class Post
    {
        public int Id { get; set; }
        public string Content { get; set; }
        public string Title { get; set; }

        public List<string> Tags { get; set; }
        public string WriterName { get; set; }
        public string WriterId { get; set; }
    }
برای استفاده از MoreLikeThis باید ابتدا محتویات مطلب خود را با استفاده از StandardAnalyzer ایندکس گذاری کنیم. همانطور که گفته شد، برای Index کردن یک سند از کد زیر میتوانیم استفاده کنیم. با این تفاوت که نحوه‌ی آنالیز سند را نیز مشخص میکنیم:
public class Post_ByContent : AbstractIndexCreationTask<Post>
{
    public Post_ByContent()
    {
        Map = posts=> from post in posts
                      select new
                      {
                          post.Content
                      };

        Analyzers.Add(p => p.Content, "StandardAnalyzer");
    }
}
از این ایندکس در Query به همراه متد MoreLikeThis استفاده میکنیم:
List<Post> posts = _documentSession
    .Query<Post, Post_ByContent>()
    .MoreLikeThis(builder => builder
        .UsingDocument(p => p.Id == "posts/59-A")
        .WithOptions(new MoreLikeThisOptions
        {
            Fields = new[] { nameof(Post.Content) },
            StopWordsDocumentId = "appConfig/StopWords"
        }))
    .ToList();
ابتدا سندی را که میخواهیم اسناد شبیه به آن بازیابی شود، معرفی میکنیم. به اینصورت بررسی بر روی تمام فیلدهای Indexگذاری شده اعمال می‌شود. اگر بخواهیم تنظیماتی را به متد اضافه کنیم از MoreLikeThisOptions استفاده میکنیم. حداقل تنظیمات میتواند معرفی نام فیلد مورد نظر برای کاهش بار سرور و همچنین معرفی سندی که StopWordهای ما در آن قرار دارد، باشد. می‌توانید در مورد StopWordها و کاربرد آن در Lucene از این مقاله استفاده کنید. 

‫گرافیک و چند رسانه‌ای در WPF - بخش اول

$
0
0
برنامه نویسانی که می‌خواهند رابط کاربری و محتوای جالبی بسازند، Windows Presentation Foundation (WPF) از چند رسانه‌ای ، گرافیک برداری، انیمیشن و ترکیبی از آن‌ها پشتیبانی می‌کند. با استفاده از Microsoft Visual Studio می‌توانید یک گرافیک برداری یا انیمیشن پیچیده و درج مدیا را در داخل برنامه داشته باشید.
این مبحث ویژگی‌های گرافیکی، انیمیشن و مدیای WPF  را معرفی می‌کند و شما را برای اضافه کردن گرافیک، افکت، صدا و ویدئو در داخل برنامه‌اتان قادر می‌سازد.
موارد جدیدی که با گرافیک و چند رسانه‌ای در WPF 4 مطرح شده‌اند:
چندین تغییری که مرتبط با انیمیشن و گرافیک می‌باشد اتفاق افتاده است.

  • Layout Rounding :
هنگامیکه لبه‌ی یک شیء در وسط یک دستگاه پیکسلی می‌افتد، سیستم گرافیکی مستقل از DPI می‌تواند لبه‌های تار یا نیمه شفاف را ایجاد و رندر کند.
این مثال تاثیر استفاده از  UseLayoutRounding را شرح می‌دهد. اگر شما در این مثال به آرامی اندازه پنجره را تغییر دهید، تفاوت دو شیء رسم شده را کاملا درک خواهید کرد.
<Page x:Class="LayoutRounding.Lines"  
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
    Title="Lines" Name="linesPage"  >  <StackPanel Width="150"  Margin="7" Orientation="Horizontal">  <!-- Single pixel line with layout rounding turned OFF.-->  <Rectangle UseLayoutRounding="False"  
       Width="45.5" Margin="10" Height="1" Fill="Red"/>  <!-- Single pixel line with layout rounding turned ON.-->  <Rectangle UseLayoutRounding="True"  
      Width="45.5" Margin="10" Height="1" Fill="Red"/>  </StackPanel>  <!-- Background Grid -->  <Page.Background>  <DrawingBrush  Viewport="0,0,10,10" ViewportUnits="Absolute" TileMode="Tile">  <DrawingBrush.Drawing>  <DrawingGroup>  <GeometryDrawing Brush="White">  <GeometryDrawing.Geometry>  <RectangleGeometry Rect="0,0,1,1" />  </GeometryDrawing.Geometry>  </GeometryDrawing>  <GeometryDrawing Geometry="M0,0 L1,0 1,0.1, 0,0.1Z " Brush="#CCCCFF" />  <GeometryDrawing Geometry="M0,0 L0,1 0.1,1, 0.1,0Z" Brush="#CCCCFF" />  </DrawingGroup>  </DrawingBrush.Drawing>  </DrawingBrush>  </Page.Background>  </Page>
و اگر به تصویر دقت کنید در شی سمت چپ از Layout Rounding استفاده نشده‌است. ولی در شیء سمت راست از Layout Rounding استفاده شده‌است.

برای توضیحات بیشتر در مورد Layout Roundingمی‌توانید به لینک درج شده مراجعه کنید.

  • Cached Composition :
با استفاده از کلاس‌های جدید BitmapCache و BitmapCacheBrush می‌توانید یک UIElementیا عنصرپیچیده را cache کنید و تا حد زیادی زمان Render شدن را بهبود دهید. Cached Composition زمانی کاربرد دارد که شما نیاز به animate، translate و ... دارید تا یک UIElement را با سرعت ممکن scale کنید. کلاس BitmapCacheBrush برای استفاده مجدد از یک عنصر ( UIElement)   cache  شده مؤثر می‌باشد. برای cache یک عنصر یا element شما باید یک وهله‌ی جدید از کلاس BitmapCache را ایجاد کنید و  به خصوصیت CacheMode عنصر دسترسی پیدا کنید.
مثال زیر چگونگی استفاده مجدد از عنصر cache شده را نمایش می‌دهد. عنصر cache شده یک Image control می‌باشد که یک عکس بزرگ را نمایش می‌دهد و کنترل تصویر  به صورت bitmap با استفاده از کلاس (BitmapCache) cache شده است و از کلاس BitmapCacheBrush برای استفاده مجدد از یک عنصر ( UIElement) cache شده، استفاده شده‌است و قلم مو یا Brush اختصاص داده شده به Background  از طریق بیست و پنج دکمه تا تاثیرات استفاده مجدد را نمایش دهد.
<Window x:Class="BitmapCacheBrushDemo.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1"  
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
        Height="300" Width="300" ><Window.Resources><RichTextBox x:Key="cachedRichTextBox"  ><RichTextBox.CacheMode><BitmapCache EnableClearType="True" RenderAtScale="1" SnapsToDevicePixels="True" /></RichTextBox.CacheMode></RichTextBox><BitmapCacheBrush x:Key="cachedRichTextBoxBrush" Target="{StaticResource cachedRichTextBox}"><BitmapCacheBrush.BitmapCache><BitmapCache EnableClearType="False" RenderAtScale="0.4" SnapsToDevicePixels="False" /></BitmapCacheBrush.BitmapCache></BitmapCacheBrush>        </Window.Resources><Grid><Grid.RowDefinitions><RowDefinition Height="*" /><RowDefinition Height="*" /><RowDefinition Height="*" /><RowDefinition Height="*" /><RowDefinition Height="*" /></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="*" /><ColumnDefinition Width="*" /><ColumnDefinition Width="*" /><ColumnDefinition Width="*" /><ColumnDefinition Width="*" /></Grid.ColumnDefinitions><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button1" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button2" Grid.Column="1" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button3" Grid.Column="2" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button4" Grid.Column="3" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button5" Grid.Column="4" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button6" Grid.Row="1" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="1" Name="button7" Grid.Row="1" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="2" Name="button8" Grid.Row="1" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="3" Name="button9" Grid.Row="1" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="4" Name="button10" Grid.Row="1" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button11" Grid.Row="2" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="1" Name="button12" Grid.Row="2" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="2" Name="button13" Grid.Row="2" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="3" Name="button14" Grid.Row="2" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="4" Name="button15" Grid.Row="2" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button16" Grid.Row="3" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="1" Name="button17" Grid.Row="3" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="2" Name="button18" Grid.Row="3" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="3" Name="button19" Grid.Row="3" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="4" Name="button20" Grid.Row="3" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Name="button21" Grid.Row="4" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="1" Name="button22" Grid.Row="4" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="2" Name="button23" Grid.Row="4" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="3" Name="button24" Grid.Row="4" FontWeight="Bold" /><Button Background="{StaticResource cachedRichTextBoxBrush}" Content="Button" Grid.Column="4" Name="button25" Grid.Row="4" FontWeight="Bold" /></Grid></Window>
برای توضیحات بیشتر در مورد Cached Composition می‌توانید به لینک درج شده مراجعه نمایید.
  • Pixel Shader 3 Support :
WPF 4 از ShaderEffect که در WPF 3.5 SP1  معرفی شد پشتیبانی می‌کند و اجازه می‌هد برنامه‌ها Effect را با استفاده از Pixel Shader(PS) version 3.0 اعمال کنند. مدل PS 3.0 Shader به طور چشمگیری پیشرفته‌تر از  از PS 2.0 می‌باشد و این باعث می‌شود Effect‌های متنوع‌تر، بهتر و یا قانع کننده‌تری را سخت افزارها پشتیبانی کنند.
برای آشنایی بیشتر با کلاس  ShaderEffect می‌توانید به لینک درج شده مراجعه نمایید.
  • Easing Functions :
  می‌توانید انیمیشن‌های بیشتری را با easing function اضافه کنید که کنترل زیادی را بر روی رفتار انیمیشن می‌دهد. برای مثال شما می‌توانید یک ElasticEase را به یک انیمیشن اعمال کنید که رفتار انیمیشن را تبدیل به یک پرنده می‌کند. برای اطلاعات بیشتر  به آدرس درج شده مراجعه کنید.
عنوان بخش دوم Graphics and Rendering می‌باشد که در ادامه آشنا خواهیم شد.    

    ‫مرتب سازی صحیح حروف فارسی در بانک اطلاعاتی SQLite

    $
    0
    0
    فرض کنید لیست حروف الفبای فارسی را در یک بانک اطلاعاتی SQLite ذخیره کرده‌اید:
    var connection = new SqliteConnection("Data Source=:memory:");
    connection.Open();
    
    var createCommand = connection.CreateCommand();
    createCommand.CommandText =
                @"
                    CREATE TABLE persian_letter (
                        value TEXT
                    );
    
                    INSERT INTO persian_letter
                    VALUES ('ا'),('ب'),('پ'),('ت'),('ث'),('ج'),('چ'),('ح'),('خ'),('د'),('ذ'),('ر'),('ز'),('ژ'),('س'),('ش'),
                           ('ص'),('ض'),('ط'),('ظ'),('ع'),('غ'),('ف'),('ق'),('ک'),('گ'),('ل'),('م'),('ن'),('و'),('ه'),('ی');
                ";
    createCommand.ExecuteNonQuery();
    اگر از این لیست کوئری گرفته و آن‌ها‌را مرتب کنیم:
    var queryCommand = connection.CreateCommand();
    queryCommand.CommandText =
                @"
                    SELECT value
                    FROM persian_letter
                    order by value
                ";
    var reader = queryCommand.ExecuteReader();
    var sortedDbItems = new List<string>();
    while (reader.Read())
    {
        sortedDbItems.Add($"{reader["value"]}");
    }
    یک چنین خروجی حاصل می‌شود:


    همانطور که ملاحظه می‌کنید، مرتب سازی حروف فارسی در اینجا به صورت پیش‌فرض کار نمی‌کند. علت اینجا است که روش پیش‌فرض مرتب سازی حروف در SQLite، بر اساس کد اسکی حروف است و فقط در مورد حروف ASCII از A تا Z درست کار می‌کند.


    امکان تعریف Collation سفارشی در SQLite

    در بانک‌های اطلاعاتی، قابلیتی که مستقیما بر روی نحوه‌ی جستجو و همچنین مرتب سازی حروف تاثیر می‌گذارد، Collation نام دارد و در SQLite برخلاف بسیاری از بانک‌های اطلاعاتی دیگر، امکان تعریف Collation سفارشی نیز وجود داردو برای این منظور باید یک function pointer را در اختیار آن قرار داد تا از آن در سمت بانک اطلاعاتی جهت مرتب سازی و جستجوی حروف استفاده کند.
    خوشبختانه پروژه‌ی Microsoft.Data.Sqliteامکان تبدیل یک managed delegate دات نتی را به یک function pointer مخصوص SQLite، میسر می‌کند. به عبارتی SQLite کدهای دات نتی را در حین انجام محاسبات خود اجرا خواهد کرد و این اجرا به صورتی نیست که ابتدا کل اطلاعات، به سمت برنامه‌ی کلاینت منتقل شود و سپس در این سمت، در حافظه، عملیاتی بر روی آن صورت گیرد. کل عملیات در سمت بانک اطلاعاتی مدیریت می‌شود.
    روش تعریف یک Collation جدید هم در اینجا بسیار ساده‌است:
    connection.CreateCollation("PersianCollationNoCase", (x, y) => string.Compare(x, y, ignoreCase: true));
    فقط کافی است بر روی اتصال باز شده، متد CreateCollation فراخوانی و نحوه‌ی مقایسه‌ی دو رشته مشخص شود. سپس این Collation نامدار، به صورت زیر در کوئری‌ها قابل استفاده خواهد بود:
    SELECT value
    FROM persian_letter
    order by value COLLATE PersianCollationNoCase
    اینبار اگر خروجی برنامه را بررسی کنیم، مشاهده خواهیم کرد که مرتب سازی حروف فارسی در SQLite به درستی کار می‌کند:



    تعریف Collation سفارشی غیرحساس به «ی و ک» !

    این مورد شاید یکی از آرزوهای توسعه دهندگان SQL Server باشد! اما با SQLite به سادگی زیر قابل تعریف و مدیریت است:
    connection.CreateCollation("PersianCollationNoCaseYekeInsensitive",
    (x, y) => string.Compare(x.ApplyCorrectYeKe(), y.ApplyCorrectYeKe(), ignoreCase: true));
    متد ApplyCorrectYeKe فوق از بسته‌ی نیوگت DNTPersianUtils.Coreدریافت شده و کار آن یک‌دست کردن «ی و ک» فارسی و عربی است.
    در یک چنین حالتی اگر اطلاعاتی را به همراه «ی و ک» فارسی و یا عربی ثبت کنیم:
    CREATE TABLE persian_letter (
    value TEXT
    );
    INSERT INTO persian_letter
    VALUES ('ی'),('ک');
    جستجوی بر روی آن‌ها دیگر وابسته‌ی به مقدار «ی و ک» وارد شده نبوده و چه «ی و ک» فارسی وارد شود و چه عربی، این کوئری همواره کار می‌کند:
    SELECT count()
    FROM persian_letter
    WHERE value = 'ى' COLLATE PersianCollationNoCaseYekeInsensitive


    کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: SQLitePersianCollation.zip

    ‫مقایسه و بررسی پوشه‌های Bin و Obj در برنامه‌های NET.

    $
    0
    0
    زمانیکه شما یک برنامه‌ی دات نتی نوشته شده به زبان سی شارپ را کامپایل می‌کنید، دو پوشه‌ی bin و obj را مشاهده می‌کنید. در این مطلب، اهمیت و تفاوت این دو پوشه را بررسی می‌کنیم. این پوشه‌ها حاوی کد‌های کامپایل شده‌ی IL می‌باشند. 

    فرآیند کامپایل به دو مرحله‌ی کامپایل سورس کد و همچنین ایجاد پیوند (linking) بین فایل‌های کامپایل شده تقسیم می‌شود.
    به دیاگرام زیر دقت نمائید:
    • در مرحله‌ی کامپایل، هر یک از سورس فایل‌ها، بصورت مجزا کامپایل می‌شود و به ازای هر سورس فایل، یک فایل مجزای کامپایل شده تولید می‌شود. بدین معنا که اگر دو فایل داشته باشیم، دو کد مستقل کامپایل شده نیز تولید می‌شوند.
    • در مرحله‌ی متصل کردن (linking)، همه‌ی فایل‌های کامپایل شده به یک اسمبلی واحد کامپایل می‌شوند که این اسمبلی می‌تواند از نوع DLL و یا EXE باشد.

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

    اما سؤالی که در ذهن ایجاد می‌شود این است که چرا این فرآیند در دو مرحله انجام می‌شود و چرا فرآیند کامپایل در یک مرحله انجام نمی‌شود. با انجام 2 مرحله‌ای فرآیند کامپایل، می‌توان از قابلیت کامپایل شرطی (Conditional Compiling) بهره‌مند شد.
    زمانیکه یک پروژه‌ی بزرگ را کامپایل می‌کنیم، انتظار داریم تنها فایل‌هایی را که تغییر کرده‌اند، کامپایل شوند و در پوشه‌ی obj، ما هر فایل کامپایل شده را بصورت مجزا داریم. در نتیجه می‌توانیم تشخیص دهیم  که چه فایل‌هایی تغییر کرده‌اند و تنها آنها را کامپایل می‌کنیم که این کار باعث افزایش سرعت فرآیند کامپایل می‌شود.

    بطور خلاصه در پوشه‌ی obj، ما به ازای هر سورس فایل، یک فایل کامپایل شده داریم؛ اما در پوشه‌ی bin تنها یک فایل یکپارچه شده را خواهیم داشت.

    ‫کدامیک از بسته‌های NET Core. را باید دریافت کنیم؟

    $
    0
    0
    زمانیکه به صفحه‌ی دریافتنگارش‌های مختلف NET Core. مراجعه می‌کنیم، بسته‌های مختلفی از یک نگارش قابل مشاهده هستند و در بدو امر واضح نیست که کدامیک را باید دریافت کرد. در این مطلب تفاوت‌های بین این بسته‌ها را مرور خواهیم کرد.


    کدام نگارش‌های NET Core. بر روی سیستم شما نصب هستند؟

    پیش از انجام هرکاری نیاز است بررسی کنیم کدامیک از بسته‌های ارائه شده، بر روی سیستم جاری نصب هستند. برای انجام اینکار دستور زیر را در خط فرمان صادر کنید:
     dotnet --info
    اگر این دستور کار نکرد و خطایی را دریافت کردید، یعنی NET Core. اصلا بر روی سیستم شما نصب نیست. برنامه dotnet.exe جزئی از runtime نصب شده‌است و به صورت خودکار به path سیستم اضافه می‌شود. به همین جهت است که در صورت نصب آن، فرمان dotnet در هر مسیری قابل اجرا است.
    Runtime تنها ویژگی‌های اساسی جهت اجرای برنامه‌های از پیش کامپایل شده‌ی NET Core. را با اجرای فرمانی مانند dotnet mydll.dll و یا اجرای دستور dotnet --info برای دریافت اطلاعاتی از جزئیات این ویژگی‌ها، به همراه دارد. اما برای کار با سورس کدها، build، publish و هر کار دیگری با آن‌ها، حتما باید SDK نیز نصب شود.

    خروجی فرمان فوق بر روی سیستم من چنین چیزی است:
     C:\Users\Vahid>dotnet --info
    .NET Core SDK (reflecting any global.json):
     Version: 2.1.301
     Commit: 59524873d6
    
    Runtime Environment:
     OS Name:   Windows
     OS Version:  10.0.17134
     OS Platform: Windows
     RID: win10-x64
     Base Path: C:\Program Files\dotnet\sdk\2.1.301\
    
    Host (useful for support):
      Version: 2.1.1
      Commit:  6985b9f684
    
    .NET Core SDKs installed:
      2.1.300 [C:\Program Files\dotnet\sdk]
      2.1.301 [C:\Program Files\dotnet\sdk]
    
    .NET Core runtimes installed:
      Microsoft.NETCore.App 2.1.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
      Microsoft.NETCore.App 2.1.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
    
    To install additional .NET Core runtimes or SDKs:
      https://aka.ms/dotnet-download
    اولین شماره نگارش نمایش داده شده‌ی در این لیست (2.1.301)، شماره نگارش SDK فعال است. سپس شماره نگارش 2.1.1 به معنای شماره نگارش Runtime فعال بر روی سیستم است که هاست dotnet.exe به شمار می‌رود. سپس لیست SDKها و Runtimeهای نصب شده‌ی بر روی سیستم را نمایش می‌دهد.
    باید دقت داشت که بر روی یک سیستم می‌توان چندین SDK و چندین Runtime مختلف را نصب کرد و هر پروژه از شماره نگارش خاصی استفاده کند. شماره نگارش runtime استفاده شده‌ی در پروژه‌ها در فایل csproj، توسط مدخل زیر مشخص می‌شود:
    <TargetFramework>netcoreapp2.1</TargetFramework>
    در مورد SDK اینطور نیست و همواره از آخرین SDK نصب شده (همان شماره نگارش SDK فعال فوق) استفاده می‌شود، مگر اینکه فایل ویژه‌ای به نام global.json را در ریشه‌ی اصلی solution قرار دهید؛ با این محتوای فرضی:
     {
      "sdk": {
            "version": "2.1.300-rc.31211"
      }
    }
    در این حالت پروژه‌ی جاری را وادار می‌کنید بجای استفاده‌ی از آخرین SDK نصب شده‌ی بر روی سیستم، از نگارش SDK خاصی استفاده کند.
    البته در اکثر موارد نیازی به انجام این کار نیست؛ چون SDK، با تمام نگارش‌های قبلی سازگار است و همواره استفاده‌ی از آخرین SDK نصب شده توصیه می‌شود. به همین جهت فایل global.json را پس از ایجاد یک solution جدید مشاهده نمی‌کنید؛ مگر اینکه خودتان به دلایل خاصی آن‌را اضافه و مقید نمائید.


    تفاوت بسته‌های مختلف قابل دریافت NET Core. در چیست؟

    زمانیکه برای دریافت آخرین نگارش NET Core. به سایت آن مراجعه می‌کنیم، به ازای هر نگارش، یک چنین لیستی قابل مشاهده است:
    • .NET Core Runtime
    • .NET Core SDK
    • .NET Core Hosting Bundle
    • Visual Studio
    • ASP.NET Core Installer
    و اکنون سؤالی که مطرح می‌شود این است: کدامیک را باید دریافت کرد؟

    Visual Studio

    اگر کاربر ویندوز هستید، با نصب آخرین نگارش Visual Studio، می‌توانید به همراه آن، آخرین نگارش SDK ،runtime و اجزای هاست برنامه‌های ASP.NET Core بر روی IIS را نیز بر روی سیستم خود نصب کنید.


    NET Core SDK.

    هدف از ارائه‌ی بسته‌ی SDK، انجام فرآیندهای build‌، اجرا و مدیریت امور مرتبط با NET Core.، بدون استفاده از Visual Studio و بر روی تمام سیستم عامل‌های پشتیبانی شده‌است. زمانیکه یک بسته‌ی SDK را نصب می‌کنید، به همراه آن این موارد نیز نصب می‌شوند:
    • .NET Core SDK 
    • .NET Core Runtime 
    • ASP.NET Core Runtime
    به همین جهت حجم آن از بسته‌ی تکی runtime بیشتر است و با نصب آن دیگر نیازی به دریافت مجزای بسته‌های runtime نیست.

    بنابراین دلیل نصب آن می‌تواند شامل یکی از موارد زیر باشد:
     - بر روی سیستمی که در حال توسعه‌ی برنامه‌های مبتنی بر NET Core. هستید. این تمام چیزی است که به آن نیاز دارید.
     - بر روی سروری که نیاز است دستور dotnet را برای انجام فرآیندهای build/publish اجرا کند.


    NET Core Runtime.

    بسته‌های Runtimes، کوچکترین بسته‌ی ممکن در این لیست هستند و هدف از آن‌ها صرفا اجرای برنامه‌های کامپایل شده‌ی NET Core. در سکوهای کاری مختلف پشتیبانی شده‌ی توسط آن است.
    باید دقت داشت که اگر برنامه‌ی شما از «ASP.NET Core meta package» استفاده می‌کند، این بسته در runtime لحاظ نشده‌است و در یک چنین حالتی باید بسته‌ی ASP.NET Core را به صورت جداگانه دریافت و نصب کنید. هرچند اگر از این متاپکیج‌ها استفاده نکنید و بسته‌های مورد نیاز را به صورت مستقیم به برنامه‌ی خود اضافه کنید، این بسته‌ها جزئی از فایل‌های publish نهایی بوده و در این حالت برنامه توسط بسته‌ی runtime نیز قابل اجرا است.
    در این حالت برنامه‌ی dotnet بجز اجرای برنامه‌ها و ارائه‌ی اطلاعاتی در مورد خود آن، کارهای دیگری را مانند build و یا publish، نمی‌تواند انجام دهد و برنامه در این حالت باید کاملا از پیش کامپایل شده باشد.

    بنابراین دلیل نصب آن می‌تواند شامل یکی از موارد زیر باشد:
    - برای اجرای برنامه‌های از پیش کامپایل شده‌ای که به همراه تمام وابستگی‌های مورد نیاز هم هستند.
    - برای اجرای برنامه‌های وبی که از ASP.NET Meta packages استفاده نمی‌کنند


    ASP.NET Core Installer

    همانطور که در توضیحات بسته‌ی runtime عنوان شد، این بسته، متاپکیج‌های ASP.NET Core را به همراه ندارد. اگر به آن‌ها نیاز دارید، باید آن‌ها را به صورت جداگانه توسط ASP.NET Core installer نصب کنید که شامل این موارد است:
    - The ASP.NET Runtime Meta Packages
    - Microsoft.AspNetCore.App
    - Microsoft.AspNetCore.All
     
    NET Core Windows Hosting Pack.

    نصب این بسته برای هاست برنامه‌های ASP.NET Core در ویندوز و بر روی IIS ضروری است و شامل این اجزا می‌شود:
    - 32 bit and 64 .NET Core Runtimes
    - ASP.NET Runtime Packages (Microsoft.AspNetCode.App/All)
    - IIS Hosting Components
    بنابراین این بسته شامل تمام موارد یاد شده‌است منهای قابلیت‌های SDK برای build و publish برنامه‌ها.



    بنابراین به صورت خلاصه

    برای سرورها این موارد را نصب کنید:
    - در ویندوز: Windows Server Hosting Bundle
    - برای Mac و لینوکس:  .NET Core Runtime + ASP.NET Core Runtimes

    برای سیستم توسعه‌ی شخصی این موارد را نصب کنید:
    - SDK
    - اگر از ویندوز استفاده می‌کنید: Visual Studio هم به همراه SDK نصب می‌شود.

    برای اجرای برنامه‌های از پیش کامپایل شده که به همراه تمام وابستگی‌های مورد نیاز هم هستند:
    - تنها Runtime را نصب کنید.
    اگر این برنامه‌ی از پیش کامپایل شده از ASP.NET Runtime Meta packages استفاده می‌کند:
    - ASP.NET Runtimes را نیز نصب کنید.

    ‫مرتب‌سازی، فیلتر کردن و صفحه‌بندی اطلاعات در ASP.NET Core

    $
    0
    0

    مقدمه

    اگر با Apiها کار کرده باشید احتمالاً با این چالش که گاهی نیاز است منابعی (Resources) که به کاربر ارسال می‌شوند مرتب (Sort)، بر اساس درخواست کاربر فیلتر (Filter) و در صفحه‌بندی (Paging) مشخصی تحویل داده شوند، بر خورد کرده‌اید. این نیاز خصوصاً در پاسخ (Response) با روش GET از استاندارد HTTP مشهود است. در این مطلب به معرفی کتابخانه‌ای می‌پردازیم که با استفاده از آن می‌توان عملیات فوق را پیاده‌سازی نمود. Sieve یک چارچوب (Framework) ساده، تمیز و قابل توسعه برای NET Core. است. در زمان نگارش این مقاله ویرایش ۲.۱.۳ از این کتابخانه در دسترس است و همانگونه که اشاره شد، این کتابخانه منبع باز (Open Source) بوده و می‌توانید آن را از مخزن گیت‌هاب در این پیوند دریافت نمایید.
      
    سیستمی با یک موجودیت به نام "پست" (Post) مفروض است. با استفاده از کتابخانه Sieve عملیات مرتب‌سازی، فیلتر و صفحه‌بندی را هنگام درخواست (GET) تمامی پست‌ها اعمال خواهیم کرد.
    // Post
    
    public int Id { get; set; }
    
    public string Title { get; set; }
    
    public int LikeCount { get; set; }
    
    public int CommentCount { get; set; }
    
    public DateTimeOffset DateCreated { get; set; } = DateTimeOffset.UtcNow;

    ۱. نصب کتابخانه

    ابتدا لازم است از طریق Package Manager Console و اجرای دستور فوق اقدام به نصب این کتابخانه نمایید:  Install-Package Sieve -Version 2.1.3

    ۲. اضافه کردن سرویس‌

    در فایل Startup.cs سرویس SieveProcessor را تزریق کنید:
    services.AddScoped<SieveProcessor>();
     

    ۳. تعیین ویژگی‌هایی از کلاس برای اعمال مرتب‌سازی و فیلتر

    باید ویژگی‌هایی (Properties) از کلاس را که می‌خواهید اعمال مرتب‌سازی و فیلتر بر روی آن‌ها انجام شوند، مشخص کنید. به دو روش این امر ممکن است:
     

    ۱.۳. از طریق اضافه کردن صفت (Attribute) به ویژگی‌ها

    تنها ویژگی‌هایی از کلاس که دارای صفت [(Sieve(CanSort = true, CanFilter = true]باشند، می‌توانند مرتب و یا فیلتر شوند (می‌توان تنها از یکی از آن‌ها نیز استفاده نمود).
    لذا کلاس پست به صورت زیر ویرایش می‌شود:
    // Post
    
    public int Id { get; set; }
    
    [Sieve(CanFilter = true, CanSort = true)]
    public string Title { get; set; }
    
    [Sieve(CanFilter = true, CanSort = true)]
    public int LikeCount { get; set; }
    
    [Sieve(CanFilter = true, CanSort = true)]
    public int CommentCount { get; set; }
    
    [Sieve(CanFilter = true, CanSort = true, Name = "created")]
    public DateTimeOffset DateCreated { get; set; } = DateTimeOffset.UtcNow;

     

    ۲.۳. از طریق Fluent API

    برای استفاده از این روش، ابتدا کلاسی را ایجاد و از کلاس SieveProcessor مشتق کنید. سپس تابع MapProperties موجود در کلاس والد را override کنید.
    // ApplicationSieveProcessor
    
    public class ApplicationSieveProcessor : SieveProcessor
    {
        public ApplicationSieveProcessor(
            IOptions<SieveOptions> options, 
            ISieveCustomSortMethods customSortMethods, 
            ISieveCustomFilterMethods customFilterMethods) 
            : base(options, customSortMethods, customFilterMethods)
        {
        }
    
        protected override SievePropertyMapper MapProperties(SievePropertyMapper mapper)
        {
            mapper.Property<Post>(p => p.Title)
                .CanFilter()
                .HasName("a_different_query_name_here");
    
            mapper.Property<Post>(p => p.CommentCount)
                .CanSort();
    
            mapper.Property<Post>(p => p.DateCreated)
                .CanSort()
                .CanFilter()
                .HasName("created_on");
    
            return mapper;
        }
    }
    حال باید کلاس جدید را تزریق نمایید:
    services.AddScoped<ISieveProcessor, ApplicationSieveProcessor>();
    در هر دو روش پارامتری دیگر با نام "Name" نیز وجود دارد که می‌توانید با استفاده از آن برای هر ویژگی، نامی غیر از نام اصلی آن را اتخاذ نمایید.
     

    ۴. دریافت پرس‌و‌جوهای (Queries) مرتب/فیلتر/صفحه‌بندی با اضافه کردن SieveModel به کنترلر (Controller)

    برای دریافت پرس‌و‌جوهای مرتب/فیلتر/صفحه‌بندی، SieveModel را به اکشنی (Action) که پست‌ها را برگشت می‌دهد به عنوان پارامتر اضافه کنید. سپس با فراخوانی تابع Apply با استفاده از تزریق SieveProcessor در کنترلر خود، پرس‌و‌جوها را به منابع خود اعمال کنید.
    [HttpGet]
    public JsonResult GetPosts(SieveModel sieveModel) 
    {
        var result = _dbContext.Posts;
        result = _sieveProcessor.Apply(sieveModel, result);
        return Json(result.ToList());
    }
    توجه داشته باشید مقادیر پرس‌و‌جوها اختیاری است و هر کدام می‌توانند به تنهایی و یا با هم مورد استفاده قرار گیرند.
     

    ۵. ارسال درخواست

    با تمام موارد گفته شده، اکنون می‌توانید درخواستی را برای دریافت (GET) شامل پرس‌و‌جوهای مرتب/فیلتر/صفحه‌بندی ارسال نمایید. برای مثال:
    GET /GetPosts
    
    ?sorts=     LikeCount,CommentCount,-created         // sort by likes, then comments, then descendingly by date created 
    &filters=   LikeCount>10,Title@=awesome title,      // filter to posts with more than 10 likes, and a title that contains the phrase "awesome title"&page=      1                                       // get the first page...&pageSize=  10                                      // ...which contains 10 posts
    sorts= LikeCount,CommentCount,-created?: مرتب‌سازی بر اساس تعداد محبوبیت، سپس تعداد نظرات و در آخر تاریخ ثبت پست به صورت نزولی.
    filters= LikeCount>10,Title@=awesome title&: فیلتر بر اساس پست‌هایی که تعداد محبوبیت آن‌ها بیش از ۱۰ است و در عنوان خود شامل عبارت "awesome title" می‌باشند.
    page=1&: صفحه اول ...
    pageSize=10&: ... که شامل ۱۰ پست است.

    به صورت رسمی‌تر:
    sorts: فهرست دستورالعمل‌هایی شامل نام ویژگی‌هایی است که مرتب‌سازی بر روی آن‌ها اعمال می‌شود و از طریق کاما (,) از یکدیگر تمایز داده می‌شوند. با اضافه کردن - قبل از نام ویژگی، آن را به صورت نزولی مرتب نمایید.
    • filters: دستورالعمل‌های جدا شده توسط کاما (,) به صورت {Name}{Operator}{Value} که در آن:
      • {Name} نام ویژگی‌ای است که صفت Sieve بر روی آن تعریف شده و یا نام سفارشی‌ای است که کاربر تعیین کرده است.
        • همچنین می‌توانید بیش از یک نام (برای یای منطقی (OR)) در درون جفت پرانتز باز و بسته و جداکننده یای منطقی (|) داشته باشید. برای مثال: LikeCount|CommentCount)>10) مشخص می‌کند مقدار LikeCount و یا CommentCount بیش از ۱۰ باشد.
      • {Operator} یکی از عملگرهای ممکن است.
      • {Value} مقداری است که در عمل فیلتر مورد استفاده قرار می‌گیرد.
    • page: شماره صفحه‌ای است که برگشت داده می‌شود.
    • pageSize: تعداد مواردی است که در هر صفحه برگردانده خواهد شد.
     

    ۶. عملگرها (Operators)

    عملگر
    توضیحات
    عملگر
    توضیحات
    ==برابر=@ شامل
    =!مخالف
    =_ شروع شود با
    <بزرگ‌تر
    *=@ شامل (حساس به حروف)*
    >کوچک‌تر
    *=_ شروع شود با (حساس به حروف)
    =<بزرگ‌تر مساوی
    *== برابر (حساس به حروف)
    =>کوچک‌تر مساوی

     
    * حساس به بزرگی و کوچکی حروف

    ۷. پیکربندی

    برای پیکربندی شامل مواردی چون حساس به بزرگ و کوچک بودن نام ویژگی، تعداد صفحات پیشفرض، حداکثر تعداد صفحه مجاز و نحوه برخورد با نام ویژگی ناموجود در ویژگی‌های کلاس ابتدا قطعه زیر را به appsettings اضافه کنید.
    {
        "Sieve": {
            "CaseSensitive": "boolean: should property names be case-sensitive? Defaults to false",
            "DefaultPageSize": "int number: optional number to fallback to when no page argument is given. Set <=0 to disable paging if no pageSize is specified (default).",
            "MaxPageSize": "int number: maximum allowed page size. Set <=0 to make infinite (default)",
            "ThrowExceptions": "boolean: should Sieve throw exceptions instead of silently failing? Defaults to false"
        }
    }

     سپس سرویس فوق را در Startup.cs اضافه کنید:
    services.Configure<SieveOptions>(Configuration.GetSection("Sieve"));

    ‫xamarin.android قسمت اول

    $
    0
    0
    هدف از این سری آشنایی با زامارین اندروید میباشدکه آشنایی با سی شارپ پیش نیاز آن میباشد و ورژن ویژوال استودیو 2017 من در حال حاضر 15.7.4 می‌باشد.
     اولین پروژه را با زامارین شروع میکنیم. طبق معمول بعد از نصب ویژوال استودیو از گزینه File گزینه New Project را انتخاب میکنیم.

    در ورژن‌های قبلی ویژوال استودیو، در زمان بارگذاری پروژه، احتیاجی به اجرای نرم افزار‌های تحریم گذر نبود؛ همانند ورژن 15.6. ولی در این ورژن که من نصب کردم بدلیل نصب خودکار کتابخانه‌های متریال دیزاین، باید از این گونه نرم افزار‌ها نیز استفاده کرد.

    درقسمت بعدی گزینه BlankApp را انتخاب و در قسمت Minimum Android Version که با انتخاب آن میتوانیم ورژن گوشی‌های اندروید برای استفاده از این اپلیکیشن را انتخاب نماییم. به عنوان مثال با انتخاب اندروید 4.4 برنامه ما صرفا برای گوشی‌های اندورید 4.4 به بالا جواب میدهد. بعد از تایید، پروژه باز شده که با این solution روبرو میشویم.

    - قسمت Properties را اگر بازکنیم، با دو گزینه روبرو میشویم که یکی فایل android manifest هست و اگر روی properties آن کلیک و ویژگی‌هایی را انتخاب کنیم، بطور خودکار بر روی manifest تاثیر میگذارند. در قسمت‌های بعد در این رابطه جداگانه بحث خواهیم کرد.

    - در قسمت Asset که به معنای منابع اندروید می‌باشد، به عنوان مثال صفحات Razor، فونت و یا صفحات HTML و یا عکس و یا ... را می‌توانیم قرار دهیم.

    - در قسمت Resource که پوشه‌های آن layout ،mipmap ،values و resource.designer میباشند، در پوشه layout می‌توانیم صفحات استاندارد اندروید را شروع به طراحی کنیم. درقسمت mipmap عکس‌ها و یا فایل‌های xml ایی را که قرار است استفاده کنیم، در پروژه قرار میدهیم. در قسمت value که بیشتر برای انتخاب و تغییر تم یا استفاده از Resource‌ها (همانند Asp.mvc که استفاده میکردیم) است که البته با ساختاری متفاوت در اندروید از آن‌ها استفاده میکنیم، قرار میگیرند.

    - در قسمت Resource.Designer که در مطالب بعد با آن آشنا خواهید شد، تمامی آیتم‌های انتخابی از جمله Layout ها  و عکس‌ها و... با ذخیره کردن در این قسمت دخیره میشوند که بعد با رفرنس دادن از طریق resource پروژه میتوانیم از عکس‌ها و لی‌آوت‌ها در کد نویسی استفاده کنیم.

    - در انتها جهت معرفی به mainactivity می‌رسیم که یک صفحه است شامل المنت‌ها و اجزای مختلف و کاربر میتواند با آن ارتباط برقرار کند. 

      [Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)]
        public class MainActivity : AppCompatActivity
        {
            protected override void OnCreate(Bundle savedInstanceState)
            {
                base.OnCreate(savedInstanceState);
                // Set our view from the "main" layout resource
                SetContentView(Resource.Layout.activity_main);
            }
        }  
      همانطور که مشاهده میکنید اکتیویتی ما از AppCompatactivity ارث بری کرده، همانند ارث بری که در mvc از controller‌ها داشتیم. در قسمت Attribute که به نام اکتیویتی تعریف شده است، برچسب یا همان Lable را که در زمان اجرا به نام اکتیویتی می‌دهد، مشاهده می‌کنید. در قسمت تم، میتوان تمی را که برای آن از قبل نوشته شده‌است و به یک اکتیویتی اختصاص داد، قرار داد. در ضمن این نکته را یاد آوری کنم زمانیکه اکتیویتی ما از Appcompatactivity ارث بری میکند، انتخاب تم اجباری میباشد.

    - در گزینه بعدی Mainluncher را میبینیم که تعیین کننده‌ی نقطه شروع اکتیویتی ما در بین اکتیویتی‌های دیگر می‌باشد.


    بدیهی است درایور‌های مربوطه به گوشی اندروید را باید تهیه کرد که در سایت مربوط به سازنده و یا در سایت‌های دیگر میتوانید دانلود کنید.  اولین برنامه را می‌نویسیم که هدف از آن، اجرای 10 دکمه بصورت داینامیک هست و اینکه با کلیک بر روی هر کدام از دکمه‌ها، رنگ آن آبی شود.

      protected override void OnCreate(Bundle savedInstanceState)
            {
                base.OnCreate(savedInstanceState);
                LinearLayout ln;
                Button btn;
                // Set our view from the "main" layout resource
                SetContentView(Resource.Layout.activity_main);
                for (int i = 0; i < 5; i++)
                {
                 btn = new Button(this);
                 btn.Text = i.ToString();
                 ln= FindViewById<LinearLayout>(Resource.Id.linearLayout1);
                 btn.Click += Btn_Click;
                 ln.AddView(btn);
                }
            }
            private void Btn_Click(object sender, System.EventArgs e)
            {
                Button btntest = sender as Button; 
                btntest.SetBackgroundColor(Android.Graphics.Color.Blue);
            }
        }
    در قسمت تعریف دکمه منظور از This این می‌باشد که این دکمه برای اکتیویتی جاری است و Findview by id که می‌بینید، من در قسمت لی‌آوت activity main، یک LinearLayout و یک Id را قرار داده ام که البته id باید منحصر بفرد باشد و با findviewbyid آی‌دی linearlayout را که قرار داده‌ام، پیدا و استفاده کردم که کد‌های آن را در زیر میتوانید مشاهده کنید.
    <?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:minWidth="25px"
        android:minHeight="25px"><LinearLayout
            android:orientation="vertical"
            android:minWidth="25px"
            android:minHeight="25px"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/linearLayout1" /></RelativeLayout>
    اکنون اولین برنامه را می‌تونید تست و اجرا کنید: AppTrainng-1.zip

    ‫xamarin.android قسمت دوم

    $
    0
    0
    در بحث گذشتهکنترل‌های مورد نظر را بصورت داینامیک تولید کردیم که در طراحی Appهای پیچیده مناسب نمی‌باشد و بهتر است فرم و طراحی گرافیکی را از قبل آماده کرده و در activity اجرا نماییم. به فرم‌های از قبل طراحی شده، layout گفته میشود. layout‌ها با فرمت xml ساخته می‌شوندو بنابراین به زبان سی شارپ مربوط نمی‌باشد.
       
    در زامارین 2 نوع layout داریم
    1: صفحات razor از قبل پردازش شده PreProcessRazorPaged
    2: layout استاندارد اندروید با ساختار xml که در زامارین ساخته میشود. در اینجا یک طراح جهت طراحی گرافیکی اندروید نصب میشود که خروجی را به xml تبدیل میکند.
      
    محل استاندارد طراحی layout
    در دایرکتوری resource بر روی دایرکتوری layout راست کلیک نموده و add new item و سپس android layout را انتخاب می‌کنیم که در این حالت یک فایل با پسوند xml اضافه میشود. با انتخاب layout و زدن دکمه f4، پنجره properties باز شده و میتوانیم خصوصیات layout را تغییر دهیم.
    پس از ایجاد layout که دستورات غیر اجرایی (مرده) می‌باشد، بایستی آن‌ها به کلاس‌های کنترل معادل خود در اندروید تبدیل شوند که به این عملیات inflating و یا inflation نیز گفته میشود. پس از عملیات inflating می‌توان کنترل‌ها را پیدا کرده و آنها را برنامه نویسی کنیم.

    FindViewbyid در پارامتر ورودی خود از طریق Resource.id، نام کنترل را دریافت نموده و بصورت Object باز می‌گرداند که بایستی نتیجه خروجی را به کلاس همان کنترل Cast نماییم:
    Button btn = FindViewById<Button>(Resource.Id.button1);
    در اینجا جهت ساده شدن دستور Find، از ساختار Generic استفاده می‌شود. در این روش پس از دستور FindviewById، نوع کنترل را مشخص می‌نماییم و نتیجه خروجی را در متغیری از نوع var ذخیره نماییم که بطور اتوماتیک در سی شارپ نوع var به نوع آن شیء تبدیل میشود.
    EditText همان Textbox خودمان می‌باشد. در Toolbox کنترلی به نام PlainText یک TextBox را به layout اضافه میکند؛ ولی در Activity یا همان برنامه نویسی، نام کلاس اصلی Textbox جهت برنامه نویسی، EditText می‌باشد:
    FindViewById<EditText>(Resource.Id.txtname).Text = "";
    که برای دسترسی مستقیم به Text و مقدار دهی به Property و یا Event‌ها، اینطور هم استفاده میشود.
      
    ساخت برنامه‌های چندین فرمی

    هر layout یک اکتیویتی مربوط به خودش را دارد. اگر بخواهیم بین فرم‌های مختلف حرکت کنیم، به ازای هر فرم، یک Activity کنترل کننده همان فرم را اضافه می‌کنیم. در یک Activity با ارسال سیگنالی به نام Intent که پارامتر اول آن، Activity مبدا یا This و پارامتر دوم آن، Activity مقصد می‌باشد. سپس به سیستم عامل اطلاع میدهیم که می‌خواهیم این سیگنال را ارسال کنیم و ارسال آن با StartActivity می‌باشد:
    Intent _intent = new Intent(this, typeof(Activity2));
    StartActivity(_intent);


    ارسال پارامتر

    جهت ارسال داده‌های معمولی bool,double,string,float,long,... و آرایه ای از آن، از دستور PutExtra استفاده می‌شود:
    Intent _intent = new Intent(this, typeof(Activity2));
    string Test="Vlaueone";
    
    _intent.PutExtra("mainactivity", Test);
    StartActivity(_intent);



    گرفتن پارامتر

    در Activity مقصد، اطلاعات مربوط به سیگنال ارسال شده، در مقادیر Global intent ذخیره می‌شود و بر اساس نوع داده ارسال شده، تابع GetExtra را می‌نویسیم:
    string Getintent = Intent.GetStringExtra("mainactivity");



    ارسال object از کلاس‌ها بین Activityها


    در زامارین، object کلاس‌ها را به یک string استاندارد بصورت json تبدیل می‌کنیم و به این عملیات Serialization گفته میشود. این کار نیز توسط کتابخانه‌های مختلفی انجام می‌شود مانند NewtonSoft. سپس رشته json ایجاد شده را با تابع PutExtra، به همراه سیگنال Intent، به Activity دوم پاس میدهیم:
    List<Product> products;
    products = new List<Product>();
    Product p = new Product
                {
    
                    name = FindViewById<EditText>(Resource.Id.mainedittextname).Text,
                    price = Convert.ToInt32(FindViewById<EditText>(Resource.Id.mainedittextprice).Text)
                };
    products.Add(p);
    Intent _intent = new Intent(this, typeof(Activity2));
    _intent.PutExtra("products", JsonConvert.SerializeObject(products));
    StartActivity(_intent);
    در Activity دوم آن‌را توسط کتابخانه‌ی NewtonSoft، به کلاس اصلی DeSerialize میکنیم که عملیات آن با دستورات ذیل انجام  میشود:
    var p = JsonConvert.DeserializeObject<List<Product>>(Intent.GetStringExtra("products"));
    foreach (var item in p)
    {
       Toast.MakeText(this, $" {item.name} , {item.price}",
                      ToastLength.Long).Show();
    };
    در کد بالا Toast را در حلقه می‌بینید که در واقع همانند alert در وب و یا همانند MessageBox در ویندوز فرم یا Wpf عمل میکند. در پارامتر اول به آن میگوییم که برای این اکتیویتی است. پارامتر دوم آن جهت نمایش مقدار و در پارامتر سوم، مدت نمایش طولانی برای آن در نظر گرفته شده است و با جایگزینی Long با Short، زمان نمایش آن کوتاه‌تر میشود و در آخر با show هم آن را نمایش میدهد.

    در ادامه کلاس‌های مورد نظر را جهت تعریف در دایرکتوری Model ایجاد میکنیم. مثل کلاس Product که بصورت Public نیز می‌باشد. از منوی Tools -> Nuget Package Management -> Manage nuget Package for Solution  را انتخاب و سپس NewtonSoft را اضافه می‌کنیم.
    از متد Finish برای اتمام کار و یا پایان Activity نیز استفاده میشود.
     
    AleartDialog

    AlertDialog زیر کلاس Dialog است که می‌تواند یک، دو و یا سه دکمه را نمایش دهد. اگر فقط می‌خواهید یک رشته را در این کادر محاوره ای نمایش دهید، از روش SetMessage استفاده کنید. قطعه کد زیر می‌تواند برای ایجاد یک AlertDialog ساده با دو دکمه حذف و لغو استفاده شود: 
    //set alert for executing the task
    AlertDialog.Builder alert = new AlertDialog.Builder(this);
    alert.SetTitle("Confirm delete");
    alert.SetMessage("Lorem ipsum dolor sit amet, consectetuer adipiscing elit.");
    alert.SetPositiveButton("Delete", (senderAlert, args) =>
                {
                    Toast.MakeText(this, "Deleted!", ToastLength.Short).Show();
                });
    
    alert.SetNegativeButton("Cancel", (senderAlert, args) =>
                {
                    Toast.MakeText(this, "Cancelled!", ToastLength.Short).Show();
                });
    
    Dialog dialog = alert.Create();
    dialog.Show();
     
    DialogFragment
    AleartDialog فقط میتواند اطلاعات محدودی را نمایش دهد؛ مانند نمایش حداکثر یک یا دو Button,EditText به کاربر. در حالیکه در برنامه نیاز به دیالوگی با ظاهر سفارشی داریم تا بتوانیم ظاهر آن را تغییر دهیم.

    Fragment

    با استفاده از Fragment میتوانیم layout هایی از پیش طراحی شده را بسازیم و چند صفحه را بصورت layout با Activity پیاده سازی کنیم. در اینجا اگر layout دوم load بشود، در این حالت اگر نیاز به ورود داده‌های مورد نیاز از طریق layout اول یا اصلی را داشته باشد، برای کاربر خسته کننده می‌‌شود. از این رو بهتر است layout دوم کوچکی بر روی layout اول یا اصلی نمایش داده شود. از این رو میتوان بجای layout از DialogFragment استفاده کنیم.
    کلاس  DialogFragment میتواند دیالوگی را در وسط صفحه نمایش دهد ولی view و ظاهر آن از یک layout از قبل ساخته شده load و نمایش داده میشود. این کار از طریق تکنیک Inflate انجام میشود.
    در ادامه یک کلاس را از Dialog Fragment مشتق کرده و در تابع oncreate، ظاهر و یا layout از پیش طراحی شده را بصورت ذیل در حافظه load کرده و بعنوان نتیجه خروجی باز می‌گردانیم:
    public class DialogFragmentActivity : DialogFragment
    {
            public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
            {
                var dlg = inflater.Inflate(Resource.Layout.layoutDialog, container, false);
                return dlg;
            }
    }
    و در اکتیویتی اصلی برای فراخوانی کلاس فرگمنت از کد زیر استفاده میشود:
    DialogFragmentActivity dlg = new DialogFragmentActivity ();
    dlg.Show(this.FragmentManager, "Fragment");
    پارامتر اول در واقع بعنوان fragment manager معاون اکتیویتی می‌باشد و در پارامتر دوم هم یک اسم دلخواه را برای آن انتخاب میکنیم. در متد باز نویسی شده On start نیز میتوان طول و عرض آن و رنگ پشت زمینه Fragment را تغییر داد که کد‌های آن را در زیر میتوانید مشاهده کنید:
    public override void OnStart()
    {
                base.OnStart();
                Dialog dialog = Dialog;
                if (dialog != null)
                {
                    dialog.Window.SetLayout(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
                    dialog.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent));
                }
    }
    اگر برنامه را اجرا و فرگمنت را نیز اجرا کنید، می‌بینید که یک کادر، بصورت Header در بالای فرگمنت نمایان شده‌است که زیاد جالب نیست. برای حذف Header فرگمنت، از کد ذیل میتوانید استفاده کنید:
    public override Dialog OnCreateDialog(Bundle savedInstanceState)
    {
                Dialog NotTitle = base.OnCreateDialog(savedInstanceState);
                NotTitle.Window.RequestFeature(WindowFeatures.NoTitle);
    
                return NotTitle;
    }

    ‫xamarin.android قسمت سوم

    $
    0
    0
    Theme
    برای اینکه بتوانیم ظاهر گرافیکی layout‌ها را کنترل نماییم، از Theme که مجموعه‌ای از styleهای گرافیکی می‌باشد، استفاده می‌کنیم. در اندروید مجموعه‌ای از تم‌های از پیش ساخته شده که به آنها Builtin Theme نیز گفته می‌شود می‌توانیم استفاده کنیم. تم‌ها ظاهر گرافیکی کلیه کنترل‌های Layout را با نام‌های زیر، کنترل می‌کنند:
    statusBarColor
    textColorPrimary
    colorAccent
    ColorPrimary
    WindowBackground

    اگر ساختار زیر را در یک صفحه استاندارد برنامه‌های موبایل را در نظر بگیریم، styleها هر بخش، یک نام منحصر به فرد دارد:



    اگر بخواهیم از style‌های از پیش طراحی شده‌ی اندروید استفاده نماییم، ابتدا میتوانیم در صفحه‌ی layout در زامارین، style مربوطه را از بخش Theme استفاده کرده و نتیجه را مشاهده کنیم. ولی تغییر style سبب تغییر layout در زمان اجرا نمی‌شود. هرگاه بخواهیم از styleهای استاندارد یا builtin اندروید استفاده نماییم، در Activity توسط خصوصیت Theme با فرمت:
    [Activity(Theme = "@style/NameThem")]
    تم را به‌عنوان تم داخلی وسپس نام کامل تم را می‌نویسیم.
     
    CustomTheme
    در طراحی فرم‌ها ممکن است بخواهیم از یک استایل خاص builtin استفاده کنیم؛ ولی ممکن است بعضی از استایل‌های آن را استفاده نکنیم، مانند تمی که از قبل استفاده شده‌است، از روش زیر استفاده می‌کنیم:
    - بر روی دایرکتوری value راست کلیک میکنیم. گزینه add new item را انتخاب و یک فایل xmlfile را با نام style ایجاد میکنیم.
    - style‌های جدید منابع application می‌باشند که در بخش resource از آن‌ها استفاده میکنیم. هر استایل جدید را توسط Style Tag مشخص میکنیم و در خصوصیت Name، نام Style را مشخص میکنیم.
    ممکن است در یک Style نتوانیم و یا نخواهیم تمامی Style‌های مورد نیاز را تامین کنیم. از این رو توسط Parent، یک StyleBuition تعریف نموده که این Styleها از آن مشتق می‌شوند. اگر در Theme جدید گزینه‌ها مشخص شوند، تم اصلی تغییر نمی‌کند. در غیر اینصورت تمامی گزینه‌های تعریف شده در تم جدید از ThemParent مقدار دهی می‌شود.
    توسط item می‌توان یک style را تعریف نمود. در Activity توسط
     [Activity(Theme = "@style/NameThem")]
    می‌توانیم استایل سفارشی شده خودمان را مشخص کنیم.

    <?xml version="1.0" encoding="utf-8" ?><resources><style name="MainTheme" parent="Theme.AppCompat.Light.DarkActionBar"><item name="windowNoTitle">true</item><item name="windowActionBar">false</item></style></resources>
     
      
    Navigation Menu
    کتابخانه متریال دیزاین کتابخانه جدیدی می‌باشد که به اندروید اضافه شده‌است. توسط  آن می‌توانیم کنترل‌های جدید را با استایل‌های جدید برای Appهای اندروید، تولید کنیم. ابتدا نیاز به نصب Component‌های ذیل در زامارین می‌باشد.
     
    استفاده که از کتابخانه متریال دیزاین
    برای اینکه بتوانیم Navigation menu را ایجاد کنیم، باید از نیوگت، کتابخانه‌های Appcompat و Designlibrary را انتخاب و نصب نماییم و اگر نگارش ویژوال استودیوی شما 15.7.3باشد، از ابتدا بصورت اتوماتیک نصب شده است و احتیاجی به این مراحل نمی‌باشد. بدیهی است در زمان نصب باید از نرم افزار‌های تحریم گذر نیز استفاده کرد.
      
    ساخت Menu
    NavigationMenu، منوی اصلی منو می‌باشد که با Swipping از گوشه راست به چپ، باز یا بسته می‌شود یا با کلیک بر روی دکمه‌ی Menu بر روی Toolbar، منو را باز یا بسته میکند.
    قدم اول: نصب منو
    منظور همان اضافه کردن کتابخانه‌ها است.
    قدم دوم:
    ابتدا باید گزینه‌های منو را در یک فایل xml تعریف نمود. هر گزینه‌ی آن از دو بخش متن اصلی منو و ID منو تشکیل شده‌است.
    بر روی دایرکتوری Resource راست کلیک کرده و یک دایرکتوری یا پوشه را به نام Menu ایجاد می‌کنیم. بر روی دایرکتوری منو، راست کلیک کرده و یک فایل Xml را به آن اضافه می‌کنیم. برای آنکه بتوانیم در این فایل دستورات ساخت منو را نوشته و به نحوی که توسط اندروید قابل خواندن و تبدیل به منو باشد، ساختار منو را از آدرس زیر ویژوال استودیو دانلود میکنیم.
    <menu xmlns:android="http://schemas.android.com/apk/res/android">

    توسط Group مجموعه گزینه‌های منو را را معرفی میکنیم.
    هر گزینه در منو ی اصلی توسط یک آیتم مشخص میشود. برای آنکه هنگام کلیک بر روی هر گزینه از طریق برنامه نویسی بتوانیم گزینه انتخاب شده را شناسایی کنیم، یک آی دی منحصر بفرد را به هر گزینه اختصاص میدهیم. زمانیکه بر روی یک گزینه کلیک میشود، توسط این ID‌ها میتوانیم شناسایی کنیم کدام گزینه انتخاب شده‌است.
    خصوصیت Android :Title متن اصلی منو را مشخص میکند.
    <?xml version="1.0" encoding="utf-8" ?><menu xmlns:android="http://schemas.android.com/apk/res/android"><group android:checkableBehavior="single"><item android:id="@+id/menuItemHome" android:title="صفحه اصلی"></item><item android:id="@+id/menuItemInsertProduct" android:title="ورود کالا جدید" ></item><item android:id="@+id/menuItemListProduct" android:title="مشاهده کالاها"></item><item android:id="@+id/menuItemExit"  android:title="خروج"></item></group></menu>

    سپس باید در Layout مورد نظر همانند صفحه Main، ساختار اصلی برنامه شامل Toolbar و Menu را بصورت زیر تعریف نماییم:
    <android.support.v7.widget.Toolbar
      android:layout_width="match_parent"
      android:id="@+id/toolbar1"
      android:background="#33B86C"
      android:minHeight="?android:attr/actionBarSize"
      android:layout_height="wrap_content"></android.support.v7.widget.Toolbar>

    ساختار منو به صورت زیر است:
    <?xml version="1.0" encoding="utf-8" ?><menu xmlns:android="http://schemas.android.com/apk/res/android"><group android:checkableBehavior="single"><item android:id="@+id/menuItemHome" android:title="صفحه اصلی"></item><item android:id="@+id/menuItemInsertProduct" android:title="ورود کالا جدید" ></item><item android:id="@+id/menuItemListProduct" android:title="مشاهده کالاها"></item><item android:id="@+id/menuItemExit"  android:title="خروج"></item></group></menu>
    در Linearlayout ریشه، گزینه Fitssystemwindow را true میکنیم که سایز linearlayout را با سایز موبایل جدید اندازه می‌کند. سپس از toolbox، کنترل Toolbarرا به پنجره اضافه میکنیم که در بالای صفحه قرار می‌گیرد.
    toolbar اضافه شده، toolbar استاندارد قبل از متریال دیزاین میباشد. در واقع toolbar اول، Toolbar استاندارد اندروید می‌باشد. برای آنکه از Toolbar متریال دیزاین استفاده کنیم، کنترل‌های متریال دیزاین در بخش supportlibrary اضافه میشود و Toolbar متریال دیزاین را اضافه میکنیم. علامت ؟ یعنی اینکه می‌خواهیم از اندازه سیستمی استفاده کنیم. اگر بخواهیم حداقل سایز Toolbarبر اساس پیش فرض در دستگاه‌های مختلف باشد، از علامت Android :attr ? استفاده میکنیم. اگر بخواهیم حداقل ارتفاع پیشنهادی اندروید در هر موبایل متصل شود، از خصوصیت Action Bar Size  استفاده میکنیم. این خصوصیت زمانی عمل میکند که Height  آن Wrapcontent باشد.
    گذاشتن دکمه منو: برای آنکه بتوانیم دکمه منو را به Toolbar اضافه کنیم، از دکمه Image Button استفاده میکنیم که یک دکمه‌ی معمولی می‌باشد ولی خلاصه‌ی آن عکس است. در خصوصیت Back ground دکمه، بصورت زیر نام فایل آیکن منو را در دایرکتوری Drawable، مشخص میکنیم و خصوصیت src آن‌را null می‌کنیم تا تصویری بجز تصویر انتخابی نباشد.
    برای آنکه بتوانیم پنجره اصلی منو را به صورتیکه دارای قابلیت حرکت به راست و چپ باشد، ایجاد کنیم، از کنترلی به‌نام  Drowerlayout استفاده میکنیم که بر روی صفحه قرار میگیرد. DrawerLayout در linearlayout ریشه قرار میگیرد و یا بعد از ToolBar و حتما باید خصوصیت fitsystemwindow کنترل Drawer را True کنیم. جهت نمایش گزینه‌های اصلی در Drawer از کنترل NavigationٰView استفاده می‌کنیم.
    گزینه‌های منو در کنترلی به نام Navigationview قرار دارد. این کنترل باید در Drawerlayout قرار گیرد. توسط فضای نام منو، محل فایل xml را که منو درون آن قرار گرفته است، مشخص می‌کنیم. آدرس این دستور در این مسیر می‌باشد:
     xmlns:app="http://schemas.android.com/apk/res-auto"
    Layout gravity  آن را end  قرار میدهیم که از سمت راست قرار بگیرد. Fit system Window را هم True میکنیم تا گزینه‌های داخل آن‌را هم Fit کند. Theme باید از نوع تم‌های متریال دیزاین و با کلمه Them . App Compact. ligth.NoActionBar باشد. برای آنکه اکتیویتی‌ها، متریال دیزاین را ساپورت کنند، میتوان از کلاس  App compact Activity  استفاده کنیم. Tool bar بصورت پیش فرض لیبل اکتیویتی را نشان می‌دهد و دستور زیر عنوان Toolbar را حذف میکند.
     SupportActionBar.SetDisplayShowTitleEnabled(false);
     
    مدیریت گزینه‌های منو
    به محض انتخاب یک گزینه درون NavigationView، رخدادی به نام NavigationItemSelected صادر می‌شود که توسط آن میتوانیم گزینه‌ی انتخاب شده را از طریق برنامه نویسی مدیریت کنیم. این کنترل در Android.Support.V7.Widget و NameSpace بالا قرار میگیرد. سپس یک رخ‌داد گردان را با نام navigationItemSelected پیاده سازی می‌کنیم. اطلاعات مربوط به گزینه‌ی انتخاب شده، در پارامتر دوم از این تابع NavigationView.NavigationItemSelectedEventArgs ذخیره می‌شود. ID، آیتم انتخاب شده در فایل Menu را باز می‌گرداند.
            var navigationview = this.FindViewById<NavigationView>(Resource.Id.navigationView1);
            navigationview.NavigationItemSelected += Navigationview_NavigationItemSelected;
            private void Navigationview_NavigationItemSelected(object sender, NavigationView.NavigationItemSelectedEventArgs e)
            {
                Intent intent = null;
                switch (e.MenuItem.ItemId)
                {
                    case Resource.Id.menuItemHome:
    
                        break;
                    case Resource.Id.menuItemExit:
                        Finish();
                        break;
                    case Resource.Id.menuItemInsertProduct:
    
                        break;
                    case Resource.Id.menuItemListProduct:
    
                        break;
                }
            }
     
    مدیریت اکتیویتی‌ها توسط Menu
    با انتخاب گزینه Menu باید اکتیویتی مربوطه انتخاب شود. بنابراین برای هر گزینه‌ی منو یک Layout و اکتیویتی را ایجاد می‌کنیم و اجرا میکنیم. ولی در اکتیویتی جدید Toolbar وجود ندارد.
     
    تکنیک ادغام:
    برای آنکه در Layoutهای مختلف، تولبار و منو و یا هر View دیگری را بصورت مشترک استفاده کنیم، یک فایل xml را به دایرکتوری Layout اضافه می‌کنیم. دستور Merge میتواند تمام layoutها را به درون layoutهای دیگر مانند home,insert ادغام و یا تزریق کند. جهت استفاده از Merge در layoutهای دیگر نیاز به Id منحصر به فرد می‌باشد.
    <?xml version="1.0" encoding="utf-8" ?><merge xmlns:android="http://schemas.android.com/apk/res/android" 
        xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/toolbarlayout"><android.support.v7.widget.Toolbar android:layout_width="match_parent" android:id="@+id/toolbar1" android:background="#33B86C" android:minHeight="?android:attr/actionBarSize" android:layout_height="wrap_content"><ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/imageButton1" android:background="@drawable/mainmenu" android:layout_gravity="end" /></android.support.v7.widget.Toolbar><android.support.v4.widget.DrawerLayout android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/drawerLayout1" android:fitsSystemWindows="true"><android.support.design.widget.NavigationView android:minWidth="25px" android:minHeight="25px" android:layout_width="200dp" android:layout_height="match_parent" android:layout_gravity="end" app:menu="@menu/menu" android:id="@+id/navigationView1" android:fitsSystemWindows="true" /></android.support.v4.widget.DrawerLayout></merge>

    در اکتیویتی‌های دیگر باید Toolbar و مدیریت گزینه‌های منو با کد‌های مشابه Main انجام شود.
            private void Navigationview_NavigationItemSelected(object sender, NavigationView.NavigationItemSelectedEventArgs e)
            {
                Intent intent = null;
                switch (e.MenuItem.ItemId)
                {
                    case Resource.Id.menuItemHome:
                        intent = new Intent(this, typeof(MainActivity));
                        break;
                    case Resource.Id.menuItemExit:
                        Finish();
                        break;
                    case Resource.Id.menuItemInsertProduct:
                        intent = new Intent(this, typeof(InsertActivity));
                        break;
                    case Resource.Id.menuItemListProduct:
                        intent = new Intent(this, typeof(ListProductsActivity));
                        break;
                }
                if (intent != null) { }
            }
    بنابراین دستورات xmlTollbar  و darawer layout در تمامی Layoutها و دستورات سی شارپ، کنترل کننده Toolbar و منو در تمامی اکتیویتی‌ها تکرار شده‌اند.
     
    حل مشکلات Layout
    یک فایل Xml را به Layout  اضافه می‌کنیم و درون آن Tag merge و کد‌های مشترک Drawer out و Toolbar را داخل تگ Merge اضافه می‌کنیم. جهت استفاده از کدهای (مقدار فایل ایکس ام ال ساخته شده که Tag merge داخل آن است)  Merge، در layout های دیگر، از دستور Include  استفاده می‌کنیم.
    نام لی‌آوت را در خصوصیت Layout اضافه می‌کنیم. برای آنکه کد‌های سی شارپ کنترل کننده‌ی Toolbar و Menu چندین Toolbar وجود دارد که در یکی از آن‌ها یک کلاس واسط از کلاس app compat Activity  را به ارث میبریم. تابع Protected را از آن بازنویسی کرده و تمام کد‌های مدیریت Toolbar و منو را در آن پیاده سازی می‌کنیم. تمام اکتیویتی‌های برنامه را از این کلاس به ارث می‌بریم. بنابراین تابع InitieToolbar به تمامی فرزندان نیز به ارث برده می‌شود. در زمان اجرای دستورات، this ، اکتیویتی جاری می‌باشد.
        public class BaseAcitivity : AppCompatActivity
        {
            protected void InitieToolbar()
            {
                var toolbar = this.FindViewById<widgetV7.Toolbar>(Resource.Id.toolbar1);
                this.SetSupportActionBar(toolbar);
                //SupportActionBar.SetDisplayShowTitleEnabled(false);
                var imagebutton = toolbar.FindViewById<ImageButton>(Resource.Id.imageButton1);
                imagebutton.Click += Imagebutton_Click;
                var navigationview = this.FindViewById<NavigationView>(Resource.Id.navigationView1);
                navigationview.NavigationItemSelected += Navigationview_NavigationItemSelected;
            }
    
            private void Navigationview_NavigationItemSelected(object sender, NavigationView.NavigationItemSelectedEventArgs e)
            {
                Intent intent = null;
                switch (e.MenuItem.ItemId)
                {
                    case Resource.Id.menuItemHome:
                        intent = new Intent(this, typeof(MainActivity));
                        break;
                    case Resource.Id.menuItemExit:
                        Finish();
                        break;
                    case Resource.Id.menuItemInsertProduct:
                        intent = new Intent(this, typeof(InsertActivity));
                        break;
                    case Resource.Id.menuItemListProduct:
                        intent = new Intent(this, typeof(ListProductsActivity));
                        break;
                }
                if (intent != null)
                    StartActivity(intent);
            }
    
            private void Imagebutton_Click(object sender, EventArgs e)
            {
                var drawerlayout = this.FindViewById<DrawerLayout>(Resource.Id.drawerLayout1);
                if (drawerlayout.IsDrawerOpen(Android.Support.V4.View.GravityCompat.End) == false)
                {
                    drawerlayout.OpenDrawer(Android.Support.V4.View.GravityCompat.End);
                }
                else
                {
                    drawerlayout.CloseDrawer(Android.Support.V4.View.GravityCompat.End);
                }
            }
        }
     
    اگر بخواهیم یک تم در تمامی اکتیویتی‌ها  به صورت سراسری استفاده شود، از فایل تنظمیات اندروید بنام AndroidManifest در دایرکتوری Properties استفاده می‌کنیم و در  بخش Application Theme، نام تم را مشخص میکنیم:
     android:theme="@style/Theme.AppCompat.Light.NoActionBar"
     

    ساخت TabPage
    پیشنیاز: نصب کتابخانه‌های متریال دیزاین همانند قبل و طبق ورژن Sdk نصب شده
    اگر بخواهیم چندین صفحه را بر روی یکدیگر Stack و یا Overload نماییم، از Tabpage استفاده می‌کنیم. صفحاتی‌که از TabPage استفاده می‌کنند، با انگشت جابجا میشوند و همانند برنامه‌ی واتساپ Fragment می‌باشند و هر Fragment دارای layout و اکتیویتی مربوط به خود می‌باشد. معماری layout آن بصورت زیر است:


    ToolBar، در بالای فرم قرار می‌گیرد. TabLayou که بصورت TabPage آن‌ها را به عهده دارد. Viewpager مدیریت Layout‌ها را به هنگام Swipe یا جابجایی به عهده دارد.
    یک layout را برای Toolbar قرار می‌دهیم. سپس Layout اصلی main را طراحی میکنیم. پس از اضافه کردن ToolBar، ابزار TabLayout را در بخش SupportLibrary متریال دیزاین انتخاب و در صفحه می‌کشیم. TabLayout در پایین Toolbar قرار می‌گیرد و با انتخاب رنگ یکسان برای هر دو، متصل و یکنواخت به نظر می‌رسد. سپس از Layout از Toolbar آیتم ViewPager را بر روی صفحه قرار می‌دهیم. اگر LayoutWeight آن را یک قرار دهیم، تمام ارتفاع صفحه را به ما تخصیص می‌دهد. زمانیکه در TabLayout تب‌ها جابجا می‌شوند یا بر روی یک آیکن کلیک می‌شود، صفحه مربوطه در بخش ViewPager به کاربر نمایش داده میشود. هر Page یک فرگمنت می‌باشد. به ازای هر فرگمنت یک Layout به دایرکتوری layout اضافه کرده و به ازای هر layoutFragment یک Activity Fragment را اضافه می‌کنیم. یک اکتیویتی از نوع را Android.Support.v4.AppFragment ایجاد میکنیم.
        public class Fragment1 : Android.Support.V4.App.Fragment
        {
            public override void OnCreate(Bundle savedInstanceState)
            {
                base.OnCreate(savedInstanceState);
            }
            public override View OnCreateView(LayoutInflater inflater,
          ViewGroup container, Bundle savedInstanceState)
            {
                return inflater.Inflate(Resource.Layout.FragmentLayout1, container, false);
            }
        }
    ابتدا باید viewpager در Layout اصلی را پیدا کرده و با دستور زیر به Tablayout متصل کنیم:
     var tablayout = FindViewById<Android.Support.Design.Widget.TabLayout>(Resource.Id.tabLayout1);
    var viewpager = FindViewById<ViewPager>(Resource.Id.viewPager1);
    tablayout.SetupWithViewPager(viewpager);
    زمانیکه آیکن را در TabLayout انتخاب میکنیم یا با انگشت Swipe میکنیم، به ترتیب بین صفحات که از Position صفحه آغاز شده‌اند، حرکت می‌کنیم، باید فرگمنت همان Position را نشان دهیم و این مدیریت توسط بخشی به‌نام Adapter انجام میشود. یک Adapter را به کنترلر اضافه میکنیم و از کلاس Fragment pager adapter به ارث می‌بریم. بر روی کلاس Fragment pager adapter ، دکمه‌های کنترل و نقطه را میزنیم و سپس کلاس را پیاده سازی می‌کنیم. در این حالت دو تابع را به ما می‌دهد: تابع Get item .count مجددا بر روی کلاس پدر راست کلیک میکنیم. در تابع کانت تعداد کل صفحه‌ها را (Layout ها) را انتخاب میکنیم. هرگاه از یک صفحه به صفحه‌ی دیگری انتقال پیدا کنیم، موقعیت صفحه جدیدی که از یک شروع میشود را به تابع Get Item بر اساس موقعیت Object  از fragment مربوطه new کرده و بعنوان خروجی باز می‌گرداند.
        class TabFragmentAdapter : FragmentPagerAdapter
        {
            public TabFragmentAdapter(FragmentManager fm) : base(fm)
            {
            }
            public override int Count => 3;
            public override Fragment GetItem(int position)
            {
                switch (position)
                {
                    case 0: return new Fragment1();
                    case 1: return new Fragment2();
                    case 2: return new Fragment3();
                    default: return new Fragment1();
                }
            }
    
    
            //int f1() { return 100; }
            //int f1 => 100;
        }
    و در اکتیویتی اصلی، کد زیر را برای Load فرگمنت‌ها نیز قرار می‌دهیم:
     viewpager.Adapter = new TabFragmentAdapter(this.SupportFragmentManager);
      
    آیکن برای TabPage
    سپس اگر بخواهیم آیکن‌های Tab را به ترتیب تعریف کنیم، از تابع Gettabat استفاده میکنیم. پارامتر ورودی آن موقعیت Tab page میباشد و Set icon هم آیکن‌های دایرکتوری Drawable را انتخاب میکند.
     tablayout.GetTabAt(0).SetIcon(Resource.Drawable.iconCall);
     

    نمایش متن همراه با عکس
     اگر بخواهیم آیکن‌های تب پیج را سفارشی کنیم، از Layout استفاده میکنیم که عرض و ارتفاع آن wrap Content  باشند و درون آن یک Text view که معادل Lable میباشند، قرار میدهند:
     View iconlayout1 = LayoutInflater.Inflate(Resource.Layout.custom_TabIconLayout, null);
    var txt = iconlayout1.FindViewById<TextView>(Resource.Id.tabTextIcon);
    txt.Text = "تماس";
    txt.SetCompoundDrawablesWithIntrinsicBounds(Resource.Drawable.iconCall, 0, 0, 0);
    tablayout.GetTabAt(0).SetCustomView(iconlayout1);

    کدهای مطلب جاری برای دریافت: Navigation-TabPage-samples.zip

    ‫تولید اطلاعات تصادفی توسط GenFu

    $
    0
    0
    گاها برای تولید اطلاعات تصادفی، خصوصا هنگام نوشتن تست‌ها، زمان زیادی بیهوده تلف شده و حجم زیادی کد اضافه تولید میشود. کتابخانه‌ای بنام GenFu ایجاد شده که وظیفه ایجاد داده‌های تصادفی را بر عهده گرفته‌است. این کتابخانه متن باز (Open Source) بوده و می‌توانید آن را از مخزن گیت‌هاب دریافت نمایید.

    در مطلب جاری قصد ایجاد اطلاعات تصادفی برای کلاس زیر را داریم :
    public class Person
    {
        public int ID { get; set; }
    
        public string Firstname { get; set; }
    
        public string Lastname { get; set; }
    
        public string Email { get; set; }
    
        public string PhoneNumber { get; set; }
    
        public override string ToString()
        {
            return $"{ID}: {Firstname} {Lastname} - {Email} - {PhoneNumber}";
        }
    }

    نصب GenFu

    برای نصب کتابخانه GenFu از دستور زیر در Package Manager Console استفاده میکنیم :
    Install-Package GenFu

    1. ایجاد یک شخص

    برای ایجاد شخصی جدید همراه با اطلاعاتی تصادفی به شکل زیر عمل خواهیم کرد :
    var person = A.New<Person>();
    Console.WriteLine(person);
    نتیجه کد فوق به این صورت خواهد بود :
    18: Diedra Morgan - Zachary.Garcia@telus.net - (531) 273-9001
    اگر دقت کنید متوجه میشوید که GenFu بصورت خودکار داده‌هایی مرتبط با Property هایی که نام گذاری کردید‌، ایجاد کرده‌است.
    برای Email، داده‌ای با فرمت صحیح ایمیل و برای PhoneNumber هم شماره تلفنی با فرمت صحیح تولید شده است.

    2. ایجاد چند شخص

    برای ایجاد لیستی از اشخاص نیز میتوانید از متد ListOf استفاده کرده و تعداد اشخاص مورد نیازتان را به آن ارسال کنید ( پیشفرض 25 ) :
    var people = A.ListOf<Person>(5);
    people.ForEach(Console.WriteLine);
    کد فوق باعث ایجاد 5 شخص با اطلاعات تصادفی متفاوتی خواهد شد:
    97: Maria MacKenzie - Alexandra.Johnson@rogers.ca - (670) 787-3053
    34: Alexander Scott - Isaiah.Price@gmail.com - (730) 645-4946
    66: Kevin Perez - Gabrielle.Alexander@hotmail.com - (230) 758-8233
    81: Maria Evans - Vanessa.Bell@rogers.ca - (508) 572-4343
    79: Tyler Parker - Alyssa.Taylor@telus.net - (297) 357-7617

    تا به اینجای کار GenFu بخوبی جوابگوی نیازهایمان بوده‌است. اما اگر پیشفرض‌ها جوابگو نبود و بخواهیم فرمت داده‌های تولید شده را تغییر دهیم چطور ؟
    برای اینکار میتوانیم از متد Configure استفاده کرده و نحوه ایجاد داده را برای Property هایی که مشخص میکنیم، تغییر دهیم.

    3. ایجاد چند شخص و مقدارهی یک property با مقدار ثابت

    اگر بخواهیم داده‌های ایجاد شده را داخل دیتابیس لحاظ کنیم، نیاز داریم تا ID آن‌ها را برابر 0 قرار دهیم تا دیتابیس مشکلی برای ثبتشان نداشته باشد. برای ایجاد لیستی از اشخاص که ID آن‌ها برابر 0 باشد :
    A.Configure<Person>().Fill(x => x.ID, 0);
    
    var people = A.ListOf<Person>(5);
    people.ForEach(Console.WriteLine);
    نتیجه :
    0: Darron Gonzalez - Benjamin.Daeninck@hotmail.com - (405) 418-7783
    0: Melanie Garcia - Jennifer.Griffin@microsoft.com - (711) 277-8826
    0: James Hughes - Tristan.Ward@live.com - (734) 400-8322
    0: Miranda Torres - Ross.Davis@rogers.ca - (495) 479-8147
    0: David Hughes - Jillian.Alexander@live.com - (361) 617-6642
    در این حالت بدون هیچگونه مشکلی میتوانید داده‌های ایجاد شده را داخل دیتابیس ذخیره نمایید.

    4. ایجاد چند شخص و مقداردهی یک property با متد

    حالت دیگری که میتوانید نحوه مقداردهی یک Property را تنظیم کنید، استفاده از متد یا delegate است:
    var i = 1;
    
    A.Configure<Person>()
        .Fill(c => c.ID, () => i++);
    
    var people = A.ListOf<Person>(5);
    people.ForEach(Console.WriteLine);

    نتیجه :
    1: Paul Long - Carlos.Kelly@telus.net - (202) 573-6278
    2: Jesse Iginla - Liberty.Moore@gmail.com - (589) 791-3606
    3: Raymundo Price - Ang.Taylor@live.com - (336) 400-1601
    4: Elizabeth Getzlaff - Leslie.Campbell@att.com - (662) 582-9010
    5: Abigail Bailey - Tristan.Ross@live.com - (225) 661-7023
    همانطور که می‌بینید، ID اشخاص بصورت تصاعدی مقداردهی شده است.

    5. ایجاد چند شخص و مقداردهی یک property با مقادیر property‌های دیگر

    همچنین میتوانید از مقادیر Property‌های دیگر برای مقداردهی یک Property استفاده کنید :
    A.Configure<Person>()
        .Fill(c => c.ID, 0)
        .Fill(c => c.Email,
            c => $"{c.Firstname}.{c.Lastname}@gmail.com");
    
    var people = A.ListOf<Person>(5);
    people.ForEach(Console.WriteLine);
    کد فوق باعث تولید اشخاصی میشود که ایمیل آن‌ها برابر (Firstname).(Lastname) خواهد بود :
    0: Patrick Perry - Patrick.Perry@gmail.com - (796) 460-6576
    0: Rebecca Main - Rebecca.Main@gmail.com - (757) 472-3332
    0: Kimberly Carter - Kimberly.Carter@gmail.com - (436) 484-8273
    0: Sara Lewis - Sara.Lewis@gmail.com - (424) 717-7682
    0: Lauren Ross - Lauren.Ross@gmail.com - (277) 294-5776

    6. استفاده از Extension‌های درون ساخت GenFu برای مقداردهی

    GenFu دارای Extension هایی بوده که باعث میشوند اطلاعات یک Property با مقادیر قابل درک و مشخصی پر شوند.
    مثال :
    A.Configure<Person>()
        .Fill(x => x.Firstname).AsPersonTitle();
    
    var people = A.ListOf<Person>(5);
    people.ForEach(Console.WriteLine);  
    نتیجه :
    64: Miss. Ratzlaff - Bryce.Simmons@att.com - (386) 309-2414
    7: Air Marshall Yarobi - Ariana.Russell@att.com - (459) 238-0717
    96: Air Marshall Taylor - Luke.Olsen@gmail.com - (775) 401-5281
    28: Doctor Cox - Leah.Diaz@att.com - (569) 464-7961
    99: Master Phillips - Chloe.Scott@hotmail.com - (578) 221-9021

    7. GenFu WireFrame

    در نهایت GenFu دارای پکیج جانبی به اسم Wireframes است که شامل HTML Helper هایی است که با استفاده از آن‌ها میتوانید المان‌های HTML مانند P, Image, Table و ... را با مقادیری برای تست بعنوان Placeholder ایجاد کنید. 
    برای نصب و مطالعه بیشتر درباره GenFu WireFrames این لینک را ببینید.

    ‫اجزاء معماری سیستم عامل اندروید (قسمت دوم رمزنگاری اندروید) :: بخش ششم

    $
    0
    0
    ذخیره داده‌ها در اندروید
    اندروید برنامه‌های کاربردی را در زمینه‌ی (context) امنیت جداگانه‌ای اجرا و برای اجرای آنها زمینه‌های خاصی را در سیستم عامل تعیین تکلیف می‌کند و این برای ما کاملا شفاف است که در این سیستم عامل بزرگ و گسترده چه تدابیری ارائه شده است. این بدان معنا است که هر برنامه با UID و GID خود اجرا خواهد شد. برای مثال زمانیکه در یک برنامه اطلاعاتی را می‌نویسید، برنامه‌های دیگر قادر نخواهند بود آن داده‌ها را بخوانند.
    اگر می‌خواهید اطلاعاتی را بین برنامه‌ها به اشتراک بگذارید، پس باید به صراحت این اشتراک گذاری را با استفاده از یک تامین‌کننده محتوا به اشتراک بگذارید تا امنیت قابل توجهی بین آنها ایجاد شود. اندروید به شما اجازه می‌دهد تا داده‌ها را با استفاده از پنج گزینه مختلف ذخیره کنید. این شما هستید که باید تصمیم بگیرید که چگونه داده‌های خاص خود را براساس الزامات پروژه ذخیره کنید. به تصویر زیر دقت کنید:
    در این تصویر اطلاعات کاملی از مکانیزم ذخیره سازی داده‌ها در پلتفرم اندروید مشاهده می‌کنید.


    ما می‌توانیم داده‌های خود را در یک بانک از نوع SQLite در نظر بگیریم. پایگاه‌داده به این دلیل، ما را از تصمیم غیرضروری برای اجرای ساختار بی نظم داده‌ها بی‌نیاز می‌کند. بیایید به یک مثال از نحوه ذخیره و بازیابی داده‌ها با استفاده از یکی از این مکانیزم‌ها نگاه کنیم.

    Shared Preferences یا اولویت‌های اشتراکی

    اولویت‌های مشترک عمدتا برای ذخیره‌سازی تنظیمات برنامه‌ها مفید هستند و تا زمانی که راه‌اندازی مجدد توسط دستگاه انجام شود، معتبر خواهد بود.بیایید بگوییم که باید اطلاعات مربوط به یک سرور ایمیل را ذخیره کنیم که برنامه ما برای بازیابی داده‌ها به آن نیاز دارد.

    ما باید نام میزبان ایمیل (hostname) , درگاه (port) و کارگزاری که از SSL استفاده می‌کند را ذخیره کنیم. کلاس زیر این کار را برای ما انجام میدهد به قطعه کد زیر توجه نمایید:

    package net.zenconsult.android;
    import java.util.Hashtable;
    import android.content.Context;
    import android.content.SharedPreferences;
    import android.content.SharedPreferences.Editor;
    import android.preference.PreferenceManager;
    public class StoreData {
     public static boolean storeData(Hashtable data, Context ctx) {
      SharedPreferences prefs = PreferenceManager
       .getDefaultSharedPreferences(ctx);
      String hostname = (String) data.get("hostname");
      int port = (Integer) data.get("port");
      boolean useSSL = (Boolean) data.get("ssl");
      Editor ed = prefs.edit();
      ed.putString("hostname", hostname);
      ed.putInt("port", port);
      ed.putBoolean("ssl", useSSL);
      return ed.commit();
     }
    }
    و بازیابی داده‌ها از طریق کلاس زیر میسر می‌شود
    package net.zenconsult.android;
    import java.util.Hashtable;
    import android.content.Context;
    import android.content.SharedPreferences;
    import android.preference.PreferenceManager;
    public class RetrieveData {
     public static Hashtable get(Context ctx) {
      String hostname = "hostname";
      String port = "port";
      String ssl = "ssl";
      Hashtable data = new Hashtable();
      SharedPreferences prefs = PreferenceManager
       .getDefaultSharedPreferences(ctx);
      data.put(hostname, prefs.getString(hostname, null));
      data.put(port, prefs.getInt(port, 0));
      data.put(ssl, prefs.getBoolean(ssl, true));
      return data;
     }
    }
    کلاس main برای ذخیره داده‌ها بدین صورت نوشته خواهد شد
    package net.zenconsult.android;
    import java.util.Hashtable;
    import android.app.Activity;
    import android.content.Context;
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.EditText;
    public class StorageExample1Activity extends Activity {
     /** Called when the activity is first created. */
     @Override
     public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.main);
      Context cntxt = getApplicationContext();
      Hashtable data = new Hashtable();
      data.put("hostname", "smtp.gmail.com");
      data.put("port", 587);
      data.put("ssl", true);
      if (StoreData.storeData(data, cntxt))
       Log.i("SE", "Successfully wrote data");
      else
       Log.e("SE", "Failed to write data to Shared Prefs");
      EditText ed = (EditText) findViewById(R.id.editText1);
      ed.setText(RetrieveData.get(cntxt).toString());
     }
    }
    با توجه به مثال فوق، خروجی ما در انتها به صورت زیر خواهد بود که تصویر زیر توضیحات بالا را پوشش می‌دهد.


    ‫آشنایی با ویژگی DebuggerTypeProxy در VS.Net

    $
    0
    0
    در مطالب قبلی، ویژگی DebuggerDisplay معرفی شده بود. ویژگی دیگری شبیه به این ویژگی وجود دارد به نام DebuggerTypeProxyکه در ادامه به معرفی آن می‌پردازیم.

    کلاس زیر را در نظر بگیرید:
    public class Data
    {
        public string Name { get; set; }
        public string ValueInHex { get; set; }
    }  
    پس از اجرای برنامه ، مقادیر کلاس ایجاد شده به این صورت خواهند بود :


    در اینجا مقدار Hex برایمان قابل فهم نیست. سناریویی را در نظر بگیرید که مقادیر باید داخل دیتابیس به صورت Hex نگهداری شوند، اما می‌خواهیم هنگام دیباگ، مقدار پراپرتی HexValue به صورت قابل درک و decimal آن نمایش داده شود.

    برای انجام اینکار میتوانیم از DebuggerTypeProxy استفاده کنیم. ابتدا کلاسی ایجاد میکنیم که بعنوان proxy، مقادیر را به شکلی که نیاز داریم نمایش دهد. این کلاس object اصلی را در Constructor دریافت کرده و مقادیر مورد نظرمان، از طریق property هایی که در آن تعریف می‌کنیم قابل دسترسی هستند:

    public class DataDebugView
    {
        private readonly Data _data;
    
        public DataDebugView(Data data)
        {
            _data = data;
        }
    
        public string DecimalValue
        {
            get
            {
                bool isValidHex = int.TryParse(_data.HexValue, System.Globalization.NumberStyles.HexNumber, null, out var value);
                return isValidHex ? value.ToString() : "INVALID HEX STRING";
            }
        }
    }

    در نهایت برای اعمال کردن این کلاس proxy، از ویژگی DebuggerTypeProxy بر روی کلاس اصلی استفاده می‌کنیم:

    [DebuggerTypeProxy(typeof(DataDebugView))]
    public class Data
    {
        public string Name { get; set; }
    
        public string HexValue { get; set; }
    }

    بعد از اعمال تغییرات و اجرای دوباره برنامه، نحوه نمایش مقادیر کلاس به این صورت تغییر خواهند یافت:

    ‫Angular Material 6x - قسمت اول - افزودن آن به برنامه

    $
    0
    0
    کتابخانه‌ی Angular Material تعدادی کامپوننت زیبای با قابلیت استفاده‌ی مجدد، به خوبی آزمایش شده و با قابلیت دسترسی بالا را بر اساس الگوهای Material Design ارائه می‌دهد. برای توسعه دهندگان Angular، کتابخانه‌ی Angular Material پیاده سازی مرجع رهنمودهای طراحی متریال گوگل است که توسط تیم اصلی Angular پیاده سازی و توسعه داده می‌شود. در این سری، مفاهیم طراحی نگارش 6x این کتابخانه را به همراه نحوه‌ی برپایی و تنظیم آن و همچنین کار با کامپوننت‌های پیشرفته‌ی آن، بررسی خواهیم کرد.


    منابع و مآخذ مرتبط با کتابخانه‌ی Angular Material

    در اینجا مآخذ اصلی کار با این کتابخانه را ملاحظه می‌کنید که شامل اصول طراحی متریال و مخازن اصلی توسعه‌ی آن می‌باشند:
    Material Design Specification
    - https://material.io/design
    Angular Material
    - https://material.angular.io
    - https://github.com/angular/material2


    مفاهیم پایه‌ی طراحی متریال

    چرا «زیبایی» رابط کاربری مهم است؟
    در ابتدای معرفی کتابخانه‌ی Angular Material عنوان شد که این مجموعه به همراه تعدادی کامپوننت «زیبا» است. بنابراین این سؤال مطرح می‌شود که چرا و یا تا چه اندازه «زیبایی» رابط کاربری اهمیت دارد؟ مهم‌ترین دلیل آن بهبود تجربه‌ی کاربری است. بر اساس تحقیقاتی که بر روی کاربران بسیاری صورت گرفته‌است، مشخص شده‌است کاربران، با رابط‌های کاربری زیبا نتایج بهتری را از لحاظ کاهش زمان اتمام کار و تعداد خطاهای مرتبط دریافت می‌کنند.

    اما ... طراحی برنامه‌های زیبا مشکل است. به همین جهت استفاده از کتابخانه‌های غنی مانند طراحی متریال که این امر را سهولت می‌بخشند، ضروری است. طراحی متریال یک زبان کامل طراحی برنامه‌های زیبا است. توسط گوگل طراحی شده‌است و دو هدف اصلی را دنبال می‌کند:
    - وفاداری به اصول کلاسیک طراحی رابط کاربری
    - ارائه‌ی تجربه‌ی کاربری یک‌دست و هماهنگ، در بین وسایل و اندازه‌های صفحات نمایشی مختلف

    اصول پایه‌ی طراحی متریال نیز شامل موارد زیر است:
    - «متریال» یک متافور است و بر اساس مطالعه‌ی نحوه‌ی کار با کاغذ، مرکب و ارتباط بین اشیاء در دنیای واقعی پدید آمد‌ه‌است.
    - اشیاء در دنیای واقعی دارای ارتباط‌های ابعادی و حجمی هستند. برای مثال دو برگه‌ی کاغذ یک فضا را اشغال نمی‌کنند. طراحی متریال برای نمایش این ارتباط سه بعدی بین اشیاء، از نور و سایه استفاده می‌کند.
    - در دنیای واقعی، اشیاء از درون یکدیگر رد نمی‌شوند. این مورد در طراحی متریال نیز صادق است.
    - طراحی متریال به همراه جعبه‌ی رنگ مخصوص و بکارگیری فضاهای خالی و عناوین درشت بسیار مشخص، واضح و عمدی است.
    - طراحی متریال به همراه حرکت و پویانمایی، جهت ارائه‌ی مفاهیم مختلف به کاربر، جهت درک بهتر او از برنامه است.


    برپایی پیشنیازهای ابتدایی کار با Angular Material

    پیش از ادامه‌ی بحث فرض بر این است که آخرین نگارش Angular CLIرا نصب کرده‌اید و اگر پیشتر آن‌را نصب کرده‌اید، یکبار دستور ذیل را اجرا کنید تا تمام وابستگی‌های سراسری نصب شده‌ی در سیستم به صورت خودکار به روز رسانی شوند:
     npm update -g
    سپس برنامه‌ی کلاینت Angular این سری را به همراه تنظیمات ابتدایی مسیریابی آن از طریق صدور فرمان ذیل آغاز می‌کنیم:
     ng new MaterialAngularClient --routing
    پس از ایجاد ساختار اولیه‌ی برنامه و نصب خودکار وابستگی‌های آن، جهت آزمایش برنامه، به پوشه‌ی آن وارد شده و آن‌را اجرا می‌کنیم:
    cd MaterialAngularClient
    ng serve -o
    که به این ترتیب برنامه در آدرس http://localhost:4200 و مرورگر پیش‌فرض سیستم نمایش داده خواهد شد.


    افزودن کتابخانه‌ی Angular Material به برنامه

    در طول این سری از سایت https://material.angular.ioزیاد استفاده خواهیم کرد. همواره به روزترین روش افزودن کتابخانه‌ی Angular Material به یک برنامه‌ی موجود را در آدرس https://material.angular.io/guide/getting-startedمی‌توانید مشاهده کنید که خلاصه‌ی آن به صورت زیر است:
    البته در Angular 6 روش تفصیلی نصب فوق که شامل 6 مرحله‌است، به صورت زیر هم خلاصه شده‌است:
     ng add @angular/material
    متاسفانه در زمان نگارش این مطلب، نگارش 6.3.1 آن توسط دستور فوق نصب نشد و خطای «Error: Collection "@angular/material" cannot be resolved.» ظاهر گردید. البته روش رفع آن در اینجا بحث شده‌است که مهم نیست و در نگارش‌های رسمی بعدی حتما لحاظ خواهد شد. به همین جهت روش تفصیلی آن‌را که همیشه کار می‌کند، در ادامه پیگیری می‌کنیم. ابتدا بسته‌های ذیل را نصب کنید:
    npm install --save @angular/material @angular/cdk
    npm install --save @angular/animations
    npm install --save hammerjs
    - دستور اول  angular/cdk و angular/material را نصب می‌کند. cdk در اینجا به معنای کیت توسعه‌ی کامپوننت‌های Angular است که امکان استفاده‌ی از ویژگی‌های Angular Material را بدون الزامی به پیروی از زبان طراحی متریال، میسر می‌کند.
    - همانطور که عنوان شد، طراحی متریال مبتنی بر حرکت و پویانمایی است. به همین جهت تعدادی از کامپوننت‌های آن نیاز به بسته‌ی angular/animations را دارند که توسط دستور دوم نصب می‌شود.
    - دستور سوم نیز کامپوننت‌های slide و slider را پشتیبانی می‌کند (Gesture Support). البته پس نصب این وابستگی، نیاز است به فایل src/main.ts مراجعه کرده و یک سطر زیر را نیز افزود:
     import "hammerjs";
    در ادامه پس از نصب بسته‌ی پویانمایی، به فایل app.module.ts مراجعه کرده و BrowserAnimationsModule را به لیست imports اضافه می‌کنیم:
    import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
    
    @NgModule({
      imports: [
        BrowserModule,
        BrowserAnimationsModule,
        AppRoutingModule
      ]
    })
    export class AppModule { }

    مدیریت بهتر import کامپوننت‌های Angular Material

    در ادامه به ازای هر کامپوننت Angular Material باید ماژول آن‌را به لیست imports افزود که پس از مدتی به یک فایل app.module.ts بسیار شلوغ خواهیم رسید. برای مدیریت بهتر این فایل، از روش مطرح شده‌ی در مطلب «سازماندهی برنامه‌های Angular» استفاده خواهیم کرد.
    به همین جهت دو پوشه‌ی core و shared را درون پوشه‌ی src/app ایجاد می‌کنیم:


    محتویات فایل src\app\core\core.module.ts به صورت زیر است:
    import { CommonModule } from "@angular/common";
    import { NgModule, Optional, SkipSelf } from "@angular/core";
    import { RouterModule } from "@angular/router";
    
    
    @NgModule({
      imports: [CommonModule, RouterModule],
      exports: [
        // components that are used in app.component.ts will be listed here.
      ],
      declarations: [
        // components that are used in app.component.ts will be listed here.
      ],
      providers: [
        /* ``No`` global singleton services of the whole app should be listed here anymore!
           Since they'll be already provided in AppModule using the `tree-shakable providers` of Angular 6.x+ (providedIn: 'root').
           This new feature allows cleaning up the providers section from the CoreModule.
           But if you want to provide something with an InjectionToken other that its class, you still have to use this section.
        */
      ]
    })
    export class CoreModule {
      constructor(@Optional() @SkipSelf() core: CoreModule) {
        if (core) {
          throw new Error("CoreModule should be imported ONLY in AppModule.");
        }
      }
    }
    در مورد جزئیات آن در مطلب «سازماندهی برنامه‌های Angular توسط ماژول‌ها» کاملا بحث شده‌است.
    محتویات فایل src\app\shared\shared.module.ts نیز به صورت زیر است:
    import { CommonModule } from "@angular/common";
    import { HttpClientModule } from "@angular/common/http";
    import { ModuleWithProviders, NgModule } from "@angular/core";
    import { FormsModule } from "@angular/forms";
    
    @NgModule({
      imports: [
        CommonModule,
        FormsModule,
        HttpClientModule
      ],
      entryComponents: [
        // All components about to be loaded "dynamically" need to be declared in the entryComponents section.
      ],
      declarations: [
        // common and shared components/directives/pipes between more than one module and components will be listed here.
      ],
      exports: [
        // common and shared components/directives/pipes between more than one module and components will be listed here.
        CommonModule,
        FormsModule,
        HttpClientModule,
      ]
      /* No providers here! Since they’ll be already provided in AppModule. */
    })
    export class SharedModule {
      static forRoot(): ModuleWithProviders {
        // Forcing the whole app to use the returned providers from the AppModule only.
        return {
          ngModule: SharedModule,
          providers: [ /* All of your services here. It will hold the services needed by `itself`. */]
        };
      }
    }
    سپس تعاریف import این دو فایل را به فایل app.module.ts اضافه می‌کنیم:
    import { CoreModule } from "./core/core.module";
    import { SharedModule } from "./shared/shared.module";
    
    @NgModule({
      imports: [
        BrowserModule,
        BrowserAnimationsModule,
        CoreModule,
        SharedModule.forRoot(),
        AppRoutingModule
      ]
    })
    export class AppModule { }
    پس از این مقدمات، فایل جدید src\app\shared\material.module.tsرا در پوشه‌ی shared ایجاد می‌کنیم تا بتوانیم مداخل کامپوننت‌های Angular Material را صرفا به آن اضافه کنیم؛ با این محتوا:
    import { CdkTableModule } from "@angular/cdk/table";
    import { NgModule } from "@angular/core";
    import {
      MatAutocompleteModule,
      MatButtonModule,
      MatButtonToggleModule,
      MatCardModule,
      MatCheckboxModule,
      MatChipsModule,
      MatDatepickerModule,
      MatDialogModule,
      MatExpansionModule,
      MatFormFieldModule,
      MatGridListModule,
      MatIconModule,
      MatInputModule,
      MatListModule,
      MatMenuModule,
      MatNativeDateModule,
      MatPaginatorModule,
      MatProgressBarModule,
      MatProgressSpinnerModule,
      MatRadioModule,
      MatRippleModule,
      MatSelectModule,
      MatSidenavModule,
      MatSliderModule,
      MatSlideToggleModule,
      MatSnackBarModule,
      MatSortModule,
      MatStepperModule,
      MatTableModule,
      MatTabsModule,
      MatToolbarModule,
      MatTooltipModule,
    } from "@angular/material";
    
    @NgModule({
      imports: [
        MatAutocompleteModule,
        MatButtonModule,
        MatButtonToggleModule,
        MatCardModule,
        MatCheckboxModule,
        MatChipsModule,
        MatDatepickerModule,
        MatDialogModule,
        MatExpansionModule,
        MatFormFieldModule,
        MatGridListModule,
        MatIconModule,
        MatInputModule,
        MatListModule,
        MatMenuModule,
        MatNativeDateModule,
        MatPaginatorModule,
        MatProgressBarModule,
        MatProgressSpinnerModule,
        MatRadioModule,
        MatRippleModule,
        MatSelectModule,
        MatSidenavModule,
        MatSliderModule,
        MatSlideToggleModule,
        MatSnackBarModule,
        MatStepperModule,
        MatSortModule,
        MatTableModule,
        MatTabsModule,
        MatToolbarModule,
        MatTooltipModule,
        CdkTableModule
      ],
      exports: [
        MatAutocompleteModule,
        MatButtonModule,
        MatButtonToggleModule,
        MatCardModule,
        MatCheckboxModule,
        MatChipsModule,
        MatDatepickerModule,
        MatDialogModule,
        MatExpansionModule,
        MatGridListModule,
        MatIconModule,
        MatInputModule,
        MatListModule,
        MatMenuModule,
        MatNativeDateModule,
        MatPaginatorModule,
        MatProgressBarModule,
        MatProgressSpinnerModule,
        MatRadioModule,
        MatRippleModule,
        MatSelectModule,
        MatSidenavModule,
        MatSliderModule,
        MatSlideToggleModule,
        MatSnackBarModule,
        MatStepperModule,
        MatSortModule,
        MatTableModule,
        MatTabsModule,
        MatToolbarModule,
        MatTooltipModule,
        CdkTableModule
      ]
    })
    export class MaterialModule {
    }
    در اینجا هر کامپوننت مورد نیاز، به قسمت‌های import و exports اضافه شده‌اند.
    سپس MaterialModule را نیز به قسمت‌های imports و exports فایل src\app\shared\shared.module.ts اضافه خواهیم کرد:
    import { MaterialModule } from "./material.module";
    
    @NgModule({
      imports: [
        CommonModule,
        FormsModule,
        HttpClientModule,
        MaterialModule
      ],
      exports: [
        // common and shared components/directives/pipes between more than one module and components will be listed here.
        CommonModule,
        FormsModule,
        HttpClientModule,
        MaterialModule
      ]
    })
    export class SharedModule {
    }
    به این ترتیب در هر ماژول جدیدی که به برنامه اضافه شود و نیاز به کار با Angular Material را داشته باشد، تنها کافی است SharedModule را import کرد؛ مانند app.module.ts برنامه (البته بدون ذکر متد forRoot آن که این forRoot فقط محض ماژول اصلی برنامه است).

    تا اینجا جهت اطمینان از اجرای برنامه، دستور ng serve -o را از ابتدا اجرا کنید.


    افزودن چند کامپوننت مقدماتی متریال به برنامه

    بهترین روش کار با این مجموعه، بررسی مستندات آن در سایت https://material.angular.io/componentsاست. برای مثال برای افزودن دکمه، به مستندات آنمراجعه کرده و بر روی دکمه‌ی view source کلیک می‌کنیم:


    سپس کدهای قسمت HTML آن‌را به برنامه و فایل app.component.html اضافه خواهیم کرد:
    <button mat-button>Click me!</button>
    به همین ترتیب مستندات check boxرا یافته و آن‌را نیز اضافه می‌کنیم:
    <mat-checkbox>Check me!</mat-checkbox>
    تا اینجا اگر برنامه را توسط دستور ng serve -o اجرا کنیم، یک چنین خروجی حاصل می‌شود:


    البته شکل ظاهری آن‌ها تا اینجا آنچنان مطلوب نیست. برای رفع این مشکل، نیاز است یک قالب را به این کنترل‌ها و کامپوننت‌ها اعمال کرد. به همین جهت فایل styles.css واقع در ریشه‌ی برنامه را گشوده و قالب پیش‌فرض متریال را به آن اضافه می‌کنیم:
     @import "~@angular/material/prebuilt-themes/indigo-pink.css";
    قالب‌های از پیش آماده‌ی متریال را در پوشه‌ی node_modules\@angular\material\prebuilt-themes می‌توانید مشاهده کنید.



    پس از اعمال قالب، اکنون است که شکل ظاهری کنترل‌های آن بسیار بهتر شده‌اند و همچنین کار با آن‌ها به همراه پویانمایی نیز شده‌است:



    افزودن آیکن‌های متریال به برنامه

    مرحله‌ی آخر این تنظیمات، افزودن آیکن‌های متریال به برنامه‌است. برای این منظور فایل src\index.html را گشوده و یک سطر ذیل را به head اضافه کنید:
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    برای آزمایش آن، به فایل app.component.html مراجعه کرده و تعریف دکمه‌ای را که اضافه کردیم، به صورت ذیل با افزودن mat-icon تغییر می‌دهیم:
    <button mat-button><mat-icon>face</mat-icon>
      Click me!</button><mat-checkbox>Check me!</mat-checkbox>
    که این خروجی را تولید می‌کند:


    لیست کامل این آیکن‌ها را به همراه توضیحات تکمیلی آن‌ها، در آدرس ذیل می‌توانید ملاحظه کنید:
    http://google.github.io/material-design-icons

    البته چون ما نمی‌خواهیم این آیکن‌ها را از وب بارگذاری کنیم، برای نصب محلی آن‌ها ابتدا دستور زیر را در ریشه‌ی پروژه صادر کنید:
     npm install material-design-icons --save
    این آیکن فونت‌ها پس از نصب، در مسیر node_modules\material-design-icons\iconfont قابل مشاهده هستند:


    همانطور که مشاهده می‌کنید، برای استفاده‌ی از این فایل‌های آیکن فونت محلی، تنها کافی است فایل material-icons.css را به برنامه معرفی کنیم. برای این منظور فایل angular.json را گشوده و قسمت styles آن‌را به صورت زیر تکمیل می‌کنیم:
    "styles": [
       "node_modules/material-design-icons/iconfont/material-icons.css",
       "src/styles.css"
    ],
    اکنون دیگر نیازی به ذکر link href اضافه شده‌ی به فایل src\index.html نداریم و باید از آن حذف شود.



    کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MaterialAngularClient-01.zip
    برای اجرای آن نیز ابتدا فایل restore.bat و سپس فایل ng-serve.bat را اجرا کنید.

    ‫Angular Material 6x - قسمت دوم - معرفی Angular Flex layout

    $
    0
    0
    در این سری قصد داریم یک برنامه‌ی ساده‌ی دفترچه تلفن را توسط Angular 6x و کامپوننت‌های متریال آن ایجاد کنیم؛ اما Grid جزئی از بسته‌ی Angular Material نیست. بنابراین برای طرحبندی برنامه و قرار دادن المان‌های مختلف در مکان‌های تعیین شده‌ی صفحه، از Angular FlexBox Module استفاده خواهیم کرد که محصور کننده‌ی CSS 3 FlexBox است.


    آشنایی با Flex Layout Box Model

    برای طراحی ظاهر یک برنامه‌ی وب نیاز است عناصر آن‌را در مکان‌های مختلفی از صفحه قرار داد که به آن Layout گفته می‌شود. برای این منظور عموما 4 روش ذیل مرسوم هستند:
    1. Table
    2. Float, position, clear
    3. CSS Grids
    4. FlexBox CSS

    امروزه دیگر آنچنان روش‌های 1 و 2 به صورت مستقیم مورد استفاده قرار نمی‌گیرند. CSS Grid روش نهایی طراحی Layout در آینده خواهد بود و در حال حاضر تعداد مرورگرهایی که از آن پشتیبانی می‌کنند، قابل توجه نیست؛ اما از FlexBox در IE 11، کروم 21 و فایرفاکس 22 به بعد پشتیبانی می‌شود.


    FlexBox CSS، سیلان المان‌های قرار گرفته‌ی در داخل آن‌را سبب می‌شود. در اینجا یک container اصلی وجود دارد که در برگیرنده‌ی المان‌ها است. در تصویر فوق دو محور را مشاهده می‌کنید. محور افقی از چپ به راست ادامه پیدا می‌کند. محور عمودی نحوه‌ی ارتباط عناصر را مشخص می‌کند.
    اکنون این سؤال مطرح می‌شود که چه تفاوتی بین یک Grid و FlexBox CSS وجود دارد؟ در یک Grid طراحی دو بعدی سطرها و ستون وجود دارد. اما به FlexBox باید به صورت سیلان یک بعدی سلول‌ها نگاه کرد. برای مثال عناصر قرار گرفته‌ی درون Container یا به صورت افقی درون آن گسترده شده و قرار می‌گیرند و یا به صورت عمودی.


    نحوه‌ی تفکر و کارکرد با FlexBox چگونه است؟

    در اینجا باید به دو مفهوم دقت داشت:
    الف) سیلان عناصر درون Container که می‌تواند افقی و یا عمودی باشد.
    ب) اندازه‌ی المان‌ها که می‌تواند ثابت و یا نسبی باشد.

    یک Container، جهت سیلان عناصر درون آن‌را مشخص می‌کند. المان‌های آن، اندازه، فاصله‌ی از یکدیگر و ترتیب قرارگیری را ارائه می‌دهند. یک flex container می‌تواند شامل چندین flex container تو در تو نیز باشد.


    نحوه‌ی سیلان عناصر در FlexBox چگونه است؟

    برای نمونه طرحبندی متداول ذیل را درنظر بگیرید:


    نحوه تفکر در مورد طراحی این طرحبندی، باید از بیرون به درون و از بالا به پایین (سیلان عمودی) باشد:


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


    در ادامه به قسمت میانی می‌رسیم که آن نیز دارای سیلان افقی از چپ به راست است:


    در اینجا نیز می‌توان سه Container را متصور شد که وسطی دارای سیلان افقی از راست به چپ است و مواردی که بر اساس اندازه‌ی آن‌ها در یک سطر جا نشده‌اند، به سطر بعدی منتقل خواهند شد:


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


    البته در این تصویر یک ایراد هم وجود دارد. با توجه به اینکه در ناحیه‌ی میانی سه Container تعریف خواهند شد. Container ایی که در میان آن قرار می‌گیرد، دارای سیلان خاص خودش است و اندازه‌های آن باید نسبت به این Container تعریف شوند و نه نسبت به کل ناحیه‌ی میانی. یعنی بجای اینکه 50 درصد، 25، 25 و 50 درصد را داشته باشیم، این‌ها در اصل 100 درصد، 50 و 50 درصد و سپس 100 درصد هستند.


    معرفی کتابخانه‌ی Angular Flex Layout

    برای کار با Flex CSS نیاز است:
    - مقدار زیادی کد CSS نوشت.
    - نیاز به درک عمیقی از Flex Box دارد.
    - نیاز است با باگ‌های مرورگرها و تفاوت‌های پیاده سازی‌های آن‌ها در مورد FlexBox آشنا بود.
    - نیاز به Prefixing دارد.
    - برای Angular طراحی نشده‌است.

    جهت رفع این مشکلات و محدودیت‌ها، تیم Angular کتابخانه‌ای را به نام Angular Flex Layoutمخصوص نگارش‌های جدید Angular طراحی کرده‌است. این کتابخانه مستقل از Angular Material است اما عموما به همراه آن استفاده می‌شود.

    مزایای کار با کتابخانه‌ی Angular flex layout
    - یک کتابخانه‌ی متکی به خود و مستقل است و برای کار با آن الزامی به استفاده‌ی از Angular Material نیست.
    - به همراه هیچ فایل CSS جانبی ارائه نمی‌شود.
    - پیاده سازی TypeScript ایی دارد. در اصل یک سری directives مخصوص Angular است که با TypeScript نوشته شده‌است.
    - به صورت پویا و inline تمام CSSهای مورد نیاز را تولید و تزریق می‌کند.
    - به همراه یک API استاتیک است و همچنین یک API واکنشگرا
    - با Angular CLI نیز یکپارچه شده‌است.


    نصب و تنظیم کتابخانه‌ی Angular Flex layout

    برای نصب این کتابخانه، در ریشه‌ی پروژه دستور زیر را صادر کنید:
     npm install @angular/flex-layout --save
    سپس ماژول آن‌را باید به shared.module.ts اضافه کرد:
    import { FlexLayoutModule } from "@angular/flex-layout";
    
    @NgModule({
      imports: [
        FlexLayoutModule
      ],
      exports: [
        FlexLayoutModule
      ]
    })
    export class SharedModule {
    }


    کار با API استاتیک Angular Flex layout

    API استاتیک Angular Flex layout شامل این مزایا و مشخصات است:
    - به صورت یکسری دایرکتیو Angular طراحی شده‌است که به HTML قالب کامپوننت‌ها اضافه می‌شود.
    - از data binding پشتیبانی می‌کند.
    - CSS نهایی را به صورت پویا و inline تولید و به صفحه تزریق می‌کند. Inline CSS تزریق شده به ویژگی‌های styles هر المان تزریق می‌شوند و موارد مشابه را در صورت وجود بازنویسی می‌کنند.
    - از تشخیص تغییرات پشتیبانی می‌کند.
    - به همراه ویژگی‌های fxHide و fxShow است.
    - کارآیی مطلوبی دارد.

    در اینجا برای تعریف container اصلی از دایرکتیوهای زیر استفاده می‌شود:
    - fxLayout جهت‌های flex را مشخص می‌کند.
    <div fxLayout="row" 
         fxLayout.xs="column"></div>
    - fxLayout می‌تواند دارای مقداری مانند row، column و row-reverse و column-reverse باشد. برای مثال مقدار row-reverse‌، نمایش از راست به چپ را سبب می‌شود.
    - fxLayoutWrap مشخص می‌کند که آیا المان‌ها باید به سطر و یا ستون بعدی منتقل شوند یا خیر؟
    <div fxLayoutWrap></div>
    - fxLayoutGap فاصله‌ی بین المان‌ها را مشخص می‌کند.
    <div fxLayoutGap="10px"></div>
    - fxLayoutAlign نحوه‌ی چیدمان المان را تعیین می‌کند.
    <div fxLayoutAlign="start stretch"></div>

    چند مثال:


    و یا حالت راست به چپ آن به صورت زیر است:


    و برای تعریف آیتم‌های قرار گرفته‌ی درون containers می‌توان از دایرکتیوهای زیر استفاده کرد:
    - fxflex برای تعیین اندازه و flex المان‌ها
    <div fxFlex="1 2 calc(15em + 20px)"></div>
    در اینجا سه مقداری که ذکر می‌شوند (و یا تنها یک مقدار) چنین معنایی را به همراه دارند:
     fxFlex="grow shrink basis"
    و یا
     fxFlex="basis"
    - grow به این معنا است که آیتم جاری در صورت وجود فضا (طراحی واکنشگرا و واکنش نشان دادن به اندازه‌ی صفحه)، نسبت به سایر المان‌ها تا چه اندازه‌ای می‌تواند بزرگ شود.
    - shrink به این معنا است که اگر به اندازه‌ی کافی فضا وجود نداشت، این المان نسبت به سایر المان‌های دیگر تا چه اندازه‌ای می‌تواند کوچک شود.
    - basis به معنای اندازه‌ی پیش‌فرض المان است.

    در اینجا اندازه‌ها برحسب پیکسل، درصد و یا calcs, em, cw, vh می‌توانند تعیین شوند. همچنین یک سری نام مستعار مانند grow, initial, auto, none, nogrow, noshrink هم قابل استفاده هستند.

    - fxflexorder برای تعیین ترتیب قرارگیری یک المان
    <div fxFlexOrder="2"></div>
    -  fxflexoffset برای تعیین فاصله یک المان از container آن
    <div fxFlexOffset="20px"></div>
    -  fxflexAlign برای تعیین محل قرارگیری المان
    <div fxFlexAlign="center"></div>
    - fxflexfill برای تعیین اینکه این المان کل ردیف یا ستون را پر خواهد کرد
    <div fxFlexFill></div>

    چند مثال:


    در اینجا سه نمایشی را که در ذیل تعریف div‌ها مشاهده می‌کنید بر اساس تغییر اندازه‌ی صفحه حاصل شده‌اند. چون آیتم دوم دارای مقدار grow مساوی 5 است، به همین جهت با تغییر اندازه‌ی صفحه و دسترسی به مقدار فضای بیشتر، بزرگ‌تر شده‌است.

    یک مثال کامل
    اگر علاقمند باشید تا توانمندی‌های angular flex layout را در قالب یک مثال کامل مشاهده کنید، به آدرس زیر مراجعه نمائید:
    https://tburleson-layouts-demos.firebaseapp.com/#/docs
    در این مثال با تغییر گزینه‌‌های مختلف، کد معادل angular flex layout آن نیز تولید می‌شود.
    همچنین wiki خود پروژه نیز به همراه مثال‌های بیشتری است:
    https://github.com/angular/flex-layout/wiki



    کار با API واکنشگرای Angular Flex layout


    در طراحی واکنشگرا، container و عناصر داخل آن بر اساس تغییرات اندازه‌ی صفحه و یا اندازه‌ی وسیله‌ی نمایشی، تغییر اندازه و همچنین موقعیت می‌دهند و این تغییرات بر اساس انطباق با viewport وسیله‌ی نمایشی صورت می‌گیرند. به همین جهت برای طراحی واکنشگرا نیاز به Flex CSS و همچنین Media Query است. نوشتن Medial Query و ترکیب آن با Flex CSS کار مشکلی است. به همین جهت Angular Flex layout به همراه یک API واکنشگرا نیز هست که در پشت صحنه Flex CSS را بر اساس طراحی متریال و Medial Queries مورد استفاده قرار می‌دهد.
    اگر علاقمند هستید تا اندازه‌های واکنشگرای استاندارد متریال را ملاحظه کنید، می‌توانید به آدرس زیر مراجعه نمائید (قسمت Breakpoint system آن):
    https://material.io/design/layout/responsive-layout-grid.html#breakpoints
    برای مثال هر اندازه‌ای کمتر از 600px در گروه extra small قرار می‌گیرد (با مخفف xs). از 600px تا 1024px در بازه‌ی small (با مخفف sm)، از 1024px تا 1440px در بازه‌ی medium (با مخفف md) و از 1440px تا 1920px در بازه‌ی large (با مخفف lg) و بیشتر از آن در بازه‌ی xlrage قرار می‌گیرند (با مخفف xl). این اعداد و بازه‌ها، پایه‌ی طراحی API واکنشگرای Angular Flex layout هستند. به همین جهت نام این بازه‌ها در این API به صورت مخفف xs, sm, md, lg, xl درنظر گرفته شده‌اند و مورد استفاده قرار می‌گیرند. همچنین اگر اندازه‌های مدنظر از این بازه‌ها کمتر باشند، می‌توان از lt-sm, lt-md, lt-lg, lt-xl نیز استفاده کرد. در اینجا lt به معنای less than است و یا اگر بازه‌های مورد نیاز بیش از این اندازه‌ها باشند می‌توان با gt-xs, gt-sm, gt-md, gt-lg کار کرد. در اینجا gt به معنای greater than است.
    به این مخفف‌ها «media query alias» هم گفته می‌شود و اکنون که لیست آن‌ها مشخص است، تنها کافی است آن‌ها را به API استاتیکی که پیشتر بررسی کردیم، اضافه کنیم. برای مثال:
    fxLayout.sm = "..."
    fxLayoutAlign.md = "..."
    fxHide.gt-sm = "..."
    برای نمونه فرض کنید یک چنین طرحبندی دسکتاپی را داریم:


    معادل طراحی آن با API استاتیک Angular Flex Layout به صورت زیر است:

    که در اینجا دو container را ملاحظه می‌کنید. ابتدا Container بیرونی جهت ارائه‌ی ستونی از سه المان اضافه شده‌است. سپس یک Container میانی برای  تعریف ردیفی از سه المان تعریف شده‌است. توسط روش "fxFlex="grow shrink basis نیز اندازه‌های آن‌ها مشخص شده‌اند.

    اکنون که این طرحبندی دسکتاپ را داریم، چگونه باید آن‌را تبدیل به طرحبندی موبایل، مانند شکل زیر کنیم؟


    برای اینکار ابتدا fxLayout.xs را به سطر میانی اضافه می‌کنیم تا هرگاه به این اندازه رسیدیم، بجای ردیف، تبدیل به ستون شود. سپس توسط fxFlexOrder.xs، در اندازه‌ی xs، محل قرارگیری المان‌های این ستون را هم مشخص می‌کنیم:


    همانطور که ملاحظه می‌کنید کار کردن با این API بسیار ساده‌است و نیازی به کارکردن مستقیم با Media Queries و یا برنامه نویسی مستقیم ندارد و تمام آن در قالب HTML یک کامپوننت قابل پیاده سازی است.
    یک نکته:مثال کاملیکه پیشتر در این بحث مطرح شد، به همراه مثال واکنشگرا نیز هست که برای مشاهده‌ی اثر آن‌ها بهتر است اندازه‌ی مرورگر را کوچک و بزرگ کنید.


    مخفی کردن و یا نمایش قسمتی از صفحه بر اساس اندازه‌ی آن

    علاوه بر media query alias هایی که عنوان شد، امکان نمایش و یا مخفی سازی قسمت‌های مختلف صفحه بر اساس اندازه‌ی صفحه‌ی نمایشی نیز هست:
    <div fxShow fxHide.xs="false" fxHide.lg="true"></div>
    در اینجا fxShow سبب نمایش این div در حالت عادی می‌شود (پیش‌فرض آن xl، md و sm است). اما اگر اندازه‌ی صفحه lg باشد، fxHide.lg تنظیم شده‌ی به true سبب مخفی سازی آن خواهد شد و در اندازه‌ی xs مجددا نمایش داده می‌شود.


    تغییر اندازه‌ی قسمتی از صفحه بر اساس اندازه‌ی آن

    در مثال زیر اگر اندازه‌ی صفحه gt-sm باشد (بیشتر از small)، اندازه‌ی این div به 100 درصد بجای 50 درصد حالت‌های دیگر، تنظیم می‌شود:
    <div fxFlex="50%" fxFlex.gt-sm="100%"></div>

    حالت‌های ویژه‌ی طراحی واکنشگرا در Angular Flex Layout

    در API واکنشگرای آن حالت‌های ویژه‌ی fxshow, fxhide, ngclass  و  ngstyle نیز درنظر گرفته شده‌اند که امکان فعالسازی آن‌ها در اندازه‌های مختلف صفحه مسیر است:
    <div fxShow [fxShow.xs]="isVisibleOnMobile()"></div><div fxHide [fxHide.gt-sm]="isVisibleOnDesktop()"></div><div [ngClass.sm]="{'fxClass-sm': hasStyle}" ></div><div [ngStyle.xs]="{color: 'blue'}"></div>


    امکان کار با API واکنشگرا از طریق برنامه نویسی

    برای این منظور می‌توان از سرویس ObservableMedia مانند مثال زیر استفاده کرد:


    در اینجا به فعالسازی یک بازه‌ی خاص گوش فرا خواهیم داد. برای مثال اگر اندازه‌ی صفحه xs بود، سبب بارگذاری محتوای خاص مرتبط با موبایل خواهیم شد.



    برای مطالعه‌ی بیشتر
    قسمت‌های عمده‌ای از مطلب جاری، از ویدیوی زیر که توسط نویسنده‌ی اصلی angular flex layout تهیه شده‌است، گردآوری شدند.
     

    ‫Angular Material 6x - قسمت سوم - طرحبندی برنامه

    $
    0
    0
    پس از نصب بسته‌ی Angular Materialو آشنایی با سیستم Angular Flex Layoutبرای پوشش کمبود سیستم طرحبندی آن، در این قسمت طرح ابتدایی دفترچه تلفن این سری را پیگیری می‌کنیم تا به طراحی زیر برای حالت‌های دسکتاپ و موبایل برسیم:



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


    ایجاد ماژول مدیریت تماس‌ها

    در قسمت اول، برنامه را به همراه تنظیمات ابتدایی مسیریابی آن ایجاد کردیم که نتیجه‌ی آن تولید فایل src\app\app-routing.module.ts می‌باشد:
     ng new MaterialAngularClient --routing
    در ادامه ماژول مخصوص مدیریت تماس‌ها را ایجاد می‌کنیم که به آن feature moduleهم گفته می‌شود. برای این منظور دستور زیر را اجرا کنید:
     ng g m ContactManager -m app.module --routing


    این دستور ماژول جدید contact-manager را به همراه تنظیمات ابتدایی مسیریابی و همچنین به روز رسانی app.module، برای درج آن، ایجاد می‌کند. البته در این حالت نیاز است به app.module.ts مراجعه کرد و محل درج آن‌را تغییر داد:
    import { ContactManagerModule } from "./contact-manager/contact-manager.module";
    
    @NgModule({
      imports: [
        BrowserModule,
        BrowserAnimationsModule,
        CoreModule,
        SharedModule.forRoot(),
        ContactManagerModule,
        AppRoutingModule
      ],
    })
    export class AppModule { }
    به صورت پیش‌فرض ContactManagerModule، پس از AppRoutingModule ذکر می‌شود و چون مسیر catch all را در ادامه در AppRoutingModule قرار می‌دهیم، دیگر هیچگاه مسیریابی‌های ContactManagerModule پردازش نخواهند شد. به همین جهت باید آن‌را پیش از AppRoutingModule قرار داد.
    سپس دستور زیر را اجرا می‌کنیم تا کامپوننت contact-manager-app در ماژول contact-manager ایجاد شود:
     ng g c contact-manager/ContactManagerApp --no-spec
    با این خروجی:
    CREATE src/app/contact-manager/contact-manager-app/contact-manager-app.component.html (38 bytes)
    CREATE src/app/contact-manager/contact-manager-app/contact-manager-app.component.ts (319 bytes)
    CREATE src/app/contact-manager/contact-manager-app/contact-manager-app.component.css (0 bytes)
    UPDATE src/app/contact-manager/contact-manager.module.ts (436 bytes)
    همانطور که ملاحظه می‌کنید این دستور کار به روز رسانی contact-manager.module را نیز جهت معرفی این کامپوننت جدید انجام داده‌است.
    این کامپوننت به عنوان میزبان سایر کامپوننت‌هایی که در مقدمه‌ی بحث عنوان شدند، عمل می‌کند. این کامپوننت‌ها را به صورت زیر در پوشه‌ی components ایجاد می‌کنیم:
    ng g c contact-manager/components/toolbar --no-spec
    ng g c contact-manager/components/main-content --no-spec
    ng g c contact-manager/components/sidenav --no-spec
    با این خروجی:



    تنظیمات مسیریابی برنامه

    در ادامه به src\app\app-routing.module.ts مراجعه کرده و این ماژول جدید را به صورت lazy load معرفی می‌کنیم:
    import { NgModule } from "@angular/core";
    import { RouterModule, Routes } from "@angular/router";
    
    const routes: Routes = [
      { path: "contactmanager", loadChildren: "./contact-manager/contact-manager.module#ContactManagerModule" },
      { path: "", redirectTo: "contactmanager", pathMatch: "full" },
      { path: "**", redirectTo: "contactmanager" }
    ];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule]
    })
    export class AppRoutingModule { }
    در اینجا تنظیمات صفحه‌ی پیش‌فرض برنامه و همچنین not found و یا catch all را نیز مشاهده می‌کنید که هر دو به contactmanager تنظیم شده‌اند.

    سپس تنظیمات مسیریابی ماژول مدیریت تماس‌ها را در فایل src\app\contact-manager\contact-manager-routing.module.ts به صورت زیر تغییر می‌دهیم:
    import { NgModule } from "@angular/core";
    import { RouterModule, Routes } from "@angular/router";
    
    import { MainContentComponent } from "./components/main-content/main-content.component";
    import { ContactManagerAppComponent } from "./contact-manager-app/contact-manager-app.component";
    
    const routes: Routes = [
      {
        path: "", component: ContactManagerAppComponent,
        children: [
          { path: "", component: MainContentComponent }
        ]
      },
      { path: "**", redirectTo: "" }
    ];
    
    @NgModule({
      imports: [RouterModule.forChild(routes)],
      exports: [RouterModule]
    })
    export class ContactManagerRoutingModule { }
    در اولین بار بارگذاری این ماژول، کامپوننت ContactManagerApp بارگذاری می‌شود. همچنین مسیر not found نیز به همان مسیر ریشه‌ی این کامپوننت تنظیم شده‌است.
    کامپوننت ContactManagerApp که کار هاست سایر کامپوننت‌های این ماژول را بر عهده دارد، دارای router-outlet خاص خود خواهد بود. به همین جهت برای آن children تعریف شده‌است که مسیر پیش‌فرض آن، بارگذاری کامپوننت MainContent است.
    بنابراین نیاز است به فایل contact-manager-app\contact-manager-app.component.html مراجعه و ابتدا منوی کنار صفحه را به آن افزود:
    <app-sidenav></app-sidenav>
    app-sidenav همان selector کامپوننت sidenav است که در فایل sidenav\sidenav.component.ts قابل مشاهده‌است.
    سپس در قالب sidenav\sidenav.component.html، کار تعریف toolbar و همچنین router-outlet را انجام می‌دهیم:
    <app-toolbar></app-toolbar><router-outlet></router-outlet>
    بر اساس مسیریابی که تعریف کردیم، router-outlet کار نمایش Main Content را انجام می‌دهد.


    معرفی Angular Material به ماژول جدید مدیریت تماس‌ها

    در قسمت اول، یک فایل material.module.ts را ایجاد کردیم که به همراه تعریف تمامی کامپوننت‌های Angular Material بود. سپس آن‌را به shared.module.ts افزودیم که حاوی تعریف ماژول فرم‌ها و همچنین Flex Layout نیز هست. به همین جهت برای معرفی این‌ها به این ماژول جدید تنها کافی است در فایل src\app\contact-manager\contact-manager.module.ts در قسمت imports، کار معرفی SharedModule صورت گیرد:
    import { SharedModule } from "../shared/shared.module";
    
    @NgModule({
      imports: [
        CommonModule,
        SharedModule,
        ContactManagerRoutingModule
      ]
    })
    export class ContactManagerModule { }
    تا اینجا پیش از ادامه‌ی کار، فرمان ng serve -o را صادر کنید تا مطمئن شویم همه چیز به درستی قابل دریافت و کامپایل است.


    پس از اجرای برنامه مشاهده می‌کنید که ابتدا ماژول مدیریت تماس‌ها بارگذاری شده‌است و سپس contact-manager-app عمل و sidenav را بارگذاری کرده و آن نیز سبب نمایش کامپوننت toolbar و سپس main-content شده‌است.


    تنظیم طرحبندی برنامه توسط کامپوننت‌های Angular Material و همچنین Flex Layout


    پس از این تنظیمات اکنون نوبت به تنظیم طرحبندی برنامه‌است و آن‌را با قراردادن کامپوننت Sidenavبسته‌ی Angular Material شروع می‌کنیم:
    <mat-sidenav-container *ngIf="shouldRun"><mat-sidenav mode="side" opened>
          Sidenav content</mat-sidenav>
       Primary content</mat-sidenav-container>
    از کامپوننت Sidenav عموما برای طراحی منوی راهبری سایت استفاده می‌شود. این کامپوننت در سه حالت قابل تنظیم است که بر روی نحوه‌ی نمایش Sidenav content و Primary content تاثیرگذار است:
    - Over: قسمت Sidenav content بر روی Primary content قرار می‌گیرد.
    - Push: قسمت Sidenav content قسمت Primary content را از سر راه خود بر می‌دارد.
    - Side:  قسمت Sidenav content در کنار Primary content قرار می‌گیرد.

    در اینجا از حالت Side، در صفحات نمایشی بزرگ (اولین تصویر این قسمت) و از حالت Over، در صفحات نمایشی موبایل (مانند تصویر زیر) استفاده خواهیم کرد.



    در ابتدا کدهای کامل هر سه کامپوننت و سپس توضیحات آن‌ها را مشاهده خواهید کرد:


    تنظیم margin در CSS اصلی برنامه

    زمانیکه sidenav و toolbar را بر روی صفحه قرار می‌دهیم، فاصله‌ای بین آن‌ها و لبه‌های صفحه مشاهده می‌شود. برای اینکه این فاصله را به صفر برسانیم، به فایل src\styles.css مراجعه کرده و margin بدنه‌ی صفحه را به صفر تنظیم می‌کنیم:
    @import "~@angular/material/prebuilt-themes/indigo-pink.css";
    
    body {
      margin: 0;
    }


    طراحی قالب main content

    <mat-card><h1>Main content</h1></mat-card>
    برای نمایش main-content از کامپوننت ساده‌ی cardاستفاده شده‌است که به فایل main-content\main-content.component.html اضافه خواهد شد. این قسمت در نهایت توسط router-outlet نمایش داده می‌شود.


    طرای منوی راهبری واکنشگرا

    sidenav\sidenav.component.css 
     sidenav\sidenav.component.html 
    .app-sidenav-container {
      position: fixed;
    }
    
    .app-sidenav {
      width: 240px;
    }
    
    .wrapper {
      margin: 50px;
    }
    <mat-sidenav-container fxLayout="row"
             fxFill><mat-sidenav #sidenav 
        fxFlex="1 1 100%" [opened]="!isScreenSmall"
        [mode]="isScreenSmall ? 'over' : 'side'"><mat-toolbar color="primary">
          Contacts</mat-toolbar><mat-list><mat-list-item>Item 1</mat-list-item><mat-list-item>Item 2</mat-list-item><mat-list-item>Item 3</mat-list-item></mat-list></mat-sidenav><mat-sidenav-content fxLayout="column" fxFlex="1 1 100%" fxFill><app-toolbar (toggleSidenav)="sidenav.toggle()"></app-toolbar><div><router-outlet></router-outlet></div></mat-sidenav-content></mat-sidenav-container>

    - نمای کلی صفحه در این قسمت طراحی شده‌است. sidenav-container که در برگیرنده‌ی اصلی است، به fxLayout از نوع row تنظیم شده‌است. یعنی mat-sidenav و mat-sidenav-content دو ستون آن‌را از چپ به راست تشکیل می‌دهند و درون یک ردیف، سیلان خواهند یافت. همچنین می‌خواهیم این container کل صفحه را پر کند، به همین جهت از fxFill استفاده شده‌است. این fxFill اعمال شده‌ی به container، زمانی عمل خواهد کرد که position آن در css، به fixed تنظیم شود که اینکار در css این قالب و در کلاس app-sidenav-container آن انجام شده‌است.
    - سپس toolbar و همچنین router-outlet که main content را نمایش می‌دهند، داخل sidenav-content قرار گرفته‌اند و هر دو با هم، ستون دوم این طرحبندی را تشکیل می‌دهند. به همین جهت fxLayout آن به column تنظیم شده‌است (ستون اول آن، لیست تماس‌ها است و ستون دوم آن، از دو ردیف toolbar و main-content تشکیل می‌شود).
    - اگر دقت کنید یک template reference variable به نام sidenav# به container اعمال شده‌است. از آن، جهت باز و بسته کردن sidenav استفاده می‌شود:
    <app-toolbar (toggleSidenav)="sidenav.toggle()"></app-toolbar>
    زمانیکه در toolbar بر روی دکمه‌ی منوی همبرگری کلیک شود، متد sidenav.toggle فراخوانی شده و این مورد سبب نمایان شدن مجدد sidenav خواهد شد. در این مورد در ادامه بیشتر بحث می‌کنیم.
    - mat-sidenav از دو قسمت تشکیل شده‌است. بالای آن توسط mat-toolbar صرفا کلمه‌ی Contacts نمایش داده می‌شود و سپس ذیل آن، لیست فرضیتماس‌ها توسط کامپوننت mat-list قرار گرفته‌اند (تا فعلا خالی نباشد. در قسمت‌های بعدی آن‌را پویا خواهیم کرد). رنگ تولبار آن‌را ("color="primary) نیز به primary تنظیم کرده‌ایم تا خاکستری پیش‌فرض آن نباشد.
    - کار کلاس mat-elevation-z10این است که بین sidenav و main-content یک سایه‌ی سه بعدی را ایجاد کند که آن‌را در تصاویر مشاهده می‌کنید. عددی که پس از z قرار می‌گیرد، میزان عمق سایه را مشخص می‌کند.
    - این قسمت از sidenav به همراه دو خاصیت opened و همچنین mode است که به مقدار isScreenSmall عکس العمل نشان می‌دهند:
    <mat-sidenav  [opened]="!isScreenSmall" [mode]="isScreenSmall ? 'over' : 'side'">
    در اینجا می‌خواهیم اگر اندازه‌ی صفحه xs شد، حالت over بجای حالت پیش‌فرض side تنظیم شود. یعنی در حالت موبایل و اندازه‌ی صفحه‌ی کوچک، sidenav در صورت فراخوانی متد sidenav.toggle در toolbar، بر روی قسمتی از صفحه ظاهر شود و نه در کنار آن که مخصوص حالت تمام صفحه است. همچنین می‌خواهیم اگر اندازه‌ی صفحه کوچک بود، sidenav بسته شود و نمایان نباشد. به همین جهت خاصیت opened آن به isScreenSmall تنظیم شده‌است. مدیریت خاصیت isScreenSmall در کدهای این کامپوننت به صورت زیر انجام می‌شود:

    محتویات فایل sidenav\sidenav.component.ts:
    import { Component, OnDestroy, OnInit } from "@angular/core";
    import { MediaChange, ObservableMedia } from "@angular/flex-layout";
    import { Subscription } from "rxjs";
    
    @Component({
      selector: "app-sidenav",
      templateUrl: "./sidenav.component.html",
      styleUrls: ["./sidenav.component.css"]
    })
    export class SidenavComponent implements OnInit, OnDestroy {
    
      isScreenSmall = false;
      watcher: Subscription;
    
      constructor(private media: ObservableMedia) {
        this.watcher = media.subscribe((change: MediaChange) => {
          this.isScreenSmall = change.mqAlias === "xs";
        });
      }
    
      ngOnInit() {
      }
    
      ngOnDestroy() {
        this.watcher.unsubscribe();
      }
    }
    ObservableMediaرا در انتهای قسمت دوماین سری بررسی کردیم. کار آن گوش فرادادن به تغییرات اندازه‌ی صفحه‌است. زمانیکه mqAlias آن برای مثال مساوی xs شد، یعنی در حالت موبایل قرار داریم. در این حالت مقدار خاصیت isScreenSmall به true تنظیم می‌شود و برعکس. با توجه به اینکه این media یک Observable است، نیاز است کار unsubscribe از آن نیز همواره در کدها وجود داشته باشد که نمونه‌ای از آن در متد ngOnDestroy صورت گرفته‌است.
    تاثیر خاصیت isScreenSmall بر روی دو خاصیت opened و mode کامپوننت sidenav را در دو تصویر زیر مشاهده می‌کنید. اگر اندازه‌ی صفحه کوچک شود، ابتدا sidenav مخفی می‌شود. اگر کاربر بر روی دکمه‌ی منوی همبرگری کلیک کند، سبب نمایش مجدد sidenav، اینبار با حالت over و بر روی محتوای زیرین آن خواهد شد:




    طراحی نوار ابزار واکنشگرا

    کدهای قالب و css تولبار (ستون دوم طرحبندی کلی صفحه) را در ادامه مشاهده می‌کنید:
      toolbar\toolbar.component.css   toolbar\toolbar.component.html 
     
    .sidenav-toggle {
      padding: 0;
      margin: 8px;
      min-width:56px;
    }
     
    <mat-toolbar color="primary"><button mat-button fxHide fxHide.xs="false" 
                  class="sidenav-toggle" (click)="toggleSidenav.emit()"><mat-icon>menu</mat-icon></button><span>Contact Manager</span></mat-toolbar>

    با توجه به استفاده‌ی از fxHide، یعنی دکمه‌ی نمایش منوی همبرگری در تمام حالات مخفی خواهد بود. برای لغو آن و نمایش آن در حالت موبایل، از حالت واکنشگرای آن یعنی fxHide.xs استفاده می‌کنیم (قسمت «کار با API واکنشگرای Angular Flex layout» در مطلب قبلیاین سری). به این ترتیب زمانیکه کاربر اندازه‌ی صفحه را کوچک می‌کند و یا اندازه‌ی واقعی صفحه‌ی نمایش او کوچک است، این دکمه نمایان خواهد شد.
    همچنین در sidenav یک چنین تعریفی را داریم:
    <app-toolbar (toggleSidenav)="sidenav.toggle()"></app-toolbar>
    بروز رخ‌داد toggleSidenav سبب خواهد شد که متد sidenav.toggle فراخوانی شود و سبب نمایش sidenav در اندازه‌های کوچک صفحه‌ی نمایشی گردد. این رخ‌داد سفارشی را نیز به رخ‌داد click دکمه‌ی همبرگری تولبار متصل کرده‌ایم که با کلیک بر روی آن، کار emit آن صورت می‌گیرد. این emit نیز سبب خواهد شد تا sidenav.toggle متصل به سمتی دیگر، فعال شود. نحوه‌ی تعریف این رخ‌داد سفارشی را در کدهای کامپوننت تولبار، در ادامه مشاهده می‌کنید:

    محتویات فایل toolbar\toolbar.component.ts:
    import { Component, EventEmitter, OnInit, Output } from "@angular/core";
    
    @Component({
      selector: "app-toolbar",
      templateUrl: "./toolbar.component.html",
      styleUrls: ["./toolbar.component.css"]
    })
    export class ToolbarComponent implements OnInit {
    
      @Output() toggleSidenav = new EventEmitter<void>();
    
      constructor() { }
    
      ngOnInit() {
      }
    }


    کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MaterialAngularClient-02.zip
    برای اجرای آن نیز ابتدا فایل restore.bat و سپس فایل ng-serve.bat را اجرا کنید. پس از اجرای برنامه، یکبار آن‌را در حالت تمام صفحه و بار دیگر با کوچک‌تر کردن اندازه‌ی مرورگر آزمایش کنید. در حالتیکه به اندازه‌ی موبایل رسیدید، بر روی دکمه‌ی همبرگری نمایان شده کلیک کنید تا عکس العمل آن و نمایش مجدد sidenav را در حالت over، مشاهده نمائید.
    Viewing all 1980 articles
    Browse latest View live


    <script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>