PDA

View Full Version : ضرورت final بودن متغیر برای تعریف actionListener در حلقه



Tiyana
جمعه 19 اردیبهشت 1393, 23:22 عصر
سلام. من چند تا button عدد دارم که اعداد 0 تا 9 رو شامل میشه و برای اونا اکشنی نوشتم که مثلا شماره اون عددو بره توی یه textField چاپ کنه و چون برای هر کدوم این کار باید انجام بشه اکشنو توی یه حلقه گذاشتم اما برای برگرداندن عدد نیاز به شمارنده حلقه (i) هست که باید به textFiled پاس بشه اما کامپایلر ارور میده که متغیر باید final باشه تا بشه این کارو کرد حالا جدا از این که برای هرکدوم جدا یه اکشن بنویسیم چه روشی وجود داره؟




for(int i=1;i<numbers.length;i++)
{
numbers[i]=new JButton(String.format("%d",i));
numbers[i].setBounds(140+a,-5+b,40,30);
numbers[i].addActionListener
(
new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
if(currentFloorTextField.isFocusable())
currentFloorTextField.setText(currentFloorTextFiel d.getText()+String.format("%d",i+1));
}
}
);

vahid-p
شنبه 20 اردیبهشت 1393, 00:17 صبح
خب این روش اشتباهه. میدونی دلیلش چیه؟ هر وقت ازتون خواسته میشه یک متغیر final باشه یا اینکه بهتره final باشه، دلیلش اینه ممکنه یه جایی تو اون متغیر رو تغییر بدی و در صورتی که منظورت از اون i یه چیز دیگه بوده. مخصوصا برای متغیر های local ( که در اینجا مثلا تو خود for فقط معتبر هست ) و وقتی که حلقه for تموم شد، شما فرض کنید که بعدا روی button کلیک کنید و اونوقت این برنامه i از کجا بیاره؟ یا شاید بعدا i یه چیز دیگه تعریف شده باشه و i اصلا اون منظور قبلی رو نداشته باشه. کلا وقتی شما از new ActionListener استفاده میکنید و تابعش رو implement میکنید مثل این است که شما یک کلاس داخلی تعریف کردید ( private class ).
پس برنامه میخواهد مطمئن باشد که i همونی هست که شما منظورتونه. متغیر local حتما باید final باشد ( وقتی که از اون تو یه کلاس داخلی استفاده میکنید ) و باعث میشه object یکبار ساخته بشه و دیگه نشه تغییر داد و برنامه حتی وقتی متغیر لوکال تموم بشه، باز هم اون آبجکت هست و پا برجاست. وقتی هم متغیر local باشه دیگه یکبار متونید مقدار بدید پس متغیر i رو اگر final کنید دیگه نمیتونید تو حلقه استفاده کنید چون چند مقدار به i نسبت داده میشه و این نادرسته.

بهترین راهکار این است که برای مواقعی که یک کلاس رو داخل یک کلاس دیگه implement میکنید، متغیر هاش رو داخل کانستراکتور یا متدها نباشه. و متغیر ها رو داخل کلاس قرار بدید. اونوقت نیازی به final بودن نیست، چون در کل کلاس معتبر هست و مرجعش یکسانه. مثلا :
int i;
JTextField currentFloorTextField;
public GUIApp() {
super();
int a = 0, b = 0;
JButton numbers[] = new JButton[9];
currentFloorTextField = new JTextField();

for (i = 1; i < numbers.length; i++) {
numbers[i] = new JButton(String.format("%d", i));
numbers[i].setBounds(140 + a, -5 + b, 40, 30);
numbers[i].addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent event) {
if (currentFloorTextField.isFocusable()) {
currentFloorTextField.setText(currentFloorTextFiel d.getText() + String.format("%d", i + 1));
};
}
});
}
}
در این حالت final کردن یا نکردنش به خودتون بستگی داره و اینکه بدونید برای چی یه چیزی رو فاینال میکنیم یا خیر. ( یکی از کاربردهای فاینال توی sync کردن Thread ها هست که حتما باید از final استفاده شود، هر وقت به این برخورد کردید synchronized() اونوقت حتما برید فلسفه final رو بخونید )

در کد شما currentTextField میتونه لوکال و final باشه یا میتونی در خارج از کانستراکتور تعریف کنید چون مطمئنید تغییرش نمیدید ولی اگه بیم این دارین که یه جا دوباره بنویسید currentTextField=new ... اونوقت تو اکشن لیسنرتون مقصدش که currentTextField بود تغییر میکنه و شاید چیزی نباشه که شما میخواید.

اما در مورد i اصلا این روش درستی نیست که از i تو اکشن لیسنرتون استفاده کنید و i هر مقداری میتونه باشه و در صورتی که شما منظورتون شماره متعلق به باتن است.
من اینکار رو به این صورت انجام میدم : ( ضمنا نیازی نیست برای هر باتن یک ActionListener جدید درست کنید و فضا اشغال بشه، چون همشون کارشون مثل همه و فقط عددهاشون فرق میکنه یکی مینویسیم و از event.getActionCommand() برای گرفتن عدد label شون استفاده میکنیم )
JTextArea txt;
public GUIApp() {
super();
int a = 0, b = 0;
JButton numbers[] = new JButton[9];
setLayout(new FlowLayout());
final JTextField currentFloorTextField = new JTextField(10);
add(currentFloorTextField);

ActionListener act = new ActionListener() {
public void actionPerformed(ActionEvent event) {
if (currentFloorTextField.isFocusable()) {
currentFloorTextField.setText(currentFloorTextFiel d.getText() + event.getActionCommand());
};
}
};

for (int i = 1; i < numbers.length; i++) {
numbers[i] = new JButton(String.format("%d", i));
numbers[i].setBounds(140 + a, -5 + b, 40, 30);
add(numbers[i]);
numbers[i].addActionListener(act);
}
}

یا اگر هم میخواید از i استفاده کنید، لازمه که i تو خود آبجکت new ActionListener باشه و چون آرگومان نمیگیره پس راه حل چیه؟ یک کلاس خودت درست میکنی و آرگومان میگیره و این کلاس ActionListener رو implement میکنه.دیگه هر باتن یک i جدا برای خودش ذخیره داره و با بقیه اشتباه نمیشه. اینجوری : ( کلاس ActHandler کلاس درونی هست، میتونی بیرونی هم تعریف کنی، یعنی تو فایل جدا ) کلا پیشنهاد میشه از کلاس جدا برای ActionListener تعریف کنید یا هم با محدودیت هاش آشنا باشید.
public class GUIApp extends JFrame {

public GUIApp() {
super();
int a = 0, b = 0;
JButton numbers[] = new JButton[9];
setLayout(new FlowLayout());
final JTextField currentFloorTextField = new JTextField(10);
add(currentFloorTextField);
for (int i = 1; i < numbers.length; i++) {
numbers[i] = new JButton(String.format("%d", i));
numbers[i].setBounds(140 + a, -5 + b, 40, 30);
add(numbers[i]);
numbers[i].addActionListener(new ActHandler(i, currentFloorTextField));

}
}

private class ActHandler implements ActionListener {

final int i;
final JTextField text;

public ActHandler(int i, JTextField text) {
this.text = text;
this.i = i;
}

@Override
public void actionPerformed(ActionEvent e) {
if (text.isFocusable()) {
text.setText(text.getText() + i);
};
}
}

}

این یکی اما new ActHandler(i, currentFloorTextField( برای هر کدام جدا باید تعریف کنی چون i تو خود آبجکت هندلرتون هست مگر اینکه اینم به جای استفاده از i از e.getActionCommand استفاده کنید. معمولا اینکار رو میکنند. مثلا برای کلید های جهتی یه keyListener ولی با شرط های اینچنینی if(e.getKeyCode()==...) دکمه فشرده شده رو تشخیص میدن. در اصل یک رخداد e هست که اطلاعاتی تو خودش داره که میتونی استفاده کنی. برای actionlistener فقط همون getActionCommand هست.

Tiyana
یک شنبه 21 اردیبهشت 1393, 16:43 عصر
خیلی ممنون دوست عزیز که وقت گذاشتید خیلی استفاده بردم حالا یه سوال دیگه برام پیش اومده و اون اینه که من سه تا تکست فیلد دارم و میخوام زمانی که کرسر موس روی اونا اومد با استفاده از این باتن هایی که ساختم به اونا مقدار بدم یه سرچی که زدم متوجه شدم که باید برای این کار از FocusListener استفاده کرد خب منم تنها راهی که به ذهنم رسید تا از این FocusListener استفاده کنم این بود که بیام یه آرایه ی 3تایی از boolean ها بسازم و به ترتیب تکست فیلد ها اونا رو مقداردهی کنم مثل کد زیر اما به نظرم فکر میکنم که راه بهتری هم برای انجام این کار باشه ضمن این که این روش هم کار نکرد البته میدونم که یه جای کار میلنگه چون با اینکه توی FocusListener مقدار [focus[0 برابر true اما در ActionListener باتن ها اون مقدار فالسه!(البته آرایه ی focus جز متغیر های کلاسه) خلاصه اینم کدش:



private boolean[] focus=new boolean[3];


public FirstGUI()
{

super("Elevator Project");
setInfoButton=new JButton("Set Information");
setInfoButton.setBounds(10,180, 110, 25);
simpleElevator.add(setInfoButton);
setInfoButton.addActionListener
(
new ActionListener()
{
public void actionPerformed(ActionEvent event)
{

if(!personList.isSelectionEmpty() && (personList.getSelectedValue()!=null))
{
final JFrame infoFrame=new JFrame("Person Info");
infoFrame.setSize( 350, 230 );
infoFrame.setLocation(480,150);
infoFrame.setVisible( true );
infoFrame.setLayout(null);

setInfoLabel=new JLabel(String.format("%s",personList.getSelectedValues()));
setInfoLabel.setBounds(10,5,40, 25);
infoFrame.add(setInfoLabel);

currentFloorLabel=new JLabel("Current Floor");
currentFloorLabel.setBounds(33,30,80, 25);
infoFrame.add(currentFloorLabel);

FocusListener focusAct=new FocusListener()
{
public void focusGained(FocusEvent event)
{
if(event.getComponent()==currentFloorTextField)
focus[0]=true;
if(event.getComponent()==destinationFloorTextField )
focus[1]=true;
if(event.getComponent()==timeRequestTextField)
focus[2]=true;
System.out.println(focus[0]);
}
public void focusLost(FocusEvent event)
{
if(event.getComponent()==currentFloorTextField)
focus[0]=false;
if(event.getComponent()==destinationFloorTextField )
focus[1]=false;
if(event.getComponent()==timeRequestTextField)
focus[2]=false;
}
};

currentFloorTextField=new JTextField();
currentFloorTextField.setBounds(100,30, 50, 25);
currentFloorTextField.addFocusListener(focusAct);
infoFrame.add(currentFloorTextField);

destinationFloorLabel=new JLabel("Destination Floor");
destinationFloorLabel.setBounds(15,65,100, 25);
infoFrame.add(destinationFloorLabel);

destinationFloorTextField=new JTextField();
destinationFloorTextField.setBounds(100,65, 50, 25);
destinationFloorTextField.addFocusListener(focusAc t);
infoFrame.add(destinationFloorTextField);

timeRequestLabel=new JLabel("Time Request");
timeRequestLabel.setBounds(30,100,100, 25);
infoFrame.add(timeRequestLabel);

timeRequestTextField=new JTextField();
timeRequestTextField.setBounds(100,100, 50, 25);
timeRequestTextField.addFocusListener(focusAct);
if(personList.getSelectedIndex()==1)
{
timeRequestTextField.setEditable(false);
timeRequestTextField.setText("0");
}

infoFrame.add(timeRequestTextField);
ActionListener act=new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
if(focus[0])
currentFloorTextField.setText(currentFloorTextFiel d.getText()+event.getActionCommand());
if(focus[1])
destinationFloorTextField.setText(destinationFloor TextField.getText()+event.getActionCommand());
if(focus[2])
timeRequestTextField.setText(timeRequestTextField. getText()+event.getActionCommand());
System.out.println(focus[0]);
}
};
numbers=new JButton[10];
int a=40;
int b=35;
numbers[0]=new JButton("0");
numbers[0].setBounds(220,135,40,30);
numbers[0].addActionListener(act);
infoFrame.add(numbers[0]);
for(int i=1;i<numbers.length;i++)
{
numbers[i]=new JButton(String.format("%d",i));
numbers[i].setBounds(140+a,-5+b,40,30);
numbers[i].addActionListener(act);
infoFrame.add(numbers[i]);
if(i%3==0)
{
a=40;
b+=35;
}
else
a+=40;
}
}
else
JOptionPane.showMessageDialog(null,"Please select any person or create new person");
}
}
);

vahid-p
یک شنبه 21 اردیبهشت 1393, 19:48 عصر
ببخشید وقت نکردم کدتون رو بخونم، ولی همین FocusListener خوبه. میتونستید با MouseListener هم انجام بدید، ولی دیگه نسبت به Tab بی تاثیر بود.قسمت focusLost تو این مثال چیزی نمینویسیم ( مثلا بیایم بنویسیم destTextField=null )، چون با کلیک کردن روی باتن ها عملا فوکوس از روی تسک فیلد ها به باتن میره و برای تسک فیلد ها focusLost میشه.


import java.awt.FlowLayout;
import java.awt.HeadlessException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextField;

public class GUIApp extends JFrame {

private JTextField destTextField;

public GUIApp() throws HeadlessException {
super();
setLayout(new FlowLayout());
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(400, 100);
TextHandler txtHandler = new TextHandler();
ActHandler actHandler = new ActHandler();
destTextField = null;
JTextField txtField[] = new JTextField[3];
JButton btn[] = new JButton[3];
for (int i = 0; i < txtField.length; i++) {
txtField[i] = new JTextField(10);
add(txtField[i]);
txtField[i].addFocusListener(txtHandler);
}
for (int i = 0; i < btn.length; i++) {
btn[i] = new JButton(String.valueOf(i));
btn[i].addActionListener(actHandler);
add(btn[i]);
}
}

private class ActHandler implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
if (destTextField != null) {
destTextField.setText(destTextField.getText() + e.getActionCommand());
}
}
}

private class TextHandler implements FocusListener {

@Override
public void focusGained(FocusEvent e) {
destTextField = (JTextField) e.getSource();
}

@Override
public void focusLost(FocusEvent e) {

}
}


این کلاس های private رو به خاطر خوانایی نوشتم و جایی که مثل مثال قبل شاید نیاز به کانستراکتور داشته باشید، اینجوری نوشتم. وگرنه معمولا خودم عادت دارم مستقیم همونجا Implement میکنم و کلاس داخلی نمیسازم براش. یعنی اینجوری :

FocusListener txtHandler=new FocusListener() {

@Override
public void focusGained(FocusEvent e) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}

@Override
public void focusLost(FocusEvent e) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
};