在android webview中输入HTML文件(android 4.4,kitkat)

时间:2013-11-09 19:53:35

标签: android html cordova webview android-4.4-kitkat

我在android webview上使用<input type="file">。 由于这个线程,我得到了它的工作: File Upload in WebView

但是接受的答案(或任何其他答案)不再适用于android 4.4 kitkat webview。

有谁知道如何修复它?

它也不适用于目标18。

我看了一些android 4.4源代码,似乎WebChromeClient没有改变,但我认为setWebChromeClient不再适用于kitkat webview,或者至少不适用于openFileChooser功能

9 个答案:

答案 0 :(得分:30)

更新2: 有一个更简单的插件可以与phonegap / cordova一起使用

https://github.com/MaginSoft/MFileChooser

更新: 使用Cesidio DiBenedetto插件的示例项目

https://github.com/jcesarmobile/FileBrowserAndroidTest

我在android开源项目上打开了issue,答案是:

  

状态:WorkingAsIntended

     

不幸的是,openFileChooser不是公共API。我们正在开发Android的未来版本中的公共API。

对于使用phonegap / cordova的用户,此解决方法已发布在错误跟踪器上:

  

Cesidio DiBenedetto添加了评论 - 28 / Mar / 14 01:27

     嘿所有,我一直在经历这个问题所以我写了一个   Cordova FileChooser暂时插入“创可贴”。   基本上,在Android 4.4(KitKat)中,如前面的评论所述,   文件对话框未打开。然而,onclick事件仍然存在   解雇了所以你可以调用FileChooser插件   打开文件对话框,选择后,您可以设置一个变量   包含文件的完整路径。此时,您可以使用   FileTransfer插件上传到您的服务器并挂钩到   onprogress事件以显示进度。此插件主要配置   对于Android 4.4所以我建议继续使用原生   早期版本的Android的文件对话框。可能存在问题   使用插件,因为我还没有完全测试所有可能的场景   许多设备,但我已经将它安装在Nexus 5上,它运行良好。

     

https://github.com/cdibened/filechooser

未经测试,因为我构建了自己的解决方法

铬开发商的评论

  

我们将在下一个专业中为WebViewClient添加一个公共API   发布以处理文件请求。

现在看来他们认为这是一个bug,他们会修复它

答案 1 :(得分:21)

我设法在我的应用中实现了提到的Cesidio DiBenedetto's workaround。它工作得很好,但对于之前从未使用PhoneGap/Cordove的人(像我一样),这可能有点棘手。所以,在我实施它的过程中,这是一个小小的方法。

Apache Cordova 是一个平台,可让您使用网络技术构建多平台移动应用。关键特性是它将本机API导出到JavaScript,因此提供了一种在网站和本机应用程序之间进行通信的方法。典型的PhoneGap / Cordova应用程序是一个静态网站,它与Cordova图层捆绑在一个APK中。但您可以使用Cordova显示远程网站,这就是我们的情况。

解决方法的工作方式如下:我们使用WebView代替标准CordovaWebView来显示我们的网站。当用户单击浏览以选择文件时,我们使用标准JavaScript(jQuery ...)捕获该单击,并使用Cordova API激活本机端的Cesidio DiBenedetto的filechooser插件,这将打开一个漂亮的文件浏览器。当用户选择文件时,该文件将被发送回JavaScript端,我们将其上传到我们的网络服务器。

重要的是要知道您需要为您的网站添加Cordova支持。好的,现在实际的方法......

首先,您必须将Cordova添加到现有应用中。我关注了this documentation。我不清楚一些步骤,所以我会尝试解释更多:

  1. 在应用程序外的某处下载并解压缩Cordova,并按照说明构建 cordova-3.4.0.jar 。由于缺少 local.properties 文件,它可能会第一次失败。您将被指示如何在错误输出中创建它;你只需要指向你用来构建你的Android应用程序的SDK。

  2. 将已编译的jar文件复制到您的app lib 目录,并将jar添加为库。如果您像我一样使用Android Studio,请确保 build.gradle 中的compile fileTree(dir: 'libs', include: ['*.jar', '*.aar'])中有dependencies。然后点击使用gradle文件同步项目按钮,您就可以了。

  3. 您不必创建 /res/xml/main.xml 文件。您可以像对待标准WebView一样对待CordovaWebView,这样您就可以将其直接放到布局文件中。

  4. 现在,只需按照原始文档中的步骤5-7,将Activity放在CordobaWebView所在的位置。检查您下载的Cordova软件包中的/framework/src/org/apache/cordova/CordovaActivity.java是个不错的主意。您只需复制实现所需的大多数方法即可。 6.步骤对于我们的目的非常重要,因为它会使用filechooser插件。

  5. 请勿在任何地方复制任何HTML和JavaScript文件,我们稍后会将其添加到您的网站。

  6. 不要忘记复制 config.xml 文件(您不必更改)。

  7. 要在CordovaWebView中加载您的网站,只需将其网址传递给cwv.loadUrl()而不是Config.getStartUrl()

    其次,您必须将FileChooser plugin添加到您的应用中。由于我们未使用标准的Cordova设置,因此我们无法按照指示点击cordova plugin add在自述文件中,我们必须手动添加它。

    1. 下载存储库并将源文件复制到您的应用程序。确保 res 文件夹的内容转到您的应用 res 文件夹。您暂时可以忽略JavaScript文件。

    2. 为您的应用添加READ_EXTERNAL_STORAGE权限。

    3. 将以下代码添加到 /res/xml/config.xml

    4. <feature name="FileChooser">
          <param name="android-package" value="com.cesidiodibenedetto.filechooser.FileChooser" />
      </feature>
      

      现在是时候将Cordova支持添加到您的网站了。比听起来更简单,您只需将 cordova.js 链接到您的网站即可但是,有两件事需要了解。

      首先,每个平台(Android,iOS,WP)都有自己的 cordova.js ,因此请确保使用Android版本(您可以在中下载的Cordova软件包中找到它> /框架/资产/万维网)。

      其次,如果要从CordovaWebView和标准浏览器(桌面版或移动版)访问您的网站,通常只加载 cordova.js 是个好主意当页面显示在CordovaWebView中时。我发现了几种检测CordovaWebView的方法,但下面的方法对我有效。以下是您网站的完整代码:

      function getAndroidVersion(ua) {
          var ua = ua || navigator.userAgent; 
          var match = ua.match(/Android\s([0-9\.]*)/);
          return match ? parseFloat(match[1]) : false;
      };
      
      if (window._cordovaNative && getAndroidVersion() >= 4.4) {
          // We have to use this ugly way to get cordova working
          document.write('<script src="/js/cordova.js" type="text/javascript"></script>');
      }
      

      请注意,我们还会检查Android版本。只有KitKat才需要此解决方法。

      此时您应该能够从您的网站手动调用 FileChooser 插件。

      var cordova = window.PhoneGap || window.Cordova || window.cordova;
      cordova.exec(function(data) {}, function(data) {}, 'FileChooser', 'open', [{}]);
      

      这应该打开文件浏览器并让你选择一个文件。请注意,只有在触发事件deviceready之后才能执行此操作。要测试它,只需使用jQuery将此代码绑定到某个按钮。

      最后一步是将所有内容放在一起,让上传表单正常工作。为实现这一目标,您只需按照 README中描述的Cesidio DiBenedetto的说明进行操作。当用户在 FileChooser 中选择文件时,文件路径将返回到JavaScript端,其中另一个Cordova插件 FileTransfer 用于执行实际上载。这意味着该文件是在本机上传的,而不是CordovaWebView(如果我理解正确的话)。

      我不想在我的应用程序中添加另一个Cordova插件,我也不确定它如何与cookie一起使用(我需要发送带有请求的cookie,因为只有经过身份验证的用户才能上传所以我决定按照我的方式去做。我修改了 FileChooser 插件,因此它不会返回路径而是整个文件。因此,当用户选择文件时,我会读取其内容,使用base64对其进行编码,将其作为JSON传递给客户端,我将其解码并使用JavaScript将其发送到服务器。它的工作方式有一个明显的缺点,因为base64非常需要CPU,所以当上传大文件时应用程序可能会冻结一点。

      要做到这一点,首先将此方法添加到 FileUtils

      public static byte[] readFile(final Context context, final Uri uri) throws IOException {
          File file = FileUtils.getFile(context, uri);
          return org.apache.commons.io.FileUtils.readFileToByteArray(file);
      }
      

      请注意,它使用 Apache Commons库,所以不要忘记包含它或以其他方式实现文件读取,而不需要外部库。

      接下来,修改 FileChooser.onActivityResult 方法以返回文件内容而不是其路径:

      // Get the URI of the selected file
      final Uri uri = data.getData();
      Log.i(TAG, "Uri = " + uri.toString());
      JSONObject obj = new JSONObject();
      try {
          obj.put("filepath", FileUtils.getPath(this.cordova.getActivity(), uri));
          obj.put("name", FileUtils.getFile(this.cordova.getActivity(), uri).getName());
          obj.put("type", FileUtils.getMimeType(this.cordova.getActivity(), uri));
      
          // attach the actual file content as base64 encoded string
          byte[] content = FileUtils.readFile(this.cordova.getActivity(), uri);
          String base64Content = Base64.encodeToString(content, Base64.DEFAULT);
          obj.put("content", base64Content);
      
          this.callbackContext.success(obj);
      } catch (Exception e) {
          Log.e("FileChooser", "File select error", e);
          this.callbackContext.error(e.getMessage());
      }
      

      最后,这是您在网站上使用的代码(需要jQuery):

      var cordova = window.PhoneGap || window.Cordova || window.cordova;
      if (cordova) {
          $('form.fileupload input[type="file"]', context).on("click", function(e) {    
              cordova.exec(
                  function(data) { 
                      var url = $('form.fileupload', context).attr("action");
      
                      // decode file from base64 (remove traling = first and whitespaces)
                      var content = atob(data.content.replace(/\s/g, "").replace(/=+$/, ""));
      
                      // convert string of bytes into actual byte array
                      var byteNumbers = new Array(content.length);
                      for (var i = 0; i < content.length; i++) {
                          byteNumbers[i] = content.charCodeAt(i);
                      }
                      var byteContent = new Uint8Array(byteNumbers);
      
                      var formData = new FormData();
                      var blob = new Blob([byteContent], {type: data.type}); 
                      formData.append('file', blob, data.name);
      
                      $.ajax({
                          url: url,
                          data: formData,
                          processData: false,
                          contentType: false,
                          type: 'POST',
                          success: function(data, statusText, xhr){
                              // do whatever you need
                          }
                      });
                  },
                  function(data) { 
                      console.log(data);
                      alert("error");
                  },
                  'FileChooser', 'open', [{}]);
          });
      }
      

      嗯,这就是全部。我需要花费几个小时来完成这项工作,所以我谦虚地希望这可能对某些人有所帮助。

答案 2 :(得分:8)

如果有人仍在使用kitkat上的webview寻找文件输入的解决方案。

在Android 4.4上点击时未调用openFileChooser
https://code.google.com/p/android/issues/detail?id=62220

可以使用名为Crosswalk的基于铬的库来解决此问题
https://crosswalk-project.org/documentation/downloads.html

步骤
1.将从上面链接下载的 xwalk_core_library android项目作为库导入项目中
2.在你的布局xml中添加以下内容

       <org.xwalk.core.XWalkView
            android:id="@+id/webpage_wv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"          
        />

3。在您的活动的 onCreate 方法中,执行以下操作

mXwalkView = (XWalkView) context.findViewById(R.id.webpage_wv);
mXwalkView.setUIClient(new UIClient(mXwalkView));
mXwalkView.load(navigateUrl, null); //navigate url is your page URL
  1. 添加活动类变量

    私有ValueCallback mFilePathCallback; 私人XWalkView mXwalkView

  2. 现在应显示文件输入对话框。但是,您需要提供回调来获取文件并将其发送到服务器。

  3. 您需要覆盖活动的onActivityResult

    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
      super.onActivityResult(requestCode, resultCode, intent);
    
    if (mXwalkView != null) {
    
        if (mFilePathCallback != null) {
            Uri result = intent == null || resultCode != Activity.RESULT_OK ? null
                    : intent.getData();
            if (result != null) {
                String path = MediaUtility.getPath(getActivity(), result);
                Uri uri = Uri.fromFile(new File(path));
                mFilePathCallback.onReceiveValue(uri);
            } else {
                mFilePathCallback.onReceiveValue(null);
            }
        }
    
        mFilePathCallback = null;
    }
    mXwalkView.onActivityResult(requestCode, resultCode, intent);
    
    }
    
  4. MediaUtility类可以在
    找到 Get real path from URI, Android KitKat new storage access framework
    参见Paul Burke的回答

  5. 要获取 mFilePathCallback 的数据对象,请在您的活动中创建一个子类

    class UIClient extends XWalkUIClient {
    public UIClient(XWalkView xwalkView) {
        super(xwalkView);
    }
    
    public void openFileChooser(XWalkView view,
            ValueCallback<Uri> uploadFile, String acceptType, String capture) {
        super.openFileChooser(view, uploadFile, acceptType, capture);
    
        mFilePathCallback = uploadFile;
        Log.d("fchooser", "Opened file chooser.");
    }
    

    }

  6. 你们都完成了。 fileupload现在应该可以工作了。 别忘了将Crosswalk所需的权限添加到您的清单中。

    uses-permission android:name =&#34; android.permission.ACCESS_FINE_LOCATION&#34;
    uses-permission android:name =&#34; android.permission.ACCESS_NETWORK_STATE&#34;
    uses-permission android:name =&#34; android.permission.ACCESS_WIFI_STATE&#34;
    uses-permission android:name =&#34; android.permission.CAMERA&#34;
    uses-permission android:name =&#34; android.permission.INTERNET&#34;
    uses-permission android:name =&#34; android.permission.MODIFY_AUDIO_SETTINGS&#34;
    uses-permission android:name =&#34; android.permission.RECORD_AUDIO&#34;
    uses-permission android:name =&#34; android.permission.WAKE_LOCK&#34;
    uses-permission android:name =&#34; android.permission.WRITE_EXTERNAL_STORAGE&#34;

答案 3 :(得分:5)

webview中的文件选择器现在可以在最新的Android版本4.4.3中使用。

使用Nexus 5自己尝试。

答案 4 :(得分:1)

尽管Kitkat版本与webview type = file表单字段不兼容,但我们可以使用webview的addJavascriptInterface方法来完成文件上传任务。服务器端应判断android的版本,如果低于4.4,则使用WebViewChromeClient私有方法,如果4.4或以上,让服务器调用android方法相互通信(例如上传文件内容异步) )

//代码

webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new WebViewJavaScriptInterface(this), "app");


这是一个可能有帮助的链接......

call-android-methods-from-javascript

答案 5 :(得分:1)

我在不使用任何库,Cordova插件或自定义WebView的情况下为此问题构建了自己的解决方案,并且它在所有Android版本中都能正常运行。

此解决方案包括使用一些非常简单的Javascript在您的WebView和Android应用程序中的网站之间进行通信,并直接从您的Android应用程序执行文件选择和上传,删除所有openFileChooser(),showFileChooser()和onShowFileChooser() WebChromeClient方法。

当用户点击文件输入编写唯一代码时,第一步是从网站触发javascript控制台消息,这将用于上传文件具有唯一的名称或路径。例如,将完整的日期时间与一个巨大的随机数连接起来:

<input type="file" name="user_file" onclick="console.log('app.upload=20170405173025_456743538912');">

然后你的应用可以读取此消息,覆盖WebChromeClient的onConsoleMessage()方法,检测该消息,读取代码,触发文件选择

webview.setWebChromeClient(new WebChromeClient() {
    // Overriding this method you can read messages from JS console.
    public boolean onConsoleMessage(ConsoleMessage message){          
        String messageText = message.message();
        // Check if received message is a file upload and get the unique code
        if(messageText.length()>11 && messageText.substring(0,11).equals("app.upload=")) {
           String code = messageText.substring(11);
           triggerFileUploadSelection(code);
           return true;
        }
        return false;
    }
});

对于文件选择,您可以像这样使用简单的Android ACTION_PICK意图

public void triggerFileUploadSelection(String code){
    // For Android 6.0+ you must check for permissions in runtime to read from external storage
    checkOrRequestReadPermission();

    // Store code received from Javascript to use it later (code could also be added to the intent as an extra)
    fileUploadCode = code;

    // Build a simple intent to pick any file, you can replace "*/*" for "image/*" to upload only images if needed
    Intent filePickerIntent = new Intent(Intent.ACTION_PICK);
    filePickerIntent.setType("*/*");

    // FILE_UPLOAD_CODE is just any unique integer request code to identify the activity result when the user selects the file
    startActivityForResult( Intent.createChooser(filePickerIntent, getString(R.string.chooseFileToUpload) ), FILE_UPLOAD_CODE );
}

用户选择文件后,您可以接收文件Uri 将其转换为实际文件路径,其中包含:

@Override
public void onActivityResult (int requestCode, int resultCode, Intent data) {
    if(requestCode==FILE_UPLOAD_CODE) {
        if(data != null && resultCode == RESULT_OK){
            // user selected a file
            try{
                Uri selectedFileUri = data.getData();
                if(selectedFileUri!=null) {
                    // convert file URI to a real file path with an auxiliary function (below)
                    String filePath = getPath(selectedFileUri);
                    if(filePath!=null) {
                        // I got the file path, I can upload the file to the server (I pass webview as an argument to be able to update it when upload is completed)
                        uploadSelectedFile(getApplicationContext(), filePath, fileUploadCode, webview);
                    }else{
                        showToastFileUploadError();
                    }
                }else{
                    showToastFileUploadError();
                }
            }catch (Exception e){
                e.printStackTrace();
                showToastFileUploadError();
            }
        }else{
            // user didn't select anything
        }
    }
}

// I used this method for images, and it uses MediaStore.Images so you should probably 
// use another method to get the path from the Uri if you are working with any kind of file
public String getPath(Uri uri) {
    String[] projection = { MediaStore.Images.Media.DATA };
    Cursor cursor = managedQuery(uri, projection, null, null, null);
    if(cursor==null)return null;
    int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
    cursor.moveToFirst();
    return cursor.getString(column_index);
}

uploadSelectedFile方法,只是创建一个包含所有信息的对象(filePath,fileUploadCode和WebView),使用该信息触发上传文件的AsyncTask ,并在它和&时更新WebView。 #39;完成了:

public static void uploadSelectedFile(Context c, String filePath, String code, WebView webView){
    // YOU CAN SHOW A SPINNER IN THE WEB VIEW EXECUTING ANY JAVASCRIPT YOU WANT LIKE THIS:
    webView.loadUrl("javascript: Element.show('my_upload_spinner');void 0"); 
    // void 0 avoids at the end avoids some browser redirection problems when executing javascript commands like this

    // CREATE A REQUEST OBJECT (you must also define this class with those three fields and a response field that we will use later):
    FileUploadRequest request = new FileUploadRequest(filePath, code, webView);

    // Trigger an async task to upload the file, and pass on the request object with all needed data
    FileUploadAsyncTask task = new FileUploadAsyncTask();
    task.execute(request);
}

AsyncTask接收包含所有信息的请求对象,并使用 MultipartUtility来构建多部分请求并轻松地将其发送到服务器。您可以从许多地方获得大量Java Multipart Utilities,其中一个位于:http://www.codejava.net/java-se/networking/upload-files-by-sending-multipart-request-programmatically

public class FileUploadAsyncTask extends AsyncTask<FileUploadRequest, Void, FileUploadRequest> {
    @Override
    protected FileUploadRequest doInBackground(FileUploadRequest... requests) {
        FileUploadRequest request = requests[0];

        try {
            // Generate a multipart request pointing to the URL where you will receive uploaded file
            MultipartUtility multipart = new MultipartUtility("http://www.example.com/file_upload.php", "UTF-8");
            // Add a field called file_code, to send the code to the server script
            multipart.addFormField("file_code", request.code);
            // Add the file to the request
            multipart.addFilePart("file_path", new File(request.filePath));

            // Send the request to the server and get the response and save it back in the request object
            request.response = multipart.finish(); // response from server.
        } catch (IOException e) {
            request.response = "FAILED";
            e.printStackTrace();
        }
        return request;
    }

现在我们已将文件上传到服务器,我们可以使用我们的AsyncTask中的onPostExecute方法再次使用Javascript更新我们的网站。最重要的是在表单的隐藏字段中设置文件代码,这样您就可以在用户发送表单时获取该代码。您还可以在网站上显示消息,甚至可以轻松显示上传的图像(如果是图像):

@Override
protected void onPostExecute(FileUploadRequest request) {
    super.onPostExecute(request);

    // check for a response telling everything if upload was successful or failed
    if(request.response.equals("OK")){
        // set uploaded file code field in a hidden field in your site form (ESSENTIAL TO ASSOCIATE THE UPLOADED FILE WHEN THE USER SENDS THE WEBSITE FORM)
        request.webView.loadUrl("javascript: document.getElementById('app_upload_code_hidden').value = '"+request.code+"';void 0");

        // Hide spinner (optional)
        //request.webView.loadUrl("javascript: Element.hide('my_upload_spinner');void 0");

        // show a message in the website, or the uploaded image in an image tag setting the src attribute 
        // to the URL of the image you just uploaded to your server. 
        // (you must implement your own fullUrl method in your FileUploadRequest class)
        // request.webView.loadUrl("javascript: document.getElementById('app_uploaded_image').src = '"+request.fullUrl()+"';void 0");
        // request.webView.loadUrl("javascript: Element.show('app_uploaded_image');void 0");
    }
}

现在Android部分已经完成,您需要使用服务器端接收通过Android应用AsyncTask上传的文件并将其保存到您需要的任何位置。

当用户发送网站表单时,您还必须处理您的网站表单,并根据用户从应用上传的文件执行您需要的任何操作。为此,您将获得表单中的文件代码(我们在onPostExecute()的字段中完成了该文件代码),您必须使用该文件代码查找应用已上传的文件你的服务器。为此,您可以使用该代码作为文件名将文件保存在路径中,或者保存代码以及将文件上载到数据库中的路径。

此解决方案仅依赖于可用且与所有Android版本兼容的元素,因此它应该适用于任何设备(我还没有收到用户对此抱怨)。

如果您在同一页面中有多个文件输入,则可以在初始javascript消息中发送字段编号或额外标识符以及唯一文件代码,通过所有应用程序代码中的标识符,并使用它来更新onPostExecute()中的正确元素。

我在这里稍微修改了实际的代码,所以如果有任何失败,重命名一些东西时可能会出现错字或一些细节。

要处理的信息非常多,所以如果有人需要澄清或有任何建议或更正,请告诉我。

答案 6 :(得分:0)

Kitkat的新文件浏览器在Chrome上同样疯狂,看看WebView现在如何使用Chromium,这可能是一个相关的问题。我发现直接从相机上传文件可以正常工作,但不能从'Images'文件夹中上传。如果您要从“图库”上传,则可以访问相同的文件。尔加。

似乎已经准备就绪,但等待发布:

https://code.google.com/p/chromium/issues/detail?id=278640

答案 7 :(得分:0)

如果你只想在你的网站上添加一个webview包装并将其作为一个应用程序启动,只是不要浏览默认的android webview,不管怎么说这是一个非常令人头痛的问题。对我来说两件事没有运行1.输入文件2. Stripe checkout integeration(使用高级JS API)

我做了什么来摆脱黑暗

刚刚使用Cordova创建了一个示例App。我们认为这要简单得多。

  1. 从官方页面安装Cordova,构建示例应用程序。
  2. 它会给你一个apk文件。 (你明白了)
  3. 您从创建的应用程序转到www文件夹并打开index.js,查找并替换该行 onDeviceReady:function(){window.open('http://yourdomain.com/yourpage')} 再次运行该应用程序,它将打开该网站

  4. 现在是主步骤。到目前为止Cordova只使用温和的webview。有一天,一切都应该改变。将Crosswalk插件添加到您的Cordova应用程序中,它将用一个全新的完整铬视图取代缓慢的webview https://crosswalk-project.org/documentation/cordova.html

  5. 运行cordova clean,然后cordova build --release清理旧版本。

  6. 打开Cordva应用程序提供的app目录内的config.xml,然后添加<allow-navigation href="http://yourdomain.com/*" />
  7. 再次运行该应用。神奇!

答案 8 :(得分:-1)

使用crosswalk-webview-21.51.546.7时,通过相机选择图片。 onActivityResult() the intent.getData()中的nullpackage Dialog; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXDialog; import com.jfoenix.controls.JFXDialogLayout; import com.jfoenix.controls.events.JFXDialogEvent; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.event.EventType; import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.VPos; import javafx.scene.control.Label; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.text.Font; public class OK_Message extends JFXDialog { private StackPane Container; private JFXDialogLayout Content; String DialogText; String Headline; JFXButton btn; boolean OK = false; private BooleanProperty okval; public OK_Message( StackPane Container, String Headline, String DialogText ){ this.Container = Container; this.DialogText = DialogText; this.Headline = Headline; } public void ShowDialog(){ setDialogContainer(Container); setContent(getDialogContent()); setTransitionType(JFXDialog.DialogTransition.TOP); setOverlayClose(false); setFocusTraversable(true); setOnDialogOpened((JFXDialogEvent event) -> { Platform.runLater(()->{ btn.requestFocus(); });}); setOnKeyPressed((KeyEvent e)->{ e.consume(); if(e.getCode()== KeyCode.ENTER){close();} else if(e.getCode()== KeyCode.SPACE){btn.requestFocus();} else{} }); show(); } private JFXDialogLayout getDialogContent(){ Content = new JFXDialogLayout(); Content.setHeading(HeadLine()); Content.setBody(Body()); // Platform.runLater(()->{b.requestFocus();}); Content.setActions(getButton()); return Content; } private Label HeadLine(){ Label l = new Label(Headline); l.setFont(new Font(18)); return l; } private GridPane Body(){ Label l = new Label(DialogText); l.setFont(new Font(14)); GridPane grid = new GridPane(); grid.setHgap(10); grid.setVgap(8); GridPane.setConstraints(l, 0, 0, 1, 1, HPos.LEFT, VPos.CENTER, Priority.ALWAYS, Priority.ALWAYS, new Insets(0, 0, 0, 0)); grid.getChildren().addAll(l); return grid; } private JFXButton getButton(){ btn = new JFXButton("OK"); btn.setButtonType(JFXButton.ButtonType.FLAT); btn.setPrefWidth(50); btn.setTextFill(Color.WHITE); btn.setOnAction((ActionEvent event) -> { CloseDialog(); }); btn.setStyle("-fx-background-color:#FFFFFF"); btn.focusedProperty().addListener(new ChangeListener<Boolean>(){ @Override public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { if(newValue){ btn.setStyle("-fx-background-color:red"); } } }); return btn; } private void CloseDialog(){ close(); } } 。这意味着通过相机上传图像无法正常工作。