SQLの窓

2017年06月06日


Android 6.0 エミュレータで 複数の Runtime Permission の対応を簡潔に吸収するクラス( CheckMyPermission )

商品作る場合では無く、テストで面倒なデバイス側での権限の付与の実装を避けたい場合のクラスを作ってみました。getRequestCount() のチェックをしなければ、権限を付与するまでループするようにする事もできます(許可しない場合の UI テスト等にも使えるかもしれません)

CheckMyPermission クラス
package com.example.lightbox.cameratest;

import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;

import java.util.ArrayList;

public class CheckMyPermission {

    private ArrayList<MyPermission> myPermission_list;
    private MainActivity context;

    public CheckMyPermission(MainActivity context, ArrayList<MyPermission> myPermission_list) {
        this.myPermission_list = myPermission_list;
        this.context = context;
    }

    public boolean checkPermission( int requestCode, int[] grantResults ) {
        boolean result = false;
        for( MyPermission myPermission :  myPermission_list) {
            if ( myPermission.getPermissionId() == requestCode ) {
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        myPermission.setFlg(true);
                }
            }
        }
        int resultCounter = 0;
        for( MyPermission myPermission :  myPermission_list) {
            if ( !myPermission.getFlg() ) {
                if ( myPermission.getRequestCount() > 0 ) {
                    break;
                }
                ActivityCompat.requestPermissions(context,
                        new String[]{myPermission.getPermissionType()}, myPermission.getPermissionId());
                myPermission.setRequestCount(myPermission.getRequestCount()+1);
                break;
            }
            else {
                resultCounter++;
            }
        }
        if ( resultCounter == myPermission_list.size() ) {
            result = true;
        }

        return result;

    }

    public boolean checkPermission(){
        boolean result = false;
        int initCounter = 0;
        for( MyPermission myPermission :  myPermission_list) {
            if (ActivityCompat.checkSelfPermission(context, myPermission.getPermissionType())== PackageManager.PERMISSION_GRANTED){
                initCounter++;
                myPermission.setFlg(true);
            }
        }
        if ( initCounter == myPermission_list.size() ) {
            result = true;
        }
        else {
            for( MyPermission myPermission :  myPermission_list) {

                if ( !myPermission.getFlg() ) {
                    // 最初に許可されていないパーミッション用のダイアログを表示する
                    ActivityCompat.requestPermissions(context,
                            new String[]{myPermission.getPermissionType()}, myPermission.getPermissionId());
                    myPermission.setRequestCount(myPermission.getRequestCount()+1);

                    // 一つだけ処理する
                    break;
                }

            }
        }

        return result;
    }
}

対象となる Permission を ArrayList にセットとして内部で殆どの処理を実行してもらいます。ArrayList にセットする MyPermission クラスは以下のような情報を保持しています

MyPermission クラス
package com.example.lightbox.cameratest;

public class MyPermission {

    private String permissionType;
    private int permissionId;
    private boolean flg = false;
    private int requestCount = 0;

    public MyPermission(String type, int id) {
        permissionType = type;
        permissionId = id;
    }

    public String getPermissionType() {
        return permissionType;
    }

    public int getPermissionId() {
        return permissionId;
    }

    public boolean getFlg() {
        return flg;
    }

    public void setFlg(boolean flg) {
        this.flg = flg;
    }

    public int getRequestCount() {
        return requestCount;
    }

    public void setRequestCount(int requestCount) {
        this.requestCount = requestCount;
    }
}


主な処理はカメラと撮影した画像の保存ですが、カメラはテストを目的としているので 『非推奨』の 旧API を使用しています。

MainActivity
package com.example.lightbox.cameratest;

import android.Manifest;
import android.content.Intent;
import android.hardware.Camera;
import android.media.MediaScannerConnection;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.io.File;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;

public class MainActivity extends AppCompatActivity {

    private String imagePath;
    private OldCamera camera;
    private CheckMyPermission checkMyPermission;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 必要なパーミッションのリスト
        ArrayList<MyPermission> myPermission_list = new ArrayList<MyPermission>();
        myPermission_list.add(new MyPermission(Manifest.permission.CAMERA,100));
        myPermission_list.add(new MyPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,101));
        // Runtime Permission 用のクラスのインスタンス
        checkMyPermission = new CheckMyPermission(MainActivity.this,myPermission_list);
        // 全ての必要なパーミッションが既に許可されていた場合
        if ( checkMyPermission.checkPermission() ) {
            cameraSettings();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        // 全ての必要なパーミッションが許可された場合
        if ( checkMyPermission.checkPermission( requestCode,  grantResults )) {
            // onCreate で初期処理できるように、MainActivity をリスタート
            Intent intent = getIntent();
            finish();
            startActivity(intent);
        }
    }

    private void cameraSettings() {

        camera = new OldCamera(MainActivity.this.findViewById(R.id.surfaceView) );

        // *************************************
        // ギャラリーに保存する処理
        // *************************************
        Button galleryButton = (Button) MainActivity.this.findViewById(R.id.button);
        galleryButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i("lightbox", "クリック");

                // 撮影
                camera.getCamera().takePicture(null, null, new Camera.PictureCallback() {
                    @Override
                    public void onPictureTaken(byte[] data, Camera camera) {

                        if (data != null) {

                            // ギャラリー用に内部ストレージにフォルダを作成
                            String imageDir = Environment.getExternalStorageDirectory().getPath() + "/cameratest";
                            File file = new File(imageDir);
                            // ディレクトリ初期作成
                            if (!file.exists()) {
                                if (file.mkdir() == false) {
                                    Log.i("lightbox", "ディレクトリを作成できませんでした");
                                    return;
                                }
                            }

                            // ギャラリー用画像保存パス
                            Calendar cal = Calendar.getInstance();
                            SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd_HHmmss");
                            imagePath = imageDir + "/" + sf.format(cal.getTime()) + ".jpg";

                            FileOutputStream jpg;
                            try {
                                jpg = new FileOutputStream(imagePath);
                                jpg.write(data);
                                jpg.close();

                                // ギャラリーに反映
                                MediaScannerConnection.scanFile(
                                        MainActivity.this,
                                        new String[] { imagePath },
                                        new String[] { "image/jpeg" },
                                        null);

                            }
                            catch (Exception e) {
                                e.printStackTrace();
                            }

                            // カメラ機能再開
                            camera.startPreview();

                        }
                    }
                });

            }
        });
    }

}


OldCamera クラス
package com.example.lightbox.cameratest;

import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;

import java.io.IOException;
import java.util.List;

public class OldCamera implements SurfaceHolder.Callback {

    // 古いカメラAPI。カメラで何かしたいわけでは無いのでこれで実装
    private Camera camera;
    private SurfaceHolder surfaceHolder;
    private List<Camera.Size> sizeList;

    public OldCamera(View view) {

        Log.i("lightbox", "コンストラクタ");

        // SurfaceView から holder を取り出す
        surfaceHolder = ((SurfaceView)view).getHolder();
        // この中でイベント処理を行う
        surfaceHolder.addCallback(OldCamera.this);

    }

    public Camera getCamera() {

        return camera;

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

        Log.i("lightbox", "開く");

        // カメラを開く
        camera = Camera.open();
        try {
            // カメラに holder を渡す
            camera.setPreviewDisplay(holder);
        }
        catch (IOException e) {
            e.printStackTrace();
        }

        // 縦固定(AndroidManifest.xml) で、portrait 撮影を前提
        camera.setDisplayOrientation(90);

        // カメラの情報
        Camera.Parameters params = camera.getParameters();
        // サポートされているサイズの一覧
        sizeList = params.getSupportedPreviewSizes();

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

        // 縦固定(AndroidManifest.xml) なので一度しか通らない
        Log.i("lightbox", "開始");

        // 縦横サイズの設定
        Camera.Parameters params = camera.getParameters();
        Camera.Size optimalSize = getOptimalPreviewSize(sizeList,width,height);
        params.setPreviewSize(optimalSize.width,optimalSize.height);
        camera.setParameters(params);

        camera.startPreview();

    }

    // http://qiita.com/zaburo/items/b5d3815d3ec45b0daf4f
    private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {

        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio=(double)h / w;

        if (sizes == null) return null;

        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = h;

        for (Camera.Size size : sizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        return optimalSize;
    }


    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

        Log.i("lightbox", "終了");

        // プレビュー終了
        if ( camera != null ) {
            camera.stopPreview();
            camera.release();
            camera = null;
        }

    }
}


画面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.lightbox.cameratest.MainActivity">

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Button" />
    </LinearLayout>

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>


AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.lightbox.cameratest">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


posted by lightbox at 2017-06-06 20:56 | 2017 Android Studio | このブログの読者になる | 更新情報をチェックする

Android 6.0 の Runtime Permission に対応する前に、AndroidManifest.xml に権限の記述の必要無いプライベートな書き込みで情報を収集する

Android の中でなかなかテキストファイルに書き込む機会も無いので、FileOutputStream と SharedPreferences でカメラで撮った画像を外部ストレージに書き込むのに知っておきたい情報の確認をしています。

ボタンのイベント

一般的に3種類あるボタンのイベントの作成方法で、ボタンの id によって処理を分けていく結果になる使い方です。ソースとしては自然と連続になるので、このような場合( Write と Read ) では第三者から見たら解りやすくなると思います。(MainActivity に implements View.OnClickListener が必要ですね)

※ 他の二つは、無名(匿名)のインナーでその場に書いてしまう方法と、ボタンのプロパティにメソッド名登録して、MainActivity の メソッドとしてイベントする方法です。


書き込み処理では、BufferedWriter はほぼ無駄なので使っていません。最後に改行コードつけておけば、BufferedReader で読めますし。

書き込んだ後は、Android Device Monitor で push してリアルに確認するのがいいですね。Android に騙されないで、なんか安心します。



test.txt

※ test.txt は、files フォルダのの下にあります
ストレージ情報
/storage/emulated/0
android.permission.CAMERA
android.permission.WRITE_EXTERNAL_STORAGE
0

Environment.getExternalStorageDirectory().getPath()/storage/emulated/0 が取得できています。ここに書き込みするためには Android 6.0 では Runtime Permission の処理が必要になってくるわけですが、今は場所のみを Android Device Monitor で確認しておきます。
package com.example.lightbox.textfiletest;

import android.Manifest;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.i("lightbox", "implements View.OnClickListener したら、setOnClickListener(this)");
        findViewById(R.id.button).setOnClickListener(this);
        findViewById(R.id.buttonRead).setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {

        // View から ID を取得して仕分け
        if (v.getId() == R.id.button ) {

            Log.i("lightbox", "AndroidManifest.xml に権限の記述の必要無いプライベートな書き込み");
            try {
                FileOutputStream fileOutputStream = openFileOutput("test.txt", MODE_PRIVATE);
                OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "utf-8");

                // 次の段階のストレージアクセスの為の情報集め
                outputStreamWriter.write("ストレージ情報\n");
                outputStreamWriter.write(String.format("%s\n", Environment.getExternalStorageDirectory().getPath()));
                // String
                outputStreamWriter.write(Manifest.permission.CAMERA + "\n");
                outputStreamWriter.write(Manifest.permission.WRITE_EXTERNAL_STORAGE + "\n");
                // int
                outputStreamWriter.write(PackageManager.PERMISSION_GRANTED + "\n");

                outputStreamWriter.close();
                fileOutputStream.close();

                // プライベートな小さな情報を保存する為の本来の方法
                SharedPreferences data = getSharedPreferences("private_data", MODE_PRIVATE);
                SharedPreferences.Editor editor = data.edit();

                // 現在の年月日・時分秒を取得する為の準備
                Calendar cal = Calendar.getInstance();
                SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd_HHmmss");

                editor.putString("now", sf.format(cal.getTime()));
                editor.commit();

            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
        else if ( v.getId() == R.id.buttonRead ) {

            Log.i("lightbox", "読み込み");
            try {
                FileInputStream fileINputStream = openFileInput("test.txt");
                InputStreamReader inputStreamReader = new InputStreamReader(fileINputStream, "utf-8");
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

                String line_buffer;
                while( null != (line_buffer = bufferedReader.readLine()) ) {
                    // 長さ 0 で Empty
                    if (line_buffer.isEmpty()) {
                        continue;
                    }

                    Log.i("lightbox",line_buffer);

                }

                inputStreamReader.close();
                fileINputStream.close();

                SharedPreferences data = getSharedPreferences("private_data", MODE_PRIVATE);
                Log.i("lightbox", data.getString("now", "このデータはありません"));

            }
            catch (Exception e) {
                e.printStackTrace();
            }

        }

    }
}

Manifest.permission.CAMERA や Manifest.permission.WRITE_EXTERNAL_STORAGE は、Runtime Permission のチェックで使う定数です。これを ActivityCompat.checkSelfPermission に渡して利用可能の有無をまずチェックします。(実際は継承元の ContextCompat のメソッド)



posted by lightbox at 2017-06-06 11:23 | 2017 Android Studio | このブログの読者になる | 更新情報をチェックする

2017年06月03日


VS(C#) : DataGrid に バインドを使用して JSON データの配列を表示する( Json.NET を使用 )



JSON 文字列を Json.NET で一行でデシリアライズします。その際、バインド用の ObservableCollection に直接セットするので、DataGrid には、DataContext にセットするだけで、表示用の列と行は自動作成されます

外部ライブラリ

JSON データの扱いには、Json.NET を使用します。ダウンロードして、Bin フォルダにある自分の環境に該当するフレームワークフォルダを ソリューションの中に置いて参照設定を行います。
※ net45 フォルダをコピーして使用しました / VS2012

JSON データ

https://lightbox.sakura.ne.jp/demo/json/syain_json.php

このデータを扱う為に以下のクラスを作成しています

Syain.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfAppDataGridFromWeb {
	class Syain {
		public string 社員コード { get; set; }
		public string 氏名 { get; set; }

		public string フリガナ { get; set; }
		public string 所属 { get; set; }

		private string _性別;
		public string 性別 {
			get {
				if (this._性別 == "0") {
					return "男";
				}
				else {
					return "女";
				}
			}
			set {
				this._性別 = value;
			}
		}
		public int 給与 { get; set; }
		public int? 手当 { get; set; }
		public string 管理者 { get; set; }

		public string 作成日 { get; set; }
		public string 更新日 { get; set; }

	}
}


画面

デフォルトで True になっていますが、バインドで列を自動作成させる為に 『AutoGenerateColumns="True"』を DataGrid に設定しています
<Window
	x:Class="WpfAppDataGridFromWeb.MainWindow"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	Title="MainWindow"
	Height="857.456"
	Width="1191.774">
	<Grid>
		<Button
			Content="Button"
			HorizontalAlignment="Left"
			Height="24"
			Margin="27,29,0,0"
			VerticalAlignment="Top"
			Width="122"
			Click="Button_Click" />
		<DataGrid
			x:Name="dataGrid"
			HorizontalAlignment="Left"
			Height="720"
			Margin="27,71,0,0"
			VerticalAlignment="Top"
			Width="1124"
			ItemsSource="{Binding}"
			IsReadOnly="True"
			MouseDoubleClick="dataGrid_MouseDoubleClick" />

	</Grid>
</Window>


処理
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Data.Odbc;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfAppDataGridFromWeb {

	public partial class MainWindow : Window {

		public MainWindow() {
			InitializeComponent();
			// ウインドウを中央へ
			this.WindowStartupLocation = WindowStartupLocation.CenterScreen;
		}

		private void Button_Click(object sender, RoutedEventArgs e) {

			dataGrid.Columns.Clear();

			Console.WriteLine("クリックされました");
			Console.WriteLine(Directory.GetCurrentDirectory());

			string url = "https://lightbox.sakura.ne.jp/demo/json/syain_json.php";
			string result = "[\"error\"]";

			// 信頼性のある WebClient で インターネットアクセス
			WebClient wc = new System.Net.WebClient();
			wc.Encoding = System.Text.Encoding.UTF8;
			try {
				// 読み込み
				result = wc.DownloadString(url);
			}
			catch( Exception ex) {
				Console.WriteLine("WebClient エラー:" + ex.ToString());
			}

			// JSON オブジェクト配列 Newtonsoft.Json( net45 )
			ObservableCollection<Syain> syain_data = JsonConvert.DeserializeObject<ObservableCollection<Syain>>(result);
			// DataGrid にバインド
			dataGrid.DataContext = syain_data;

		}

		// DataGrid をダブルクリックした時の行データの取得
		private void dataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
			int row = dataGrid.SelectedIndex;
			if ( row >= 0 ) {
				Syain syain = (Syain)dataGrid.Items.GetItemAt(row);
				Debug.WriteLine(syain.社員コード + "|" + syain.氏名);

				MessageBox.Show(syain.社員コード + "|" + syain.氏名,
					"確認",
					MessageBoxButton.OK,
					MessageBoxImage.Information);
			}
		}

	}
}

※ HttpClient はバグがあるという話なので使用していません。

関連する記事

Swing の JTable に 1) WEBのJSON2次元配列。2) WEB の JSON配列 を取り込んで表示する( Google Gson と okhttp を使用 )

補足

以下の例では、AutoGenerateColumns="False" に設定して、列の定義をプログラムで行っています。このほうがアプリケーションとしては、列単位で仕様を決定しやすいので自由度が増すはずです。

※ データは JSON の2次元配列を使用しています
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Data.Odbc;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfAppDataGridFromWeb {

	public partial class MainWindow : Window {

		private DataGridTextColumn col;
		private ObservableCollection<Syain> syain_list = null;

		public MainWindow() {
			InitializeComponent();
			// ウインドウを中央へ
			this.WindowStartupLocation = WindowStartupLocation.CenterScreen;
		}

		private void Button_Click(object sender, RoutedEventArgs e) {

			dataGrid.Columns.Clear();

			Console.WriteLine("クリックされました");
			Console.WriteLine(Directory.GetCurrentDirectory());

			string url = "https://lightbox.sakura.ne.jp/demo/json/syain_array.php";
			string result = "[\"error\"]";

			// 信頼性のある WebClient で インターネットアクセス
			WebClient wc = new System.Net.WebClient();
			wc.Encoding = System.Text.Encoding.UTF8;
			try {
				// 読み込み
				result = wc.DownloadString(url);
			}
			catch( Exception ex) {
				Console.WriteLine("WebClient エラー:" + ex.ToString());
			}

			// JSON 2次元配列 Newtonsoft.Json( net45 )
			string[][] syain_data = JsonConvert.DeserializeObject<string[][]>(result);

			// バインド用クラス
			Syain syain;
			// 列用
			col = null;
			// バインド用コレクション
			syain_list = new ObservableCollection<Syain>();

			foreach (string[] syain_row in syain_data) {

				// 初回
				if (col == null) {
					foreach (string syain_col in syain_row) {
						col = new DataGridTextColumn();
						col.Header = syain_col;
						col.Binding = new Binding(syain_col);
						dataGrid.Columns.Add(col);
					}
					continue;
				}

				syain = new Syain();
				syain.社員コード = syain_row[0];
				syain.氏名 = syain_row[1];
				syain.フリガナ = syain_row[2];
				syain.所属 = syain_row[3];
				syain.性別 = syain_row[4];
				syain.給与 = int.Parse(syain_row[7]);
				syain.手当 = (syain_row[8] == "" ? 0 : int.Parse(syain_row[8]));
				syain.管理者 = syain_row[9];
				syain.作成日 = syain_row[5];
				syain.更新日 = syain_row[6];
				// observablecollection に追加
				syain_list.Add(syain);

			}

			//// DataGrid にバインド
			dataGrid.DataContext = syain_list;

		}

		// DataGrid をダブルクリックした時の行データの取得
		private void dataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
			int row = dataGrid.SelectedIndex;
			if ( row >= 0 ) {
				Syain syain = (Syain)dataGrid.Items.GetItemAt(row);
				Debug.WriteLine(syain.社員コード + "|" + syain.氏名);

				MessageBox.Show(syain.社員コード + "|" + syain.氏名,
					"確認",
					MessageBoxButton.OK,
					MessageBoxImage.Information);
			}
		}

	}
}




タグ:C# Json.NET JSON
posted by lightbox at 2017-06-03 20:31 | VS(C#) | このブログの読者になる | 更新情報をチェックする

Swing の JTable に 1) WEBのJSON2次元配列。2) WEB の JSON配列 を取り込んで表示する( Google Gson と okhttp を使用 )



環境準備

Eclipse は Pleiades の Neon です。Swing を使用する為の WindowBuilder のインストールとプロジェクト作成は、▼ 以下のリンク先を参照して下さい。

Pleiades All in One(NEON) で、Windows アプリを作成する手順( WindowBuilder + Swing デザイナー or SWT デザイナー[JFace] )

WEB へのアクセスは okhttp を使用します。ライブラリは MAVEN で取り込みます。okhttp は okio に依存しますが、リポジトリのファイルが壊れている場合は、okio の dependency も記述して、旧バージョンを順に試すといいでしょう。

JSON 文字列の扱いは、Google Gson を使用します

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>WinApp</groupId>
  <artifactId>WinApp</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <dependencies>
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.8.0</version>
    </dependency>
    <dependency>
      <groupId>com.squareup.okhttp3</groupId>
      <artifactId>okhttp</artifactId>
      <version>3.8.0</version>
    </dependency>    
  </dependencies>
  
  <build>
    <sourceDirectory>src</sourceDirectory>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.5.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
MAVEN プロジェクトへの変更は、プロジェクトを右クリックして『構成』から、選択できます

JTable は表示のみ対応した、JTableUnit と言うクラスを作成して使用しています。

JTableUnit
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;

public class JTableUnit extends JTable {
	
	private JScrollPane _scrollPane;

	public JTableUnit( ) {
		super();
		_scrollPane = new JScrollPane(this);
	}
	
	@Override
	public boolean isCellEditable(int row, int column) {
		return false;
	}	
	
	public JScrollPane getScrollPane() {
		return _scrollPane; 
	}

	// ********************
	// 初期化
	// ********************
	public void reset() {

		DefaultTableModel dtm = (DefaultTableModel) this.getModel();
		dtm.setRowCount(0);
		
		int cols = this.getColumnCount();

		for( int i = cols-1; i >= 0; i-- ) {
			this.removeColumn((this.getColumnModel()).getColumn(i));
		}
		
		// データモデルも初期化
		dtm.setColumnCount(0);

	}
	// ********************
	// 行を全て削除
	// ********************
	public void clear(){
		DefaultTableModel dtm = (DefaultTableModel) this.getModel();
		dtm.setRowCount(0);
	}
	public void addColumn(String name) {
		DefaultTableModel dtm = (DefaultTableModel) this.getModel();
		dtm.addColumn(name);
	}
	public void setColumnTitle(String name,String title) {
		TableColumn tc = this.getColumn(name);
		 tc.setHeaderValue(title);
		 tc.setIdentifier(name);
	}
	public void addRow() {
		DefaultTableModel dtm = (DefaultTableModel) this.getModel();
		Object[] obj  = null;
		dtm.addRow(obj);
	}
	public void setColumn(int row, int col, String data) {
		this.setValueAt( data, row,  col );
	}
}


前提条件

JSON データは utf-8 です。

WEB 上の2次元配列データは https://lightbox.sakura.ne.jp/demo/json/syain_array.php です。

WEB 上の通常 JSON配列データは https://lightbox.sakura.ne.jp/demo/json/syain_json.php です。

3番目の処理では、JSON データのフォーマットに合わせたクラス(Syain) を作成して使用しています

public class Syain {

	String 社員コード;
	String 氏名;
	String フリガナ;
	String 所属;
	String 性別;
	String 給与;
	String 手当;
	String 管理者;

	String 作成日;
	String 更新日;
	
}


処理

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Set;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;

import com.google.gson.Gson;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class Main extends JFrame {

	private JPanel contentPane;
	private JTableUnit table;
	private JPanel panel;

	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					Main frame = new Main();
					frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	public Main() {
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 1000, 700);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		setContentPane(contentPane);
		contentPane.setLayout(null);

		JButton buttonArray = new JButton("2次元配列");
		buttonArray.setBounds(10, 10, 112, 21);
		buttonArray.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {

				loadWebJsonArray();

			}
		});
		contentPane.add(buttonArray);

		JButton buttonProp = new JButton("JSONプロパティでタイトル");
		buttonProp.setBounds(134, 10, 218, 21);
		buttonProp.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {

				loadWebJson();

			}
		});
		contentPane.add(buttonProp);
		
		JButton buttonFix = new JButton("タイトルを内部で固定");
		buttonFix.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				
				loadWebJsonFix();
				
			}
		});
		buttonFix.setBounds(364, 10, 180, 21);
		contentPane.add(buttonFix);

		JButton buttonClear = new JButton("行クリア");
		buttonClear.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				table.clear();
			}
		});
		buttonClear.setBounds(574, 10, 150, 21);
		contentPane.add(buttonClear);

		panel = new JPanel();
		panel.setBounds(12, 52, 960, 600);
		panel.setLayout(new BorderLayout(0, 0));
		contentPane.add(panel);

		table = new JTableUnit();
		panel.add(table.getScrollPane());

	}

	// *************************
	// 2次元配列
	// *************************
	private void loadWebJsonArray(){

		table.reset();

		String url = "https://lightbox.sakura.ne.jp/demo/json/syain_array.php";
		String result ="[\"error\"]";
		OkHttpClient client = new OkHttpClient();
		Request.Builder builder = new Request.Builder();
		builder.url(url);
		Request request = builder.build();

		Response response = null;
		try {
			response = client.newCall(request).execute();
			result = response.body().string();
		}
		catch (IOException e) {
			e.printStackTrace();
		}

		Gson gson = new Gson();
		String[][] data = gson.fromJson(result, String[][].class);

		int line_count = -1;
		int col_count = 0;
		for( String[] row : data ) {

			// 初回( タイトル部分 )
			if ( line_count == -1 ) {
				col_count = 0;
				for( String col : row ) {
					table.addColumn(String.format("col%d", col_count) );
					col_count++;
				}
				col_count = 0;
				for( String col : row ) {
					table.setColumnTitle(String.format("col%d", col_count), col );
					col_count++;
				}
				line_count++;

				// タイトル部分が一行あるので、次のデータをすぐに読む
				continue;
			}

			// 2回目以降( データ部分 )

			// 空行の追加
			table.addRow();
			col_count = 0;
			for( String col : row ) {
				// line_count に、
				table.setColumn(line_count, col_count, col );
				col_count++;
			}
			line_count++;
		}

	}

	// *************************
	// JSONプロパティでタイトル
	// *************************
	private void loadWebJson(){

		table.reset();

		String url = "https://lightbox.sakura.ne.jp/demo/json/syain_json.php";
		String result ="[\"error\"]";
		OkHttpClient client = new OkHttpClient();
		Request.Builder builder = new Request.Builder();
		builder.url(url);
		Request request = builder.build();

		Response response = null;
		try {
			response = client.newCall(request).execute();
			result = response.body().string();
		}
		catch (IOException e) {
			e.printStackTrace();
		}

		Gson gson = new Gson();
		// キーの順序を保持する為に、LinkedHashMap を使用
		LinkedHashMap<String,String>[] data = gson.fromJson(result, LinkedHashMap[].class);

		int line_count = 0;
		int col_count = 0;
		Set<String> keyset = null;
		Collection<String> values = null;
		Iterator<String> it;
		for( LinkedHashMap<String, String> row_data : data ) {

			// 初回( タイトル部分 )
			if ( keyset == null ) {
				keyset = row_data.keySet();

				col_count = 0;
				it = keyset.iterator();
				while( it.hasNext() ) {
					it.next();
					table.addColumn(String.format("col%d", col_count) );
					col_count++;
				}
				col_count = 0;
				it = keyset.iterator();
				while( it.hasNext() ) {
					String col_data = it.next();
					table.setColumnTitle(String.format("col%d", col_count), col_data );
					col_count++;
				}

			}

			// 空行の追加
			table.addRow();

			// 2回目以降( データ部分 )
			col_count = 0;
			values = row_data.values();
			it = values.iterator();
			while( it.hasNext() ) {
				String col_data = it.next();
				table.setColumn(line_count, col_count, col_data );
				col_count++;
			}
			line_count++;

		}

	}

	// *************************
	// タイトルを内部で固定
	// *************************
	private void loadWebJsonFix(){

		table.reset();
		for( int i = 0; i < 10; i++ ) {
			table.addColumn(String.format("col%d", i) );
		}
		table.setColumnTitle("col0", "社員コード");
		table.setColumnTitle("col1", "氏名");
		table.setColumnTitle("col2", "フリガナ");
		table.setColumnTitle("col3", "所属");
		table.setColumnTitle("col4", "性別");
		table.setColumnTitle("col5", "給与");
		table.setColumnTitle("col6", "手当");
		table.setColumnTitle("col7", "管理者");
		table.setColumnTitle("col8", "作成日");
		table.setColumnTitle("col9", "更新日");

		String url = "https://lightbox.sakura.ne.jp/demo/json/syain_json.php";
		String result ="[\"error\"]";
		OkHttpClient client = new OkHttpClient();
		Request.Builder builder = new Request.Builder();
		builder.url(url);
		Request request = builder.build();

		Response response = null;
		try {
			response = client.newCall(request).execute();
			result = response.body().string();
		}
		catch (IOException e) {
			e.printStackTrace();
		}

		Gson gson = new Gson();
		// データ部分のみ使用
		Syain[] data = gson.fromJson(result, Syain[].class);

		int line_count = 0;
		int col_count = 0;
		for( Syain row_data : data ) {

			// 空行の追加
			table.addRow();
			// 行データをセット
			table.setColumn(line_count, 0, row_data.社員コード );
			table.setColumn(line_count, 1, row_data.氏名 );
			table.setColumn(line_count, 2, row_data.フリガナ );
			table.setColumn(line_count, 3, row_data.所属 );
			table.setColumn(line_count, 4, row_data.性別 );
			table.setColumn(line_count, 5, row_data.給与 );
			table.setColumn(line_count, 6, row_data.手当 );
			table.setColumn(line_count, 7, row_data.管理者 );
			table.setColumn(line_count, 8, row_data.作成日 );
			table.setColumn(line_count, 9, row_data.更新日 );

			line_count++;

		}

	}

}


画面にデザイナで Panel を作成しておいて、JScrollPane 込みで作成されている JTableUnit を Panel に組み込んでいます(96、97 行)。

2番目の処理は、JSON のフォーマットに対応した クラスを使わずに、LinkedHashMap を使用して key 部分を取り出してタイトルに使用しています。

3番目の処理は、固定的な処理を行う一般的なアプリケーションで有用で、ソースの可読性が格段にアップします。


タグ:java swing JSON gson
posted by lightbox at 2017-06-03 18:25 | java : Swing | このブログの読者になる | 更新情報をチェックする

Swing の JTable に 1) ローカルの CSV。2) WEB の CSV を取り込んで表示する



環境準備

Eclipse は Pleiades の Neon です。Swing を使用する為の WindowBuilder のインストールとプロジェクト作成は、▼ 以下のリンク先を参照して下さい。

Pleiades All in One(NEON) で、Windows アプリを作成する手順( WindowBuilder + Swing デザイナー or SWT デザイナー[JFace] )

WEB へのアクセスは okhttp を使用します。ライブラリは MAVEN で取り込みます。okhttp は okio に依存しますが、リポジトリのファイルが壊れている場合は、okio の dependency も記述して、旧バージョンを順に試すといいでしょう。

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>WinApp</groupId>
  <artifactId>WinApp</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <dependencies>
    <dependency>
      <groupId>com.squareup.okhttp3</groupId>
      <artifactId>okhttp</artifactId>
      <version>3.8.0</version>
    </dependency>    
  </dependencies>
  
  <build>
    <sourceDirectory>src</sourceDirectory>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.5.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

MAVEN プロジェクトへの変更は、プロジェクトを右クリックして『構成』から、選択できます

JTable は表示のみ対応した、JTableUnit と言うクラスを作成して使用しています。

JTableUnit
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;

public class JTableUnit extends JTable {
	
	private JScrollPane _scrollPane;

	public JTableUnit( ) {
		super();
		_scrollPane = new JScrollPane(this);
	}
	
	@Override
	public boolean isCellEditable(int row, int column) {
		return false;
	}	
	
	public JScrollPane getScrollPane() {
		return _scrollPane; 
	}

	// ********************
	// 初期化
	// ********************
	public void reset() {

		DefaultTableModel dtm = (DefaultTableModel) this.getModel();
		dtm.setRowCount(0);
		
		int cols = this.getColumnCount();

		for( int i = cols-1; i >= 0; i-- ) {
			this.removeColumn((this.getColumnModel()).getColumn(i));
		}
		
		// データモデルも初期化
		dtm.setColumnCount(0);

	}
	// ********************
	// 行を全て削除
	// ********************
	public void clear(){
		DefaultTableModel dtm = (DefaultTableModel) this.getModel();
		dtm.setRowCount(0);
	}
	public void addColumn(String name) {
		DefaultTableModel dtm = (DefaultTableModel) this.getModel();
		dtm.addColumn(name);
	}
	public void setColumnTitle(String name,String title) {
		TableColumn tc = this.getColumn(name);
		 tc.setHeaderValue(title);
		 tc.setIdentifier(name);
	}
	public void addRow() {
		DefaultTableModel dtm = (DefaultTableModel) this.getModel();
		Object[] obj  = null;
		dtm.addRow(obj);
	}
	public void setColumn(int row, int col, String data) {
		this.setValueAt( data, row,  col );
	}
}


前提条件

CSV は、ローカル・WEB 共に shift_jis です。
WEB 上のデータは https://lightbox.sakura.ne.jp/demo/json/syain_csv.php です。

処理

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class Main extends JFrame {

	private JPanel contentPane;
	private JTableUnit table;
	private JPanel panel;

	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					Main frame = new Main();
					frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	public Main() {
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 1000, 700);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		setContentPane(contentPane);
		contentPane.setLayout(null);

		JButton buttonLocal = new JButton("ローカルCSV読込み");
		buttonLocal.setBounds(10, 10, 150, 21);
		buttonLocal.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {

				loadCsv();

			}
		});
		contentPane.add(buttonLocal);

		JButton buttonWeb = new JButton("WEBCSV読込み");
		buttonWeb.setBounds(172, 10, 150, 21);
		buttonWeb.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {

				loadWebCsv();

			}
		});
		contentPane.add(buttonWeb);

		JButton buttonClear = new JButton("行クリア");
		buttonClear.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				table.clear();
			}
		});
		buttonClear.setBounds(334, 10, 150, 21);
		contentPane.add(buttonClear);

		panel = new JPanel();
		panel.setBounds(12, 52, 960, 600);
		panel.setLayout(new BorderLayout(0, 0));
		contentPane.add(panel);

		table = new JTableUnit();
		panel.add(table.getScrollPane());



	}

	// *************************
	// ローカルCSV読込み
	// *************************
	private void loadCsv(){

		table.reset();

		try {
			FileInputStream fis = new FileInputStream("C:\\Users\\lightbox\\Documents\\社員.csv");
			InputStreamReader isr = new InputStreamReader(fis, "MS932");
			BufferedReader br = new BufferedReader(isr);

			String cols_string;
			String[] col_data = null;
			int line_count = 0;
			while ( null != (cols_string = br.readLine() ) ) {

				// 空行を無視する
				if (cols_string.equals("")) {
					continue;
				}

				// 初回( タイトル部分 )
				if ( col_data == null ) {
					col_data = cols_string.split(",");

					// addColumn と  setColumnTitle は別々に実行する必要があります
					for( int i = 0; i < col_data.length; i++ ) {
						// カラムの ID を定義しています
						table.addColumn(String.format("col%d", i) );
					}
					for( int i = 0; i < col_data.length; i++ ) {
						// カラムの ID に対してタイトル文字列をセットしています
						table.setColumnTitle(String.format("col%d", i), col_data[i] );
					}
					continue;
				}

				// 2回目以降( データ部分 )
				col_data = cols_string.split(",");

				// 空行の追加
				table.addRow();
				for( int i = 0; i < col_data.length; i++ ) {
					// 行, カラム でデータをセット
					table.setColumn(line_count, i, col_data[i] );
				}
				line_count++;
			}

			br.close();
			isr.close();
			fis.close();
		}
		catch (Exception e) {
			e.printStackTrace();
		}

	}

	// *************************
	// WEBCSV読込み
	// *************************
	private void loadWebCsv(){

		table.reset();

		String url = "https://lightbox.sakura.ne.jp/demo/json/syain_csv.php";
		String result ="[\"error\"]";
		OkHttpClient client = new OkHttpClient();
		Request.Builder builder = new Request.Builder();
		builder.url(url);
		Request request = builder.build();

		Response response = null;
		try {
			response = client.newCall(request).execute();
			result = response.body().string();
		}
		catch (IOException e) {
			e.printStackTrace();
		}

		String[] row_data = result.split("\n");
		String[] col_data = null;
		int line_count = 0;
		for( String cols_string : row_data ) {

			// 空行を無視する
			if (cols_string.equals("")) {
				continue;
			}

			// 初回( タイトル部分 )
			if ( col_data == null ) {
				col_data = cols_string.split(",");
				for( int i = 0; i < col_data.length; i++ ) {
					// カラムの ID を定義しています
					table.addColumn(String.format("col%d", i) );
				}
				for( int i = 0; i < col_data.length; i++ ) {
					// カラムの ID に対してタイトル文字列をセットしています
					table.setColumnTitle(String.format("col%d", i), col_data[i] );
				}
				continue;
			}

			// 2回目以降( データ部分 )
			col_data = cols_string.split(",");

			// 空行の追加
			table.addRow();
			col_data = cols_string.split(",");
			for( int i = 0; i < col_data.length; i++ ) {
				// 行, カラム でデータをセット
				table.setColumn(line_count, i, col_data[i] );
			}
			line_count++;

		}
	}

}

画面にデザイナで Panel を作成しておいて、JScrollPane 込みで作成されている JTableUnit を Panel に組み込んでいます(82、83 行)。

okhttp での キャラクタセットは、サーバ側から送られた Content-Type を元に response.body().string() 実行時に自動変換されます。



タグ:swing java OkHttp
posted by lightbox at 2017-06-03 17:48 | java : Swing | このブログの読者になる | 更新情報をチェックする
Seesaa の各ページの表示について
Seesaa の 記事がたまに全く表示されない場合があります。その場合は、設定> 詳細設定> ブログ設定 で 最新の情報に更新の『実行ボタン』で記事やアーカイブが最新にビルドされます。

Seesaa のページで、アーカイブとタグページは要注意です。タグページはコンテンツが全く無い状態になりますし、アーカイブページも歯抜けページはコンテンツが存在しないのにページが表示されてしまいます。

また、カテゴリページもそういう意味では完全ではありません。『カテゴリID-番号』というフォーマットで表示されるページですが、実際存在するより大きな番号でも表示されてしまいます。

※ インデックスページのみ、実際の記事数を超えたページを指定しても最後のページが表示されるようです

対処としては、このようなヘルプ的な情報を固定でページの最後に表示するようにするといいでしょう。具体的には、メインの記事コンテンツの下に『自由形式』を追加し、アーカイブとカテゴリページでのみ表示するように設定し、コンテンツを用意するといいと思います。


※ エキスパートモードで表示しています

アーカイブとカテゴリページはこのように簡単に設定できますが、タグページは HTML 設定を直接変更して、以下の『タグページでのみ表示される内容』の記述方法で設定する必要があります

<% if:page_name eq 'archive' -%>
アーカイブページでのみ表示される内容
<% /if %>

<% if:page_name eq 'category' -%>
カテゴリページでのみ表示される内容
<% /if %>

<% if:page_name eq 'tag' -%>
タグページでのみ表示される内容
<% /if %>
この記述は、以下の場所で使用します
container 終わり



フリーフォントで簡単ロゴ作成
フリーフォントでボタン素材作成
フリーフォントで吹き出し画像作成
フリーフォントではんこ画像作成
ほぼ自由に利用できるフリーフォント
フリーフォントの書体見本とサンプル
画像を大きく見る為のウインドウを開くボタンの作成

CSS ドロップシャドウの参考デモ
イラストAC
ぱくたそ
写真素材 足成
フリーフォント一覧
utf8 文字ツール
右サイド 終わり
base 終わり