SSISO Community

시소당

TRANSFERHANDLER 사용하기



2003/3/18  Tech  Tip  "Dragging  Text  and  Images  with  Swing"  에서는  여러  스윙  컴포넌트간에  텍스트와  이미지들의  드래그와  드롭(drag&drop)이  가능하도록  프로그램에  이를  지원하는  방법을  소개했다.  하지만  지난  팁에서는  컴포넌트가  수락(accept)하는  것들과  드롭된  물체를  처리하는  방법을  이미  알고  있었다.  그렇지만  그러한  정보를  알고  있지  못하면  어떨까?  이  글이  후자의  케이스를  설명한다.

3월의  팁에서  javax.swingTransferHandler  클래스를  소개하면서  TransferHandler  객체와  관련된  모든  스윙  컴포넌트들은  자동적으로  드롭될  대상을  받을  수  있다고  했다.  그때는  디폴트  TransferHandler  객체를  사용했었다.  이번  팁에서는,  TransferHandler객체를  사용자  정의하는데,  이  때,  어떤  타입의  객체가  컴포넌트에  드롭될  수  있는지  지정하고  드롭이  가능한  이벤트에서  충족되어야  할  것들을  명시해야  한다.

첫번째로  텍스트를  드래그하고  드롭할  수  있게끔  하는  Drop프로그램으로  이야기를  시작해  보자.  Drop은  JEditorPane  를  포함하는  JFrame를  생성하고,  이를  실행하면,  JEditorPane  에  텍스트를  입력할  수가  있다.  그리고  나면  텍스트를  선택하여  드래그하고  해제(release)하는  것이  가능해진다.  또한  다른  애플리케이션으로부터  텍스트를  선택해와서  텍스트  입력부분에  그것을  드래그할  수도  있다.  텍스트를  선택하고  드래그하면,  아이템을  드래그하고  있다는  것을  표시하는  친숙한  사각형  표시를  볼  수  있다.  또한  타겟  플러스  사인으로  드롭이  가능한  지역을  판단할  수도  있다.

      import  javax.swing.text.JTextComponent;
      import  javax.swing.JFrame;
      import  javax.swing.JEditorPane;
      import  javax.swing.JButton;
      import  java.awt.event.ActionListener;
      import  java.awt.event.ActionEvent;
      import  java.awt.BorderLayout;

      class  Drop  extends  JFrame  {

            private  JTextComponent  jText;

            public  Drop()  {
                super("Drag  n  Drop  Demo");
                setDefaultCloseOperation(EXIT_ON_CLOSE);
                setSize(300,  400);

                createTextArea();
                createClearButton();
            }

            private  void  createTextArea()  {
                jText  =  new  JEditorPane();
                getContentPane().add(
                                                      jText,  BorderLayout.CENTER);
            }

            private  void  createClearButton()  {
                JButton  clear  =  new  JButton("Clear");
                clear.addActionListener(new  Clearer());
                getContentPane().add(
                                                        clear,  BorderLayout.SOUTH);
            }

            private  class  Clearer  implements
                ActionListener  {
                public  void  actionPerformed(ActionEvent  e)  {
                    jText.setText("");
                }
            }

            public  static  void  main(String[]  args)  {
                new  Drop().setVisible(true);
            }
      }

자,  JEditorPane에  파일을  드래그하고  드롭하는  것을  해보자.  JeditorPane은  드롭된  파일을  수락하지  않기  때문에  이러한  속성(behavior)을  바꾸고자  한다면  custom  TransferHandler를  만들어야  한다.  만드는  방법은  TransferHandler클래스에  importData()와  canImport()메소드를  오버라이드하는  것이다.  canImport()  메소드는  JComponent가  수락하는  것들을  보여주는  메소드인데,  가령,  다음과  같은  예에서는  JComponent가  JComponent에  드롭된  모든  것들을  수락하고  있다.

      public  boolean  canImport(JComponent  c,
                                                        DataFlavor[]  flavors)  {
            return  true;
        }  

importData()  메소드는  드롭에  대한  응답으로  동작이  실행되는  장소이고  주로  JComponent에  드롭된  Transferable  객체와  상호작용한다.

다음은  사용자정의된  TransferHandler,  DropHandler  이다.  canImport()메소드는  컴포넌트에  대한  드롭의  수를  단순히  계산한다.

      import  javax.swing.TransferHandler;
      import  javax.swing.JComponent;
      import  javax.swing.text.JTextComponent;
      import  java.awt.datatransfer.Transferable;
      import  java.awt.datatransfer.DataFlavor;


      class  DropHandler  extends  TransferHandler  {

            private  int  numberOfDrops;

            public  boolean  importData(JComponent  component,
                                                        Transferable  transferable){

                JTextComponent  source  =  
                                                        (JTextComponent)  component;
                source.setText(
                                        "Drop  Number:  "  +  ++numberOfDrops);
                return  true;
            }

            public  boolean  canImport(JComponent  c,
                                                              DataFlavor[]  flavors)  {
                return  true;
            }
      }

위의  예를  실행하려면,  대상  컴포넌트에  TransferHandler를  연결해야  하는데,  모든  JComponents는  setTransferHandler()메소드를  통해서  TransferHandler  을  부를  수가  있다.  다음과  같이  이  호출을  Drop.java  의  createTextArea()메소드에  추가한다.

      private  void  createTextArea()  {
                jText  =  new  JEditorPane();
                getContentPane().add(jText,  BorderLayout.CENTER);
                jText.setTransferHandler(new  DropHandler());
      }

이  예제를  실행하기  전에  2가지  중요한  사항을  기억해야  한다.  하나는  컴포넌트의  디폴트  TransferHandler  를  오버라이드하면,  디폴트  속성이  더이상  유지되지  않는다는  것이다.  예를  들어  JEditorPane에  String를  드롭하면,  예상하는대로  String  이  나타나지  않을  수  있다.  다른  하나는,  canImport()을  항상  트루(true)로  리턴되도록  오버라이드  해놓았기  때문에,  JEditorPane  는  어떤  드롭이던지  수락한다는  것이다.  하지만  어디에서  드래그했느냐에  따라서,  소스  컴포넌트의  데이터를  잃을  수도  있다.  가령,  텍스트  에디터로부터  드래그한  데이더는  성공적으로  드롭을  하더라도  그  에디터로부터  삭제된다는  것이다.

TransferHandler를  File  객체만  수락하도록  사용자정의  해보자.  이를  위해,  3월의  팁에서  소개된  DataFlavors의  개념을  적용해  볼  수가  있다.  전송  중인  객체는  객체가  지원하는  모든  DataFlavor  객체들의  배열을  갖는다.  canImport()  메소드를  다음과  같이  변경함으로써  지원을  받는  DataFlavors  중의  하나가  File  인지  아닌지를  테스트해  볼  수가  있다.

      public  boolean  canImport(JComponent  c,
                                                              DataFlavor[]  flavors)  {
                  return  Arrays.asList(flavors).
                    contains(DataFlavor.javaFileListFlavor);
      }

            public  void  drop(DropTargetDropEvent  dtde)  {
                      Transferable  transferable  =
                                                dtde.getTransferable();
                      if  (transferable.isDataFlavorSupported(
                                          DataFlavor.javaFileListFlavor))  {
                          dtde.acceptDrop(DnDConstants.ACTION_COPY);
                          List  files  =  null;
                          try  {
                              files  =  
                                  (List)  transferable.getTransferData(
                                                DataFlavor.javaFileListFlavor);
                          }  //exceptions  omitted  here

                          for  (int  i  =  0;  i  <  files.size();  i++)  {
                              File  file  =  (File)  files.get(i);
                              jText.setText(
                                                jText.getText()  +  "\n"  +  file);
                          }
                      }
                  }

DataFlavor  객체의  배열을  List로  변환해서  리스트가  DataFlavor.javaFileListFlavor  를  포함하고  있는지  여부를  알  수가  있다.  JEditorPane  에  드롭되는  File을  처리하기  위해서는,  importData()  메소드  또한  변경해야  한다.  이  메소드를  이용하면  드롭되는  객체가  File인지  아닌지를  알아낼  수가  있다.  만약  객체가  File이  아니라면  false를  리턴하고,  File이면  이를  처리할  수  있을  것이다.  적절한  예외처리를  해줘야  함은  물론이다.

파일을  다루는  실제적인  코드는  importFiles()메소드이다.  다음의  예에서  DropHandler  의  업데이트된  버전인  importFiles()  메소드는  드롭된  File  의  경로를  각각의  라인에  나타낸다.  DropHandler  는  다음과  같다.

      import  javax.swing.TransferHandler;
      import  javax.swing.JComponent;
      import  javax.swing.text.JTextComponent;
      import  java.util.List;
      import  java.util.Arrays;
      import  java.io.File;
      import  java.io.IOException;
      import  java.awt.datatransfer.Transferable;
      import  java.awt.datatransfer.DataFlavor;
      import  java.awt.datatransfer.UnsupportedFlavorException;

      class  DropHandler  extends  TransferHandler  {


            public  boolean  importData(JComponent  component,
                                                      Transferable  transferable)  {
                if  (!canImport(component,
                    transferable.getTransferDataFlavors()))  {
                    return  false;
                }  else  {
                    JTextComponent  source  =  
                                                        (JTextComponent)  component;
                    try  {
                        importFiles(transferable,  source);
                        return  true;
                    }  catch  (UnsupportedFlavorException  e)  {
                        source.setText(e.getMessage());
                    }  catch  (IOException  e)  {
                        source.setText(e.getMessage());
                    }
                    return  false;
                }
            }

            private  void  importFiles(Transferable  transferable,
                                                              JTextComponent  source)
                throws  UnsupportedFlavorException,  IOException  {
                List  files  =
                    (List)  transferable.getTransferData(
                        DataFlavor.javaFileListFlavor);
                for  (int  i  =  0;  i  <  files.size();  i++)  {
                    source.setText(source.getText()  +  "\n"
                      +  (File)files.get(i));
                }
            }

            public  boolean  canImport(JComponent  c,
                                                              DataFlavor[]  flavors)  {
                  return  Arrays.asList(flavors).
                    contains(DataFlavor.javaFileListFlavor);
            }
      }

Drop를  실행하면,  JEditorPane  는  오직  파일만을  수락할  것이다.  사용자는  한번에  원하는  만큼의  파일을  드롭할  수  있고,  각각의  라인에  나타나는  경로를  볼  수  있다.

코드의  최종  버전은  제임스  고슬링의  java.net  blog  entry  "URLs  are  your  friend"를  따른다.  제임스  고슬링은  드래그와  드롭을  이용할  때  URL로  변환되도록  하는  것을  선호한다고  한다.  이  마지막  예에서,  JEditorPane  에  드롭된  파일들의  경로를  보여주는  대신,  파일로부터  구성된  URLs이  디스플레이  된다.

고슬링은  또한  몇몇  브라우저와  애플리케이션들은  Files을  드롭하려고  할  때  Strings을  드롭한다고  지적하고  있는데,  DropHandler의  이번  버전은  canImport()를  재계산하고,  헬퍼  메소드인  canImportFiles()  과  canImportStrings()을  도입함으로써  Files  또는  Strings을  수락한다.

      public  boolean  canImport(JComponent  c,
                                                          DataFlavor[]  flavors)  {
            return  canImportFiles(flavors)  ||
                canImportStrings(flavors);
        }

        private  boolean  canImportFiles(
                                                          DataFlavor[]  flavors)  {
            return  Arrays.asList(flavors).
                contains(DataFlavor.javaFileListFlavor);
        }

        private  boolean  canImportStrings(
            DataFlavor[]  flavors)  {
            return  Arrays.asList(flavors).
                contains(DataFlavor.stringFlavor);
        }

업데이트된  아래의  DropHandler  에서는,  importFiles()  메소드가  경로대신  URL을  디스플레이하도록  변경되었고,  importStrings()메소드가  첨가되었다.

      private  void  importStrings(Transferable  transferable,
                                                              JTextComponent  source)
            throws  UnsupportedFlavorException,  IOException  {
            String  string  =
                (String)  transferable.getTransferData(
                    DataFlavor.stringFlavor);
            String  message  =  null;
            File  file  =  new  File(string);
            if  (file.exists())  {
                message  =  file.toURL().toString();
            }  else  {
                try  {
                    URL  url  =  new  URL(string);
                    url.getContent();
                    message  =  url.toString();
                }  catch  (IOException  e)  {
                    message  =  "Could  not  convert  string  to  URL  "
                        +  string;
                }
            }
            source.setText(source.getText()  +  "\n"  +  message);

        }

importStrings()메소드는  String으로부터  File을  구성하려  한다.  만약  File이  존재하면  메소드는  File의  toURL()메소드를  이용해서  URL을  구성하지만,  File이  존재하지  않으면  importStrings()는  String으로부터  URL을  직접  구성하려고  할  것이다.  그러면  메소드는  URL에  첨부된  리소스가  있는지를  확인한다.  웹  브라우저로부터  이미지를  드래그했을  때,  DropHandler는  이것이  가능할  경우에는  적절하게  행동하겠지만,  불가능할  경우에는  에러를  낼  것이다.

importData()  메소드는  단순화될  수도  있다.  이  메소드는  전송된  객체가  File  인지  String인지를  테스트한  후,  적절한  메소드를  호출한다.  재작성된  DropHandler  를  보자.

      import  javax.swing.TransferHandler;
      import  javax.swing.JComponent;
      import  javax.swing.text.JTextComponent;
      import  java.util.List;
      import  java.util.Arrays;
      import  java.io.File;
      import  java.io.IOException;
      import  java.awt.datatransfer.Transferable;
      import  java.awt.datatransfer.DataFlavor;
      import  java.awt.datatransfer.UnsupportedFlavorException;
      import  java.net.URL;

      class  DropHandler  extends  TransferHandler  {

            public  boolean  importData(JComponent  component,
                                                      Transferable  transferable)  {
                DataFlavor[]  flavors  =
                                  transferable.getTransferDataFlavors();
                JTextComponent  source  =  
                                                        (JTextComponent)  component;
                try  {
                    if  (canImportFiles(flavors))  {
                        importFiles(transferable,  source);
                    }  else  if  (canImportStrings(flavors))  {
                        importStrings(transferable,  source);
                    }  else
                        return  false;
                    return  true;
                }  catch  (UnsupportedFlavorException  e)  {
                    source.setText(e.getMessage());
                }  catch  (IOException  e)  {
                    source.setText(e.getMessage());
                }
                return  false;

            }

            private  void  importFiles(Transferable  transferable,
                                                              JTextComponent  source)
                throws  UnsupportedFlavorException,  IOException  {
                List  files  =
                    (List)  transferable.getTransferData(
                        DataFlavor.javaFileListFlavor);
                for  (int  i  =  0;  i  <  files.size();  i++)  {
                    source.setText(source.getText()  +  "\n"  +
                        ((File)  (files.get(i))).toURL());
                }
            }

            private  void  importStrings(
                                                        Transferable  transferable,  
                                                        JTextComponent  source)
                throws  UnsupportedFlavorException,  IOException  {
                String  string  =
                    (String)  transferable.getTransferData(
                        DataFlavor.stringFlavor);
                String  message  =  null;
                File  file  =  new  File(string);
                if  (file.exists())  {
                    message  =  file.toURL().toString();
                }  else  {
                    try  {
                        URL  url  =  new  URL(string);
                        url.getContent();
                        message  =  url.toString();
                    }  catch  (IOException  e)  {
                        message  =  "Could  not  convert  string  to  URL  "
                            +  string;
                    }
                }
                source.setText(
                                        source.getText()  +  "\n"  +  message);
            }

            public  boolean  canImport(JComponent  c,
                                                              DataFlavor[]  flavors)  {
                return  canImportFiles(flavors)  ||
                    canImportStrings(flavors);
            }

            private  boolean  canImportFiles(
                                                                DataFlavor[]  flavors)  {
                return  Arrays.asList(flavors).
                    contains(DataFlavor.javaFileListFlavor);
            }

            private  boolean  canImportStrings(
                DataFlavor[]  flavors)  {
                return  Arrays.asList(flavors).
                    contains(DataFlavor.stringFlavor);
            }
      }

이  팁은  일반적인  JTextComponent를  위한  디폴트  TransferHandler로  시작했다.  그리고는  제한적으로  DataFlavor를  취할  수  있는  TransferHandler를  보여주고,  Files와  Strings를  수락하기  위해  TransferHandler를  변경하는  방법과  유용한  URLs를  생성하는  방법을  설명했다.  두  가지  예  모두에서  canImport()  와  importData()  메소드를  필요에  맞게  조정해야  한다.

TransferHandler와  데이터  전송에  대한  좀  더  자세한  정보는  자바  튜토리얼의  "How  to  Use  Data  Transfer"를  참고한다.

출처  TRANSFERHANDLER  사용하기|작성자  쭌
http://blog.naver.com/imcho57?Redirect=Log&logNo=40008079698

825 view

4.0 stars