Skip to content

10. [TD]: پیاده‌سازی لایه [ui] با رابط کاربری Swing

کلیدواژه‌ها: معماری چندلایه، اسپرینگ، تزریق وابستگی، کتابخانهٔ کامپوننت Swing.

10.1. Support

در لایه [UI]، می‌خواهیم یک رابط کاربری گرافیکی Swing بسازیم. NetBeans ابزاری به نام [Matisse] برای ساخت این رابط‌های Swing دارد که از آنچه Eclipse ارائه می‌دهد برتر است. رابط‌های Swing در حال جایگزینی با رابط‌های JavaFx هستند. NetBeans و Eclipse از همان ابزار برای ساخت مورد دوم استفاده می‌کنند. بنابراین، اگر رابط‌های JavaFx را بسازیم، می‌توانیم Eclipse را در سراسر معماری لایه‌ای حفظ کنیم.

NetBeans می‌تواند هر پروژه Maven را باز کند. بنابراین، ما از پروژه Maven قبلی استفاده کرده و یک رابط Swing به آن اضافه می‌کنیم. در [2]، ما پروژه‌های Maven مربوط به سه لایه‌ای را که با Eclipse ساخته‌ایم، بارگذاری می‌کنیم (File / Open project). سپس باینری‌های [3] آن‌ها را می‌سازیم. گزینه‌های [Build] و [Clean and Build] باینری را برای پروژه‌ای که روی آن اعمال شده‌اند، ساخت می‌کنند. این باینری‌ها در پوشه [target] [4-5] پروژه قرار می‌گیرند:

گزینه [Clean] این پوشه [target] را حذف می‌کند. گزینه [Build] آن را دوباره می‌سازد. تجربه نشان می‌دهد که وقتی مشکلات غیرمنتظره‌ای پیش می‌آید، اولین کاری که باید انجام دهید این است که یک [Clean and Build] را روی پروژه اجرا کنید تا اطمینان حاصل کنید که با آخرین نسخه آن کار می‌کنید. این امر به‌ویژه زمانی ضروری است که فایل‌های پیکربندی دارید که در صورت تغییر، هنگام اجرای پروژه باعث بازکامپایل خودکار نمی‌شوند. سپس باید با استفاده از یک [Clean and Build] این بازگردانی کامپایل را به اجبار انجام دهید تا نسخه‌های جدید در پوشه [target] نصب شوند.

10.2. نحوه کار برنامه

بیایید به معماری کلی برنامه [Elections] بازگردیم:

اکنون در حال بررسی پیاده‌سازی جدید لایه [ui] هستیم. تنها پیاده‌سازی ایجادشده تاکنون یک رابط کنسول است. اکنون در حال ایجاد یک رابط کاربری گرافیکی هستیم.

کاربر رابط زیر را برای تعامل با برنامه [Elections] در اختیار خواهد داشت:

رابط کاربری گرافیکی در لایه [ui] قرار دارد. این لایه با کاربر تعامل می‌کند.

  • در هنگام راه‌اندازی، برنامه کنسولی [main] سه لایه برنامه را با استفاده از Spring ایجاد می‌کند. این کار حتی قبل از اینکه رابط کاربری گرافیکی قابل مشاهده شود، انجام می‌شود. همچنین در طول این مرحلهٔ راه‌اندازی، جزئیات مربوط به انتخابات (تعداد کرسی‌های قابل پر شدن، آستانهٔ انتخاباتی، فهرست‌های رقیب) از لایهٔ [dao] درخواست می‌شود. اگر این مرحلهٔ راه‌اندازی با شکست مواجه شود (برای مثال، اگر به داده‌ها دسترسی نداشته باشد)، یک پیام خطا روی کنسول نمایش داده می‌شود و رابط کاربری گرافیکی نمایش داده نمی‌شود.
  • اگر داده‌ها با موفقیت خوانده شوند، رابط کاربری گرافیکی با اطلاعات زیر نمایش داده می‌شود (به اسکرین‌شات بالا مراجعه کنید):
    • تعداد کرسی‌های قابل پر شدن (۲)
    • آستانهٔ انتخاباتی در (۳)
    • شناسه و نام فهرست‌های نامزدها در (4)
  • کاربر سپس با استفاده از فیلدهای ۴ (شناسه – نام)، ۵ (رأی‌ها) و ۶ (برای افزودن)، تعداد آرای خود را به هر فهرست نامزد تخصیص می‌دهد.

Image

  • لینک (10) سپس می‌تواند برای محاسبه کرسی‌ها استفاده شود:

Image

  • لینک [Enregistrer] (12) امکان ذخیره نتایج در منبع داده را فراهم می‌کند.

10.3. کلاس [ElectionsSwing] که لایه [ui] را پیاده‌سازی می‌کند

10.3.1. پروژه NetBeans

توجه: بخش 22.4 نحوه دریافت NetBeans را توضیح می‌دهد.

پروژه نهایی NetBeans برای برنامه [1] نام‌گذاری خواهد شد. آن را با دنبال کردن مراحل در [2-5] بسازید:

اطمینان حاصل کنید که پروژه برای کامپایل شدن توسط JDK 1.8 [1-6] پیکربندی شده باشد:

10.3.2. پیکربندی Maven

پروژه جدید [elections-swing-metier-dao-jdbc] بر پایه پروژه قبلی [elections-console-metier-dao-jdbc] ساخته خواهد شد. برای این کار، وابستگی Maven را به شرح زیر اضافه کنید: [1-3]:

10.3.3. ساخت رابط کاربری گرافیکی

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

  • [1]: افزودن یک شیء به بسته [elections.ui.service]
  • [2]: انتخاب گزینه [JFrame Form] از دسته‌بندی [Swing GUI Forms]
  • [4]: نام‌گذاری کلاس
  • [5]: بسته کلاس.
  • جادوگر را تکمیل کنید.
  • [6]: کلاس تولیدشده
  • [7]: کلاس [AbstractElectionsSwing] در حالت [Design]
  • [8]: زبانه [Navigator] که درخت [9] اجزای پنجره را نمایش می‌دهد
  • [10]: زبانه [Properties] که ویژگی‌های کامپوننت [jFrame] انتخاب‌شده در [9] را نمایش می‌دهد
  • [11]: [JFrame] یک محفظهٔ مؤلفه است. این مؤلفه‌ها می‌توانند در داخل محفظه طبق قواعد مختلف چیدمان که به نام layouts شناخته می‌شوند، مرتب شوند. در اینجا، ما layout، [Free Design] و [14] را انتخاب می‌کنیم که به اجزا اجازه می‌دهند آزادانه در داخل محفظه قرار گیرند.

ما می‌توانیم این اجزا را در نوار ابزار با نام Palette بیابیم:

  • [1]: پالت
  • [2]: یک کامپوننت JLabel در محفظهٔ کامپوننت قرار داده شده است
  • با کلیک راست روی آن، می‌توانید به ویژگی‌های مختلفی دسترسی پیدا کنید: نام آن [4]، متن آن [3] یا دستگیرکننده‌های رویداد آن [5]. ما از [3] برای تنظیم متن [6] استفاده می‌کنیم.
  • [1]: برگه [Properties] از مؤلفه [JLabel] دسترسی به ویژگی‌های آن را فراهم می‌کند: موقعیت افقی آن [2]، موقعیت عمودی [3]، قلم متن [4] و متن [5].

وقتی یک کامپوننت در رابط کاربری گرافیکی رها شده و پیکربندی می‌شود، و کار ذخیره می‌شود (Ctrl-S)، کد در کلاس [AbstractElectionsSwing] تولید می‌شود:

 
 

این کد خاکستری‌شده را ویرایش نکنید، زیرا هر بار که فایل را ذخیره می‌کنید، پاک شده و دوباره تولید می‌شود. هرگونه تغییری که انجام دهید از بین خواهد رفت.

یک آموزش برای ایجاد رابط کاربری گرافیکی با NetBeans را می‌توانید در URL [https://netbeans.org/kb/docs/java/quickstart-gui.html?print=yes#design] (نوامبر ۲۰۱۵) بیابید.

اکنون رابط کاربری زیر را خواهیم ساخت:

اجزای رابط کاربری به شرح زیر هستند:

خیر.
نوع
نام
نقش
1
JMenuBar
jMenuBar1
یک منو
2
JLabel
jLabelSAP
تعداد صندلی‌های قابل پر شدن
3
JLabel
jLabelSE
آستانهٔ انتخاباتی
4
JComboBox
jComboBoxNomsListes
فهرست اسامی فهرست‌های رقیب
5
JTextField
jTextFieldVoixListe
تعداد آراء برای یک فهرست
6
JLabel
jLabelAjouter
برای افزودن یک لیست به (8)
7,8
(JScrollPane, JList)
jListNomsVoix
نام‌ها و صداهای فهرست‌ها
9
JLabel
jLabelSupprimer
برای حذف از (8) فهرست انتخاب‌شده در (8)
10
JLabel
jLabelCalculer
برای محاسبه نتایج انتخابات
11
JLabel
jLabelEffacer
برای پاک کردن نتایج انتخابات
12
JLabel
jLabelEnregistrer
ثبت نتایج انتخابات
13,14
(JScrollPane, JList)
jListResultats
برای نمایش نتایج انتخابات
15,16
(JScrollPane,
JTextPane)
jTextPaneMessages
برای مشاهده پیام‌های پیگیری

توضیح (JScrollPane, JList) [13-14] برای نشان دادن این است که وقتی یک مؤلفه [JList] به داخل پنجره کشیده می‌شود، به‌طور خودکار در یک مؤلفه [JScrollPane] درج می‌شود که امکان پیمایش در لیست را فراهم می‌کند. این کامپوننت [JScrollPane] است که امکان مشاهده همه موارد موجود در لیست را فراهم می‌کند، در حالی که در هر لحظه تنها تعداد محدودی از آن‌ها قابل مشاهده هستند. همین امر در مورد کامپوننت‌های [JTextPane] و [15-16] نیز صدق می‌کند.

منو را می‌توان به صورت زیر ایجاد کرد:

  • [1, 2]: یک کامپوننت [Menu Bar] روی پنجره قرار می‌گیرد
  • [3]: منوی تولیدشده به‌صورت پیش‌فرض، همان‌طور که در برگه [Navigator] نشان داده شده است
  • [4,5,6]: با کلیک راست روی یک گزینه منو، می‌توانید:
    • متن آن را ([4]) یا نام آن را ([5]) تغییر دهید
    • رویدادهای آن را مدیریت کنید [6]
  • [7]: منوی مورد نظر

منوی مورد نظر به شرح زیر است:

سطح ۱
سطح ۲
انتخابات
 
 
Quitter
فهرست‌ها
 
 
Ajouter
 
Supprimer
نتایج
 
 
Calculer
 
Effacer
 
Enregistrer
درباره
 

شما می‌توانید رابط کاربری گرافیکی را در هر زمان آزمایش کنید:

 

هنگام ساخت رابط کاربری، باید یک رویدادپرداز به برخی از برچسب‌ها و منوها اختصاص دهید ([Ajouter, Effacer, ...]). در اینجا نحوه انجام آن آمده است:

  • [1]: روی کامپوننتی که می‌خواهید رویداد آن را مدیریت کنید، کلیک راست کنید
  • [2]: گزینه [Events] را انتخاب کنید
  • [3]: یک دسته‌بندی رویداد را انتخاب کنید
  • [4]: رویدادی را که می‌خواهید مدیریت کنید انتخاب کنید

کد جاوا تولید شده توسط این عملیات به شرح زیر است:


    jLabelCalculer.addMouseListener(new java.awt.event.MouseAdapter() {
      public void mouseClicked(java.awt.event.MouseEvent evt) {
        jLabelCalculerMouseClicked(evt);
      }
    });
...

  private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
    //TODO کد رسیدگی خود را اینجا اضافه کنید:
}
  • خطوط ۱–۵: یک رویدادپرداز به کامپوننت jLabelCalculer اضافه می‌شود. متد addMouseListener به‌عنوان پارامتر، کلاسی را انتظار دارد که رابط زیر MouseListener را پیاده‌سازی کند:
 

رابط MouseListener توسط کلاس‌های مختلفی، از جمله کلاس MouseAdapter پیاده‌سازی شده است. این کلاس پنج متد رابط MouseListener را پیاده‌سازی می‌کند، اما این متدها هیچ کاری انجام نمی‌دهند. بنابراین باید از این کلاس ارث‌بری کنید تا متد(های) دلخواه خود را پیاده‌سازی کنید. این کار در کد بالا انجام شده و در زیر نشان داده شده است:


    jLabelCalculer.addMouseListener(new java.awt.event.MouseAdapter() {
      public void mouseClicked(java.awt.event.MouseEvent evt) {
        jLabelCalculerMouseClicked(evt);
      }
});

کد بالا از تکنیک کلاس ناشناس که در بخش 2.5 دوره [ref1] توضیح داده شده است، استفاده می‌کند.

خط ۱: پارامتر متد addMouseListener یک کلاس ناشناس است که به‌صورت پویا تعریف می‌شود. این یک نمونه از کلاسی است که از کلاس MouseAdapter (خط ۱) ارث‌بری شده است، که در آن متد mouseClicked (خطوط ۲–۴) بازنویسی شده تا یک عمل خاص را انجام دهد.

روش jLabelCalculerMouseClicked، که به آن خط ۳ گفته می‌شود، به شرح زیر تعریف شده است:


  private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
    // TODO کد رسیدگی خود را اینجا اضافه کنید:
}

توسعه‌دهنده رویداد «MouseClicked» را با افزودن کد به این متد مدیریت می‌کند.

تمام رویدادپردازها به این صورت توسط NetBeans تولید می‌شوند. توسعه‌دهنده می‌تواند خطوط کدی را که NetBeans برای مرتبط کردن یک متد با رویداد یک کامپوننت تولید می‌کند، نادیده بگیرد. او می‌تواند به سادگی کد خود را در خط ۲ بالا قرار دهد. در اینجا یک مثال آمده است:


  private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
    System.out.println("Mouse Clicked");
}

اگر رابط کاربری گرافیکی (GUI) را اجرا کرده و روی لینک [Calculer] کلیک کنید، پیامی در کنسول ظاهر می‌شود:

  • [1]: روی برچسب [Calculer] دوبار کلیک کنید
  • [2]: رسیدگی‌کننده این رویداد اجرا شد و پیام‌های mouseClicked را در کنسول NetBeans تولید کرد.

کامپوننت‌های [jComboBoxNomsListes, jListNomsVoix, jListResultats] به شرح زیر اعلام شده‌اند:


protected javax.swing.JComboBox jComboBoxNomsListes;
protected javax.swing.JList jListNomsVoix;
protected javax.swing.JList jListResultats;

این کامپوننت‌ها لیست‌هایی هستند که معمولاً با نوع T پیکربندی می‌شوند: نوع عناصر مدل نمایش‌داده‌شده توسط این کامپوننت‌ها. این نوع T می‌تواند هر نوع دلخواهی باشد. مقدار نمایش‌داده‌شده در کامپوننت لیست از نوع [String] است. به‌طور پیش‌فرض، متد [T.toString()] برای نمایش استفاده می‌شود. برای داشتن کنترل بیشتر بر روی آنچه نمایش داده می‌شود، نوع T در اینجا از نوع String خواهد بود. بنابراین، اعلامیه‌ی صحیح لیست‌های ما به شرح زیر است:


protected javax.swing.JComboBox<String> jComboBoxNomsListes;
protected javax.swing.JList<String> jListNomsVoix;
protected javax.swing.JList<String> jListResultats;

این نتیجه با تغییر یکی از ویژگی‌های کامپوننت حاصل می‌شود:

10.3.4. جداسازی کد

بیایید به ساختار برنامه خود بازگردیم:

کلاس [AbstractElectionsSwing] باید لایه [ui] را که در بالا نشان داده شده است پیاده‌سازی کند. کد آن که توسط NetBeans تولید شده، در حال حاضر تنها شامل کد مدیریت پنجره و رویدادپردازها است که فعلاً هیچ کاری انجام نمی‌دهند. همانطور که در بالا نشان داده شده است، کلاس [AbstractElectionsSwing] باید تعاملات با لایه [métier] را مدیریت کند. این کار در داخل رویدادپردازها انجام خواهد شد. برای روشن‌تر کردن ساختار کد، ما تصمیم گرفته‌ایم آن را به دو کلاس تقسیم کنیم:

  • [AbstractElectionsSwing]، که به جز چند جزئیات جزئی، همان‌طور که توسط NetBeans تولید شده باقی خواهد ماند. این کلاس خود هیچ رویدادی را مدیریت نخواهد کرد. دست‌گیرنده‌های رویداد خالی و به صورت انتزاعی (abstract) اعلام خواهند شد. آن‌ها توسط کلاسی که از [AbstractElectionsSwing] مشتق شده است، پیاده‌سازی خواهند شد.
  • [ElectionsSwing]، کلاسی مشتق شده از [AbstractElectionsSwing]، که تمام رویدادها را پیاده‌سازی خواهد کرد.

این نوع تفکیک غیرمعمول نیست. برای مثال، می‌توان آن را در صفحات وب ASP.NET (نسخه غیر MVC) یافت. پروژه NetBeans به شرح زیر در حال تکامل است:

 

کد کلاس [AbstractElectionsSwing] در همین حال به شرح زیر در حال تکامل است:


public abstract class AbstractElectionsSwing {
....
    private void jMenuItemCalculerActionPerformed(java.awt.event.ActionEvent evt) {
        doCalculer();
    }

...

    private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
        if (jLabelCalculer.isEnabled()) {
            doCalculer();
        }
    }

....
    // پردازشگرهای رویداد
    abstract protected void doSupprimer();

    abstract protected void doCalculer();

    abstract protected void doQuitter();

    abstract protected void doEffacer();

    abstract protected void doEnregistrer();

    abstract protected void doAjouter();

    abstract protected void doInformer();

    abstract protected void doMajLabelAjouter();

    abstract protected void doMajLabelSupprimer();
...
}
  • خط ۱: کلاس به صورت انتزاعی (abstract) تعریف شده است
  • خطوط ۳–۵: رسیدگی به کلیک روی گزینه منوی [jMenuItemCalculer]. می‌توانیم ببینیم که رسیدگی به رویداد به متد doCalculer در خط ۱۹ واگذار شده است. این متد پیاده‌سازی نشده و به صورت انتزاعی (abstract) اعلام شده است. این کلاس مشتق [ElectionsSwing] است که آن را پیاده‌سازی خواهد کرد؛
  • خطوط ۹–۱۳: رویدادپرداز clic برای برچسب [jLabelCalculer]. یک کلیک همیشه یک رویداد را فعال می‌کند، صرف‌نظر از اینکه کامپوننت [jLabel] فعال (enabled=true) باشد یا غیرفعال (enabled=false). در اینجا، ما اطمینان حاصل می‌کنیم که برای رسیدگی به رویداد، این کامپوننت واقعاً فعال باشد؛
  • از خط ۱۵ به بعد: این تکنیک واگذاری رسیدگی به رویداد به یک متد انتزاعی برای همهٔ رسیدگیرهای رویداد اعمال می‌شود.

کلاس [ElectionsSwing] که از [AbstractElectionsSwing] مشتق شده است، تمام رویدادپردازها را که توسط [AbstractElectionsSwing] پیاده‌سازی نشده‌اند، پیاده‌سازی می‌کند:


package elections.ui.service;;
...
public class ElectionsSwing extends AbstractElectionsSwing {

    // پردازشگرهای رویداد

    @Override
    protected void doInformer() {
...
    }

    @Override
    protected void doAjouter() {
    ...
    }

    @Override
    protected void doCalculer() {
    ...
    }

    @Override
    protected void doEffacer() {
    ...
    }

    @Override
    protected void doEnregistrer() {
...
    }

    @Override
    protected void doQuitter() {
        System.exit(0);
    }

    @Override
    protected void doSupprimer() {
...
    }

    @Override
    protected void doMajLabelAjouter() {
...
    }

    @Override
    protected void doMajLabelSupprimer() {
...
    }

}
  • خط ۳: کلاس [ElectionsSwing] از کلاس [AbstractElectionsSwing] مشتق شده است
  • خطوط ۷–۵۰: رویدادپردازها برای پنجرهٔ گرافیکی

متدهای کلاس مشتق [ElectionsSwing]، کامپوننت‌های کلاس والد [AbstractElectionsSwing] را دستکاری خواهند کرد. در حال حاضر، این کامپوننت‌ها در محدودهٔ private قرار دارند و مانع دسترسی کلاس فرزند [ElectionsSwing] به آن‌ها می‌شوند:


private JMenuItem jMenuItemAPropos = null;

private JLabel jLabelAjouter = null;

برای حل این مشکل، ما اطمینان حاصل می‌کنیم که دامنهٔ کامپوننت‌های رابط کاربری [protected] باشد:

  • ویژگی [protected] را روی [3] تنظیم کنید؛

10.3.5. پیاده‌سازی رابط [IElectionsUI]

بیایید به ساختار برنامه خود بازگردیم:

در بالا، لایه [ui] باید رابط [IElectionsUI] را به شیء [main] ارائه دهد:


package elections.ui.service;

public interface IElectionsUI {
    /**
     * lance le dialogue avec l'utilisateur
     */
    public void run();
}

این رابط در پروژه [elections-console-metier-dao-jdbc] تعریف شده و در بخش 9.4 شرح داده شده است. از آنجایی که این پروژه یک وابستگی پروژه [swing] است، این رابط شناخته‌شده است.

از آنجایی که کلاس [AbstractElectionsSwing] به یک کلاس انتزاعی تبدیل شده است، دیگر نمی‌توان آن را توسط Spring نمونه‌سازی کرد. اکنون این کلاس [ElectionsSwing] است که باید نمونه‌سازی شود. کلاس [ElectionsSwing] باید رابط [IElectionsUI] را پیاده‌سازی کند. بنابراین کد آن به شرح زیر تغییر می‌کند:


public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {

    // روش اجرای رابط [ElectionsUI]
    public void run() {
...
    }

  • خط ۱: کلاس [ElectionsSwing] رابط [IElectionsUI] را پیاده‌سازی می‌کند
  • خطوط ۴–۶: متد [run] این رابط

متد run باید چه کاری انجام دهد؟ نمایش پنجره گرافیکی. چگونه این کار را انجام دهیم؟ می‌توانیم از متد [main] که NetBeans در کلاس [AbstractElectionsSwing] تولید کرده است الگو بگیریم و دقیقاً همان کاری را انجام می‌دهد که مورد نیاز است:


public static void main(String args[]) {
    /* نمای نیمبوس را تنظیم کنید */
    //<editor-fold defaultstate="collapsed" desc="کد تنظیم ظاهر و حس (اختیاری)">
    /* اگر Nimbus (معرفی‌شده در Java SE 6) در دسترس نباشد، از ظاهر و حس پیش‌فرض استفاده کنید.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
     */
    try {
      for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
        if ("Nimbus".equals(info.getName())) {
          javax.swing.UIManager.setLookAndFeel(info.getClassName());
          break;
        }
      }
    } catch (ClassNotFoundException ex) {
      java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
    } catch (InstantiationException ex) {
      java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
    } catch (IllegalAccessException ex) {
      java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
    } catch (javax.swing.UnsupportedLookAndFeelException ex) {
      java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
    }
    //</editor-fold>

    /*فرم را ایجاد و نمایش دهید */
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        new NewJFrame().setVisible(true);
      }
    });
  }

سازنده [AbstractElectionsSwing] که در خط ۲۸ استفاده شده است، به شرح زیر است:


  public AbstractElectionsSwing() {
    initComponents();
}
  • خط ۲: متد [initComponents] یک متد خصوصی است که توسط ژنراتور GUI تولید شده است. کد آن قابل تغییر نیست.

روش [run] از کلاس [ElectionsSwing] می‌تواند به صورت زیر باشد:


  @Override
  public void run() {
    // نمایش رابط کاربری گرافیکی
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        init();
        setVisible(true);
      }
    });
}
  • خط ۶: رابط کاربری گرافیکی با استفاده از متد [init] inicialize می‌شود. در اینجا می‌خواهیم متد [initComponents] را از کلاس والد فراخوانی کنیم، اما این متد خصوصی است. بنابراین، متد زیر [init] را به کلاس والد [AbstractElectionsSwing] اضافه می‌کنیم:

  protected void init(){
    initComponents();
}
  • (ادامه)
    • از آنجا که در کلاس [AbstractElectionsSwing] قرار دارد، متد [init] به متد خصوصی [initComponents] همان کلاس دسترسی دارد؛
    • از آنجا که دارای ویژگی [protected] است، در کلاس فرزند [ElectionsSwing] قابل مشاهده است؛
  • خط ۷: رابط کاربری گرافیکی قابل مشاهده می‌شود؛

توجه: به محض اینکه متد [run] در کلاس [ElectionsSwing] نوشته شد، متد [main] از کلاس انتزاعی [AbstractElectionsSwing] را می‌توان حذف کرد.

10.3.6. کلاس قابل اجرا

بیایید به ساختار برنامهٔ خود بازگردیم:

ما می‌خواهیم Spring لایه [ui] را به همان شیوه‌ای که زمانی که توسط برنامه console پیاده‌سازی شده بود، نمونه سازی کند. برای این کار، کلاس پیاده‌سازی [ElectionsSwing] باید مرجعی به لایه [métier] داشته باشد:


@Component
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI{

  // مرجع لایه [métier]
  @Autowired
  private IElectionsMetier metier;
...
  • خط ۱: کلاس [ElectionsSwing] یک کامپوننت Spring است؛
  • خطوط ۵–۶: Spring یک مرجع به لایه [métier] تزریق می‌کند؛

رابط کاربری گرافیکی با اجرای کلاس زیر [BootElectionsSwing] راه‌اندازی می‌شود:

  

package elections.ui.boot;

import elections.ui.service.IElectionsUI;

public class BootElectionsSwing extends AbstractBootElections {
    public static void main(String[] arguments) {
        new BootElectionsSwing().run();
    }

    @Override
    protected IElectionsUI getUI() {
        return ctx.getBean("electionsSwing", IElectionsUI.class);
    }
}

ما کد مشابهی را در بخش 9.5 توضیح دادیم، زمانی که کد کلاس‌های [AbstractBootElections] و [BootElectionsConsole] را بررسی کردیم. در خط ۱۲، ما بیانی به نام [electionsSwing] را بازیابی می‌کنیم که با نام استاندارد Spring برای کلاس [ElectionsSwing] مطابقت دارد.

10.3.7. ابتدایی‌سازی رابط کاربری گرافیکی

هنگامی که رابط کاربری گرافیکی نمایش داده می‌شود، برخی از اجزای آن инициалиزه شده‌اند:

Image

همان‌طور که در بالا نشان داده شده است:

  • که فهرست کشویی با نام‌های فهرست‌ها پر شده است؛
  • که تعداد کرسی‌های قابل پر شدن و آستانه انتخاباتی نمایش داده می‌شوند؛
  • که برخی از پیوندها غیرفعال شده‌اند؛
  • که یک پیام موفقیت در پایین پنجره نمایش داده می‌شود؛

این مقداردهی‌ها چه زمانی انجام می‌شوند؟ این کارها تنها پس از مقداردهی اولیه فیلد [electionsMetier] در کلاس [ElectionsSwing] انجام می‌شوند. دلیل آن این است که نام‌های لیست از لایه [metier] درخواست خواهند شد. اسپرینگ این فیلد را به ترتیب زیر مقداردهی اولیه می‌کند:

  • استفاده از سازنده بدون پارامتر کلاس [ElectionsSwing
  • تزریق وابستگی‌ها، در این مورد مرجع به لایه [métier
  • اجرای متد [run] از کلاس [ElectionsSwing]:

    @Override
    public void run() {
        //رابط کاربری گرافیکی نمایش داده می‌شود
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                init();
                setVisible(true);
            }
        });
}
  • در خط ۱۲، مشخص کردیم که متد [init] از کلاس والد باید فراخوانی شود، که اجزای رابط کاربری گرافیکی (GUI) را رسم می‌کند. ما قصد داریم این متد را به‌صورت محلی در کلاس [ElectionsSwing] بازنویسی کنیم. در داخل این متد است که ما کامپوننت‌های پنجره (کادرهای ترکیبی، برچسب‌ها) را با داده‌ها اولیه می‌کنیم:

روش محلی [init] می‌تواند ساختار زیر را داشته باشد:


public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {

    ...
  // ارجاع به لایه [métier]
  @Autowired
  private IElectionsMetier metier;

    // ابتدایی‌سازی‌ها
    public void init() {
      // کامپوننت‌های تولیدشده توسط کلاس والد
    super.init();
    
    //لیست‌ها از لایه [metier] درخواست می‌شوند
        ...
        //نام‌های فهرست با فهرست کشویی jComboBoxNomsListes مرتبط هستند
        ...
        // و همچنین پارامترهای انتخابات
        ...
        //برچسب‌های مرتبط با این دو مورد اطلاعات مقداردهی اولیه شده‌اند
        ...
        // پیام موفقیت
        ...
        // ابتدایی‌سازی وضعیت برخی از اجزای فرم
...
}

توجه کنید که در خط ۱۱، متد [init] از کلاس والد فراخوانی شده است.

10.3.8. کلاس [Utilitaires]

تعدادی از متدهای ابزار استاتیک در کلاس [Utilitaires] گروه‌بندی شده‌اند:

 

کلاس [Utilitaires] به شرح زیر است:


package istia.st.elections.ui;

import javax.swing.JLabel;
import javax.swing.JMenuItem;

//کلاس ابزار
class Utilitaires {
    // مدیریت وضعیت یک آرایه از برچسب‌ها
    public static void setEnabled(JLabel[] labels, boolean value) {
        for (int i = 0; i < labels.length; i++) {
            labels[i].setEnabled(value);
        }
    }

    //مدیریت وضعیت آرایهٔ گزینه‌های منو
    public static void setEnabled(JMenuItem[] menuItems, boolean value) {
        ...
    }

}
  • خط ۹: متد setEnabled وضعیت اجزای JLabel را که در یک آرایه تعریف شده‌اند، تنظیم می‌کند. متد setEnabled یک کامپوننت JLabel به JLabel اجازه می‌دهد که فعال یا غیرفعال شود.

وظیفه: با پیروی از مثال متد setEnabled در خط ۹ (,)، متد setEnabled را در خط ۱۶ بنویسید که همان کار را با اجزای JMenuItem انجام می‌دهد.


10.3.9. کد کلاس [ElectionsSwing]

بیایید ساختار کلی کلاس [ElectionsSwing] را به یاد بیاوریم:


package istia.st.elections.ui;

...
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {

  // مرجع لایه [métier]
  @Autowired
  private IElectionsMetier metier;


    // ابتدایی‌سازی‌ها
    public void init() {
    ...
    }

    // پردازشگرهای رویداد

    @Override
    protected void doInformer() {
...
    }

    @Override
    protected void doAjouter() {
...
    }

    @Override
    protected void doCalculer() {
    ...
    }

    @Override
    protected void doEffacer() {
...
    }

    @Override
    protected void doEnregistrer() {
...
    }

    @Override
    protected void doQuitter() {
        System.exit(0);
    }

    @Override
    protected void doSupprimer() {
...
    }

    @Override
    protected void doMajLabelAjouter() {
    ...
    }

    @Override
    protected void doMajLabelSupprimer() {
...
    }

}

ما متدهای کلاس را یکی‌یکی بررسی خواهیم کرد.

10.3.9.1. متد [init]

بیایید به رابط کاربری گرافیکی بازگردیم:

اهداف روش [init] عبارتند از:

  • برای پر کردن کادر ترکیبی [4] با شناسه‌ها و نام‌های فهرست‌ها به فرمت [id - nom]
  • نمایش پیام موفقیت در [15]
  • برای مقداردهی اولیه برچسب‌های [2] و [3]
  • برای غیرفعال کردن برخی پیوندها

اسکلت روش [init] می‌تواند به صورت زیر باشد:


@Override
    protected void init() {
        //تولید کامپوننت توسط کلاس والد
        super.init();
        // ابتکاری‌های محلی
        modèleNomsVoix = new DefaultListModel<>();
        jListNomsVoix.setModel(modèleNomsVoix);
        modèleRésultats = new DefaultListModel<>();
        jListResultats.setModel(modèleRésultats);
        String info;
        try {
            // فهرست‌ها از لایه [métier] درخواست می‌شوند
            listes = ...
            // نام‌های لیست با کادر ترکیبی jComboBoxNomsListes مرتبط هستند
            ...
            // و همچنین پارامترهای انتخابات
            int nbSiegesAPourvoir = ...
            double seuilElectoral = ...
            //برچسب‌های مرتبط با این دو بخش اطلاعات مقداردهی اولیه شده‌اند
            ...
            // پیام موفقیت
            info = "Source de données lue avec succès";
        } catch (ElectionsException ex1) {
            //یک خطا ثبت می‌شود
            info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
        } catch (RuntimeException ex2) {
            //خطا ثبت می‌شود
            info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
        }
        //اطلاعات نمایش داده می‌شود
        jTextPaneMessages.setText(info);
        jTextPaneMessages.setCaretPosition(0);
        //وضعیت فرم
        Utilitaires.setEnabled(new JLabel[] { jLabelAjouter, jLabelCalculer, jLabelEnregistrer, jLabelSupprimer }, false);
        Utilitaires.setEnabled(
                new JMenuItem[] { jMenuItemAjouter, jMenuItemCalculer, jMenuItemEnregistrer, jMenuItemSupprimer }, false);
        // مرکز پنجره
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        Dimension frameSize = getSize();
        if (frameSize.height > screenSize.height) {
            frameSize.height = screenSize.height;
        }
        if (frameSize.width > screenSize.width) {
            frameSize.width = screenSize.width;
        }
        setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2);
    }

    private String getInfoForException(String message, ElectionsException ex) {
        // نمایش پیام
        StringBuffer info = new StringBuffer(String.format("%s -------------\n", message));
        info.append(String.format("Code erreur : %d\n", ex.getCode()));
        // نمایش خطاها
        for (String erreur : ex.getErreurs()) {
            info.append(String.format("-- %s\n", erreur));
        }
        return info.toString();
    }

    private String getInfoForException(String message, RuntimeException ex) {
        // نمایش پیام
        StringBuffer info = new StringBuffer(String.format("%s -------------\n", message));
        //نمایش ردیابی استک استثنا
        Throwable cause = ex;
        while (cause != null) {
            info.append(String.format("-- %s\n", cause.getMessage()));
            cause = cause.getCause();
        }
        return info.toString();
    }

وظیفه: کد متد [init] را تکمیل کنید.


مطالب دوره را مطالعه کنید: کامپوننت‌های JTextField، JLabel

توجه:

یک کامپوننت JList داده‌های موجود در یک قالب را نمایش می‌دهد. به‌طور پیش‌فرض، این قالب از نوع DefaultListModel است (خطوط ۲ و ۳). یک شیء DefaultListModel تا حدودی مانند نوع ArrayList رفتار می‌کند:

  • برای افزودن یک شیء o به قالب:

[DefaultListModel].addElement(Object o);

در این برنامه، شیء o همیشه از نوع String خواهد بود.

  • برای حذف عنصر شماره i از قالب:

[DefaultListModel].remove(int i);
  • برای بازیابی عنصر شماره i از قالب:

[DefaultListModel].elementAt(int i);

برای افزودن یک عنصر به مجموعه [jComboBoxNomsListes]، از روش [addItem] استفاده کنید:


jComboBoxNomsListes.addItem(chaîne de caractères)

یک کامپوننت JTextPane دارای متدهای getText() و setText() برای خواندن و نوشتن متن نمایش داده شده است.

10.3.9.2. وضعیت لینک [Ajouter] را مدیریت کنید

لینک [Ajouter] [6] تنها زمانی فعال است که فیلد [5] برای آراء خالی نباشد. در کلاس [AbstractElectionsSwing]، دست‌پردازه‌ای که حرکات مکان‌نما را در فیلد [5] دنبال می‌کند، به شرح زیر است:


  private void jTextFieldVoixListeCaretUpdate(javax.swing.event.CaretEvent evt) {
    doMajLabelAjouter()
}

خط ۲ متد [doMajLabelAjouter] از کلاس [ElectionsSwing] را فراخوانی می‌کند.


    protected void doMajLabelAjouter() {
        // وضعیت برچسب را روی [jLabelAjouter] تنظیم می‌کند
        ...
        //وضعیت منوی [jMenuItemAjouter] را تنظیم می‌کند
        ...
}

وظیفه: کد متد [doMajLabelAjouter] را تکمیل کنید.


10.3.9.3. آرا را به هر فهرست تخصیص دهید

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

  • یک لیست را از (4) انتخاب کنید
  • وارد کردن تعداد آرا در (5)
  • با کلیک بر روی لینک [Ajouter] تأیید کنید

خطاهای ورودی همان‌طور که در مثال زیر نشان داده شده است، برجسته می‌شوند:

Image

اگر تعداد آرا صحیح باشد، فهرست به مؤلفه (8) اضافه می‌شود، تعداد آرا پاک می‌شود و لینک [Ajouter] غیرفعال می‌گردد:

در کلاس [AbstractElectionsSwing]، هاندلری که کلیک روی لینک [Ajouter] را مدیریت می‌کند به شرح زیر است:


    private void jLabelAjouterMouseClicked(java.awt.event.MouseEvent evt) {
        if (jLabelAjouter.isEnabled()) {
            doAjouter();
        }
}

خط ۳ متد [doAjouter] از کلاس [ElectionsSwing] را فراخوانی می‌کند:


  //قالب‌های لیست JList
  private DefaultListModel<String> modèleNomsVoix = null;
  private DefaultListModel<String> modèleRésultats = null;

  // فهرست‌های رقیب
  private ListeElectorale[] listes;

  // فهرست‌های واردشده توسط کاربر
  private final List<ListeElectorale> listesSaisies = new ArrayList<>();
  private ListeElectorale[] tListesSaisies;
...
  @Override
  protected void doAjouter() {
    //آیا تعداد آرا صحیح است؟
    ...
    // اگر خطایی وجود داشته باشد، علامت‌گذاری می‌شود
    if (erreur) {
      JOptionPane.showMessageDialog(null, "Nombre de voix incorrect", "Elections : erreur",
              JOptionPane.INFORMATION_MESSAGE);
      jTextFieldVoixListe.requestFocus();
      //بازگشت به رابط کاربری گرافیکی
      return;
    }
    //خطایی وجود ندارد – لیست را ذخیره کنید
    listesSaisies.add(...);
    modèleNomsVoix.addElement(...);
    // تعداد رأی‌ها را ریست کنید
    jTextFieldVoixListe.setText("");
    //وضعیت فرم (منوها، برچسب‌ها)
...
}
  • خط ۲۵: هر بار که کاربر به فهرستی رأی اضافه می‌کند و انتخاب خود را تأیید می‌کند، این فهرست در فیلد [listesSaisies] در خط ۹ قرار می‌گیرد. این فهرست به همراه اطلاعات [id, version, nom, voix] در آنجا ذخیره خواهد شد. سه مورد اول اطلاعات از لیست‌هایی که در ابتدا در آرایه در خط ۶ ذخیره شده‌اند، گرفته می‌شوند. از متد [getSelectedIndex] کادر ترکیبی برای تعیین نمایه لیست انتخاب‌شده استفاده می‌شود؛

وظیفه: کد متد [doAjouter] را تکمیل کنید.


10.3.9.4. وضعیت لینک [Supprimer] را مدیریت کنید

لینک [Supprimer] [9] تنها زمانی فعال است که یک آیتم در [8] انتخاب شده باشد.

در کلاس [AbstractElectionsSwing]، دست‌پردازه‌ای که به کلیک روی یک آیتم در لیست [8] پاسخ می‌دهد به شرح زیر است:


  private void jListNomsVoixValueChanged(javax.swing.event.ListSelectionEvent evt) {
    doMajLabelSupprimer();
}

خط ۲ متد [doMajLabelSupprimer] از کلاس [ElectionsSwing] را فراخوانی می‌کند.


    @Override
    protected void doMajLabelSupprimer() {
            //برچسب [jLabelSupprimer] و گزینهٔ منوی متناظر فعال هستند
            ...
}

وظیفه: کد متد [doMajLabelSupprimer] را تکمیل کنید.


10.3.9.5. حذف یک فهرست نامزد

لینک [Supprimer] [9] به شما امکان می‌دهد جفت (نام، صدا) انتخاب‌شده در (8) را حذف کنید. پس از انجام حذف، لینک [Supprimer] غیرفعال می‌شود. این لینک تنها پس از انتخاب یک فهرست جدید در (8) دوباره فعال خواهد شد.

در کلاس [AbstractElectionsSwing]، دست‌پردازه‌ای که به کلیک روی لینک [Supprimer] پاسخ می‌دهد به شرح زیر است:


  private void jLabelSupprimerMouseClicked(java.awt.event.MouseEvent evt) {
    if(jLabelSupprimer.isEnabled()){
      doSupprimer();
    }
}

خط ۳ متد [doSupprimer] از کلاس [ElectionsSwing] را فراخوانی می‌کند.


@Override
    protected void doSupprimer() {
        // حذف لیست انتخاب‌شده، قالب modèleNomsVoix و لیست‌های واردشده
        ...
        // به‌روزرسانی وضعیت برچسب‌ها و گزینه‌های منوی فرم
        Utilitaires.setEnabled(...);
    ...
}

وظیفه: کد متد [doSupprimer] را تکمیل کنید.


10.3.9.6. وضعیت لینک [Calculer] را مدیریت کنید

لینک [Calculer] [10] تنها زمانی فعال است که حداقل یک عنصر در [8] وجود داشته باشد.


وظیفه: کد لازم برای مدیریت این لینک را به متدهای [doAjouter] و [doSupprimer] که قبلاً نوشته شده‌اند اضافه کنید. گزینه منوی مربوطه نیز مدیریت خواهد شد.


توجه: تعداد عناصر درون یک عنصر از نوع DefaultListModel با استفاده از متد size() به دست می‌آید.

10.3.9.7. محاسبه صندلی‌ها

لینک [Calculer] [10] به شما امکان می‌دهد محاسبه صندلی‌ها را آغاز کرده و نتایج را در (14) نمایش دهید. اگر محاسبه ناموفق باشد (تمام لیست‌ها حذف شده‌اند)، یک پیام خطا در [15] نمایش داده می‌شود. در همه موارد، پس از اتمام محاسبه، لینک [Calculer] [10] غیرفعال می‌شود.

در کلاس [AbstractElectionsSwing]، هاندلری که به کلیک روی لینک [Calculer] پاسخ می‌دهد به شرح زیر است:


  private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
    if(jLabelCalculer.isEnabled()){
      doCalculer();
    }
}

خط ۳ متد [doCalculer] از کلاس [ElectionsSwing] را فراخوانی می‌کند.


  // فهرست‌های واردشده توسط کاربر
  private final List<ListeElectorale> listesSaisies = new ArrayList<>();
  private ListeElectorale[] tListesSaisies;

...
  @Override
  protected void doCalculer() {
    tListesSaisies = listesSaisies.toArray(new ListeElectorale[0]);
    //تخصیص صندلی
    try {
      ...
    } catch (ElectionsException ex) {
      // استثناء نمایش داده می‌شود
      ...
      return;
    }
    // نمایش نتایج
    ...
    // به‌روزرسانی وضعیت فرم
    Utilitaires.setEnabled(...);
}

وظیفه: کد متد [doCalculer] را تکمیل کنید.


10.3.9.8. نتایج را در منبع داده ذخیره کنید

لینک [Enregistrer] (12) برای ذخیره نتایج محاسبه تخصیص صندلی در منبع داده استفاده می‌شود. پس از اتمام موفقیت‌آمیز ذخیره، لینک [Enregistrer] غیرفعال می‌شود. اگر ذخیره با موفقیت انجام نشود، یک پیام خطا در [15] نمایش داده می‌شود. در هر صورت، لینک [Enregistrer] سپس غیرفعال می‌شود.

در کلاس [AbstractElectionsSwing]، هاندلری که کلیک روی برچسب [Enregistrer] را مدیریت می‌کند به شرح زیر است:


  private void jLabelEnregistrerMouseClicked(java.awt.event.MouseEvent evt) {
    if(jLabelEnregistrer.isEnabled()){
      doEnregistrer();
    }
}

خط ۳ متد [doEnregistrer] از کلاس [ElectionsSwing] را فراخوانی می‌کند:


@Override
    protected void doEnregistrer() {
        // درخواست ذخیره در لایه کسب‌وکار
        try {
            ...
        } catch (ElectionsException ex) {
            // نمایش استثنا
            ...
            //بازگشت به رابط کاربری گرافیکی
            return;
        }
        // به‌روزرسانی فرم
        Utilitaires.setEnabled(...);
...
}

وظیفه: کد متد [doEnregistrer] را تکمیل کنید.


10.3.9.9. پاک کردن نتایج

لینک [Effacer] (۱۱) به شما امکان می‌دهد نتایج نمایش‌داده‌شده در (۱۴) را پاک کنید.

در کلاس [AbstractElectionsSwing]، هندلری که کلیک روی برچسب [Effacer] را مدیریت می‌کند به شرح زیر است:


  private void jLabelEffacerMouseClicked(java.awt.event.MouseEvent evt) {
    if(jLabelEffacer.isEnabled()){
      doEffacer();
    }
}

خط ۳ متد [doEffacer] را از کلاس [ElectionsSwing] فراخوانی می‌کند:


    @Override
    protected void doEffacer() {
        // پاک کردن فهرست نتایج
        ....
        // تازه کردن فرم
        Utilitaires.setEnabled(...);
}

وظیفه: کد متد [doEffacer] را تکمیل کنید.


توجه: کلاس DefaultListModel دارای متدی به نام clear() است که تمام عناصر آن را حذف می‌کند.

10.3.10. بهبودها

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


وظیفه: الگوریتم را طوری بهبود دهید که هیچ‌یک از این دو وضعیت رخ ندهد. یک راه‌حل ساده این است که یک فرهنگ لغت از لیست‌های وارد شده نگهداری کنیم، که در آن کلیدها آیتم‌های کادر ترکیبی هستند. همچنین اطمینان حاصل خواهیم کرد که لینک [Calculer] تنها پس از وارد شدن تمام لیست‌ها فعال شود.


به دوره [ref1] مراجعه کنید: کلاس HashTable در بخش 3.8.