C#Windowsコンソールアプリで現在の行を更新するにはどうすればよいですか?


507

C#でWindowsコンソールアプリを構築する場合、現在の行を拡張したり新しい行に移動したりすることなく、コンソールに書き込むことはできますか?たとえば、プロセスがどれだけ完了しているかを表すパーセンテージを表示したい場合は、カーソルと同じ行の値を更新するだけで、各パーセンテージを新しい行に置く必要はありません。

これは「標準」のC#コンソールアプリで実行できますか?


クールなコマンドラインインターフェイスに本当に興味がある場合は、curses / ncursesを確認してください。
Charles Addis

@CharlesAddisがcurses / ncursesはC ++でのみ機能しませんか?
Xam 2018年

回答:


783

"\r"コンソールのみに出力する場合、カーソルは現在の行の先頭に戻り、それを書き直すことができます。これでうまくいくはずです:

for(int i = 0; i < 100; ++i)
{
    Console.Write("\r{0}%   ", i);
}

番号の後ろのいくつかのスペースに注目して、以前にあったものがすべて消去されることを確認してください。
また、行の終わりに「\ n」を追加したくないので、Write()代わりにを使用していることに注意しWriteLine()てください。


7
for(int i = 0; i <= 100; ++ i)は100%になります
Nicolas Tyler

13
以前の書き込みが新しい書き込みよりも長い場合、どのように処理しますか?コンソールの幅を取得し、行にスペースを埋め込む方法はありますか?
Drew Chapin 2013年

6
@druciferre頭から離れて、あなたの質問に対する2つの答えを考えることができます。どちらも、現在の出力を最初に文字列として保存し、次のように一定の文字数でパディングします。Console.Write( "\ r {0}"、strOutput.PadRight(nPaddingCount、 '')); 「nPaddingCount」は自分で設定した数値にすることも、以前の出力を追跡してnPaddingCountを以前の出力と現在の出力の長さの差と現在の出力の長さとして設定することもできます。nPaddingCountが負の場合、abs(prev.len-curr.len)を実行しない限り、PadRightを使用する必要はありません。
John Odom、2014年

1
@malgmよく整理されたコード。ダーススレッドのいずれかがいつでもコンソールに書き込むことができる場合、新しい行を書き込むかどうかに関係なく、問題が発生します。
マーク

2
@JohnOdomは、前の(パディングされていない)出力長を保持し、それを最初の引数として入力するだけです(もちろんPadRight、パディングされていない文字列または長さを最初に保存します)。
Jesper Matthiesen、2018年

254

を使用Console.SetCursorPositionしてカーソルの位置を設定し、現在の位置に書き込むことができます。

以下は、単純な「スピナー」を示すです。

static void Main(string[] args)
{
    var spin = new ConsoleSpinner();
    Console.Write("Working....");
    while (true) 
    {
        spin.Turn();
    }
}

public class ConsoleSpinner
{
    int counter;

    public void Turn()
    {
        counter++;        
        switch (counter % 4)
        {
            case 0: Console.Write("/"); counter = 0; break;
            case 1: Console.Write("-"); break;
            case 2: Console.Write("\\"); break;
            case 3: Console.Write("|"); break;
        }
        Thread.Sleep(100);
        Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
    }
}

既存の出力を新しい出力または空白で必ず上書きする必要があることに注意してください。

更新:この例ではカーソルを1文字だけ戻すと批判されているので、わかりやすくするためにこれを追加しSetCursorPositionます。を使用して、カーソルをコンソールウィンドウの任意の位置に設定できます。

Console.SetCursorPosition(0, Console.CursorTop);

カーソルを現在の行の先頭に設定します(またはConsole.CursorLeft = 0直接使用できます)。


8
問題は\ rを使用して解決できるかもしれませんが、SetCursorPosition(またはCursorLeft)を使用すると、たとえば行の先頭に書き込まない、ウィンドウ内で上に移動するなど、より柔軟に使用できるため、出力などに使用できるより一般的なアプローチです。カスタムプログレスバーまたはASCIIグラフィック。
Dirk Vollmar、2009年

14
+1は冗長であり、義務の要請を超えて行きます。良いものに感謝します。
コパス2009年

1
それを行う別の方法を示すための+1。他のすべての人が\ rを示し、OPが単にパーセンテージを更新している場合は、これで行全体を書き直す必要なく値を更新できます。OPは実際には行の先頭に移動したいとは言っていませんでした。カーソルと同じ行で何かを更新したいだけです。
アンディ

1
SetCursorPositionの追加された柔軟性は、ユーザーが気づくのに十分な長さのループの場合、速度が少し低下し、カーソルがちらつくという犠牲を伴います。以下の私のテストコメントを参照してください。
ケビン

5
また、行の長さが原因でコンソールが次の行に折り返されないことを確認してください。そうしないと、コンソールウィンドウでコンテンツが実行されないという問題が発生する可能性があります。
Mandrake 2012年

84

これまでのところ、これを行う方法について3つの競合する選択肢があります。

Console.Write("\r{0}   ", value);                      // Option 1: carriage return
Console.Write("\b\b\b\b\b{0}", value);                 // Option 2: backspace
{                                                      // Option 3 in two parts:
    Console.SetCursorPosition(0, Console.CursorTop);   // - Move cursor
    Console.Write(value);                              // - Rewrite
}

私は常にConsole.CursorLeft = 03番目のオプションのバリエーションであるを使用してきたため、いくつかのテストを行うことにしました。これが私が使ったコードです:

public static void CursorTest()
{
    int testsize = 1000000;

    Console.WriteLine("Testing cursor position");
    Stopwatch sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < testsize; i++)
    {
        Console.Write("\rCounting: {0}     ", i);
    }
    sw.Stop();
    Console.WriteLine("\nTime using \\r: {0}", sw.ElapsedMilliseconds);

    sw.Reset();
    sw.Start();
    int top = Console.CursorTop;
    for (int i = 0; i < testsize; i++)
    {
        Console.SetCursorPosition(0, top);        
        Console.Write("Counting: {0}     ", i);
    }
    sw.Stop();
    Console.WriteLine("\nTime using CursorLeft: {0}", sw.ElapsedMilliseconds);

    sw.Reset();
    sw.Start();
    Console.Write("Counting:          ");
    for (int i = 0; i < testsize; i++)
    {        
        Console.Write("\b\b\b\b\b\b\b\b{0,8}", i);
    }

    sw.Stop();
    Console.WriteLine("\nTime using \\b: {0}", sw.ElapsedMilliseconds);
}

私のマシンでは、次の結果が得られます。

  • バックスペース:25.0秒
  • キャリッジリターン:28.7秒
  • SetCursorPosition:49.7秒

さらに、SetCursorPositionどちらの方法でも見られなかった顕著なちらつきを引き起こしました。したがって、道徳は可能な限りバックスペースまたは改行使用することであり、これを行うためのより速い方法を教えてくれてありがとう、SO!


更新:コメントで、JoelはSetCursorPositionが移動距離に対して一定であり、他のメソッドは線形であると提案しています。これが事実である、ことをさらに試験確認遅い時定数とは依然として遅いです。私のテストでは、バックスペースの長い文字列をコンソールに書き込む方が、約60文字になるまでSetCursorPositionよりも高速です。60文字(程度)よりも短いラインの一部を置き換えるために高速です、バックスペースはそう、それがちらつきませんので、私はR \の上にB \の私の最初の裏書に立つつもりだとSetCursorPosition


4
問題の操作の効率は、実際には問題になりません。これはすべて、ユーザーが気付くには速すぎて発生するはずです。不要なマイクロプティマイズは悪いです。
マルフィスト2009年

@Malfist:ループの長さに応じて、ユーザーは気づく場合と気づかない場合があります。上記の編集で追加したように(コメントを見る前に)、SetCursorPositionがちらつきを発生させ、他のオプションのほぼ2倍の時間がかかります。
ケビン

1
それはマイクロ最適化(100万回実行し、50秒かかることはまだ非常に短い時間です)、結果の+1であり、知ることは間違いなく非常に役立つことに同意します。
アンディ

6
ベンチマークには根本的な欠陥があります。SetCursorPosition()の時間は、カーソルがどこまで移動しても同じである可能性がありますが、他のオプションは、コンソールが処理する必要がある文字数によって異なります。
Joel Coehoorn、2009年

1
これは、利用可能なさまざまなオプションの非常に良い要約です。ただし、\ rを使用するとちらつきも発生します。\ bを使用すると、修正テキスト( "Counting:")が書き換えられないため、ちらつきは明らかにありません。\ bおよびSetCursorPositionで発生しているため、さらに\ bを追加して修正テキストを書き直すと、ちらつきが発生します。Joelの発言について:Joelは基本的に正しいですが、\ rは非常に長い行でSetCursorPositionよりも優れていますが、違いは少なくなります。
Dirk Vollmar、2009年

27

\ b(バックスペース)エスケープシーケンスを使用して、現在の行の特定の数の文字をバックアップできます。これは現在の場所を移動するだけで、文字は削除されません。

例えば:

string line="";

for(int i=0; i<100; i++)
{
    string backup=new string('\b',line.Length);
    Console.Write(backup);
    line=string.Format("{0}%",i);
    Console.Write(line);
}

ここで、lineはコンソールに書き込むパーセント行です。トリックは、前の出力に対して正しい数の\ b文字を生成することです。

\ rアプローチに対するこれの利点は、パーセンテージ出力が行の先頭にない場合でも機能することです。


1
+1、これが提示された最速の方法であることが判明(下記の私のテストコメントを参照)
Kevin

19

\rこれらのシナリオに使用されます。
\r キャリッジリターンを表します。これは、カーソルが行の先頭に戻ることを意味します。
これが、Windowsが\n\r新しい行マーカーとして使用する理由です。
\n行を下に移動し、行\rの先頭に戻ります。


22
ただし、実際には\ r \ nです。
Joel Mueller

14

私は歌手のConsoleSpinnerクラスで遊んだだけでした。Mineはそれほど簡潔ではありませんが、そのクラスのユーザーが独自のwhile(true)ループを記述しなければならないというのは、私とうまく調和していませんでした。私はこのような経験のために撮影しています:

static void Main(string[] args)
{
    Console.Write("Working....");
    ConsoleSpinner spin = new ConsoleSpinner();
    spin.Start();

    // Do some work...

    spin.Stop(); 
}

そして、私はそれを以下のコードで実現しました。私はしたくないので、Start()ブロックにする方法を、私は、ユーザーが書き込みを心配する必要はしたくないwhile(spinFlag)様ループを、と私は私が処理するために別のスレッドを生成しなければならなかった、同時に複数のスピナーを許可します紡糸。そして、それはコードがはるかに複雑でなければならないことを意味します。

また、私はそれほど多くのマルチスレッド処理を行っていないため、微妙なバグを1つまたは3つ残した可能性があります(たぶん均等です)。しかし、これまでのところかなりうまくいくようです:

public class ConsoleSpinner : IDisposable
{       
    public ConsoleSpinner()
    {
        CursorLeft = Console.CursorLeft;
        CursorTop = Console.CursorTop;  
    }

    public ConsoleSpinner(bool start)
        : this()
    {
        if (start) Start();
    }

    public void Start()
    {
        // prevent two conflicting Start() calls ot the same instance
        lock (instanceLocker) 
        {
            if (!running )
            {
                running = true;
                turner = new Thread(Turn);
                turner.Start();
            }
        }
    }

    public void StartHere()
    {
        SetPosition();
        Start();
    }

    public void Stop()
    {
        lock (instanceLocker)
        {
            if (!running) return;

            running = false;
            if (! turner.Join(250))
                turner.Abort();
        }
    }

    public void SetPosition()
    {
        SetPosition(Console.CursorLeft, Console.CursorTop);
    }

    public void SetPosition(int left, int top)
    {
        bool wasRunning;
        //prevent other start/stops during move
        lock (instanceLocker)
        {
            wasRunning = running;
            Stop();

            CursorLeft = left;
            CursorTop = top;

            if (wasRunning) Start();
        } 
    }

    public bool IsSpinning { get { return running;} }

    /* ---  PRIVATE --- */

    private int counter=-1;
    private Thread turner; 
    private bool running = false;
    private int rate = 100;
    private int CursorLeft;
    private int CursorTop;
    private Object instanceLocker = new Object();
    private static Object console = new Object();

    private void Turn()
    {
        while (running)
        {
            counter++;

            // prevent two instances from overlapping cursor position updates
            // weird things can still happen if the main ui thread moves the cursor during an update and context switch
            lock (console)
            {                  
                int OldLeft = Console.CursorLeft;
                int OldTop = Console.CursorTop;
                Console.SetCursorPosition(CursorLeft, CursorTop);

                switch (counter)
                {
                    case 0: Console.Write("/"); break;
                    case 1: Console.Write("-"); break;
                    case 2: Console.Write("\\"); break;
                    case 3: Console.Write("|"); counter = -1; break;
                }
                Console.SetCursorPosition(OldLeft, OldTop);
            }

            Thread.Sleep(rate);
        }
        lock (console)
        {   // clean up
            int OldLeft = Console.CursorLeft;
            int OldTop = Console.CursorTop;
            Console.SetCursorPosition(CursorLeft, CursorTop);
            Console.Write(' ');
            Console.SetCursorPosition(OldLeft, OldTop);
        }
    }

    public void Dispose()
    {
        Stop();
    }
}

サンプルコードは私のものではありませんが、素晴らしい変更です。これはBrad Abramsのブログから引用したものです(私の回答のリンクを参照してください)。SetCursorPositionを示す簡単なサンプルとして書かれただけだと思います。ところで、私が単純なサンプルだと思っていたものについての議論が始まったことについて、(ポジティブな意味で)確かに驚いています。それが私がこのサイトを愛する理由です:-)
Dirk Vollmar

4

改行(\ n)を最後に(暗黙的または明示的に)使用するのではなく、行の先頭に明示的にCarrage Return(\ r)を使用すると、必要な結果が得られます。例えば:

void demoPercentDone() {
    for(int i = 0; i < 100; i++) {
        System.Console.Write( "\rProcessing {0}%...", i );
        System.Threading.Thread.Sleep( 1000 );
    }
    System.Console.WriteLine();    
}

-1、質問でC#が要求されます。C#で書き直し、F#に戻します
Malfist

C#をF#に戻すのではなく、編集の競合のように見えます。彼の変化はあなたの変化の1分後であり、スプリントに焦点を合わせました。
アンディ

編集ありがとうございます。私はF#インタラクティブモードを使用して物事をテストする傾向があり、重要な部分はC#でも同じであるBCL呼び出しであると考えました。
James Hugard、

3
    public void Update(string data)
    {
        Console.Write(string.Format("\r{0}", "".PadLeft(Console.CursorLeft, ' ')));
        Console.Write(string.Format("\r{0}", data));
    }

1

MSDNのコンソールドキュメントから:

この問題を解決するには、OutプロパティまたはErrorプロパティのTextWriter.NewLineプロパティを別の行終端文字列に設定します。たとえば、C#ステートメントConsole.Error.NewLine = "\ r \ n \ r \ n";は、標準エラー出力ストリームの行終端文字列を2つのキャリッジリターンとラインフィードシーケンスに設定します。次に、C#ステートメントのConsole.Error.WriteLine();のように、エラー出力ストリームオブジェクトのWriteLineメソッドを明示的に呼び出すことができます。

だから-私はこれをしました:

Console.Out.Newline = String.Empty;

その後、自分で出力を制御できます。

Console.WriteLine("Starting item 1:");
    Item1();
Console.WriteLine("OK.\nStarting Item2:");

そこに到達する別の方法。


あなただけの...改行プロパティを再定義することなく、同じ目的のためにConsole.Write()を使用することができます
Radosławジェール

1

これは、生成ファイルをクールに見せたい場合に機能します。

                int num = 1;
                var spin = new ConsoleSpinner();
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("");
                while (true)
                {
                    spin.Turn();
                    Console.Write("\r{0} Generating Files ", num);
                    num++;
                }

そして、これは私が以下のいくつかの答えから得てそれを変更した方法です

public class ConsoleSpinner
    {
        int counter;

        public void Turn()
        {
            counter++;
            switch (counter % 4)
            {
                case 0: Console.Write("."); counter = 0; break;
                case 1: Console.Write(".."); break;
                case 2: Console.Write("..."); break;
                case 3: Console.Write("...."); break;
                case 4: Console.Write("\r"); break;
            }
            Thread.Sleep(100);
            Console.SetCursorPosition(23, Console.CursorTop);
        }
    }

0

ここに別のものがあります:D

class Program
{
    static void Main(string[] args)
    {
        Console.Write("Working... ");
        int spinIndex = 0;
        while (true)
        {
            // obfuscate FTW! Let's hope overflow is disabled or testers are impatient
            Console.Write("\b" + @"/-\|"[(spinIndex++) & 3]);
        }
    }
}

0

1行を更新したいが、情報が長すぎて1行に表示できない場合は、新しい行が必要になることがあります。私はこの問題に遭遇しました。以下はこれを解決する1つの方法です。

public class DumpOutPutInforInSameLine
{

    //content show in how many lines
    int TotalLine = 0;

    //start cursor line
    int cursorTop = 0;

    // use to set  character number show in one line
    int OneLineCharNum = 75;

    public void DumpInformation(string content)
    {
        OutPutInSameLine(content);
        SetBackSpace();

    }
    static void backspace(int n)
    {
        for (var i = 0; i < n; ++i)
            Console.Write("\b \b");
    }

    public  void SetBackSpace()
    {

        if (TotalLine == 0)
        {
            backspace(OneLineCharNum);
        }
        else
        {
            TotalLine--;
            while (TotalLine >= 0)
            {
                backspace(OneLineCharNum);
                TotalLine--;
                if (TotalLine >= 0)
                {
                    Console.SetCursorPosition(OneLineCharNum, cursorTop + TotalLine);
                }
            }
        }

    }

    private void OutPutInSameLine(string content)
    {
        //Console.WriteLine(TotalNum);

        cursorTop = Console.CursorTop;

        TotalLine = content.Length / OneLineCharNum;

        if (content.Length % OneLineCharNum > 0)
        {
            TotalLine++;

        }

        if (TotalLine == 0)
        {
            Console.Write("{0}", content);

            return;

        }

        int i = 0;
        while (i < TotalLine)
        {
            int cNum = i * OneLineCharNum;
            if (i < TotalLine - 1)
            {
                Console.WriteLine("{0}", content.Substring(cNum, OneLineCharNum));
            }
            else
            {
                Console.Write("{0}", content.Substring(cNum, content.Length - cNum));
            }
            i++;

        }
    }

}
class Program
{
    static void Main(string[] args)
    {

        DumpOutPutInforInSameLine outPutInSameLine = new DumpOutPutInforInSameLine();

        outPutInSameLine.DumpInformation("");
        outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");


        outPutInSameLine.DumpInformation("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
        outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");

        //need several lines
        outPutInSameLine.DumpInformation("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
        outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");

        outPutInSameLine.DumpInformation("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
        outPutInSameLine.DumpInformation("bbbbbbbbbbbbbbbbbbbbbbbbbbb");

    }
}

0

私はvb.netで同じ解決策を探していましたが、これを見つけて素晴らしいです。

ただし、@ JohnOdomは、前のスペースが現在のスペースよりも大きい場合に空白スペースを処理するためのより良い方法を提案しました。

私はvb.netで関数を作成し、誰かが助けてもらえると思った。

これが私のコードです:

Private Sub sPrintStatus(strTextToPrint As String, Optional boolIsNewLine As Boolean = False)
    REM intLastLength is declared as public variable on global scope like below
    REM intLastLength As Integer
    If boolIsNewLine = True Then
        intLastLength = 0
    End If
    If intLastLength > strTextToPrint.Length Then
        Console.Write(Convert.ToChar(13) & strTextToPrint.PadRight(strTextToPrint.Length + (intLastLength - strTextToPrint.Length), Convert.ToChar(" ")))
    Else
        Console.Write(Convert.ToChar(13) & strTextToPrint)
    End If
    intLastLength = strTextToPrint.Length
End Sub

ここでは、ローカル静的変数のVB機能を使用できますStatic intLastLength As Integer
Mark Hurd

0

私はこれを検索して、私が書いたソリューションが速度を最適化できるかどうかを確認しました。私が欲しかったのは、現在のラインを更新するだけではなく、カウントダウンタイマーでした。これが私が思いついたものです。誰かに役立つかもしれません

            int sleepTime = 5 * 60;    // 5 minutes

            for (int secondsRemaining = sleepTime; secondsRemaining > 0; secondsRemaining --)
            {
                double minutesPrecise = secondsRemaining / 60;
                double minutesRounded = Math.Round(minutesPrecise, 0);
                int seconds = Convert.ToInt32((minutesRounded * 60) - secondsRemaining);
                Console.Write($"\rProcess will resume in {minutesRounded}:{String.Format("{0:D2}", -seconds)} ");
                Thread.Sleep(1000);
            }
            Console.WriteLine("");

0

@ E.Lahu Solutionに触発され、パーセンテージでバーの進行状況が実装されます。

public class ConsoleSpinner
{
    private int _counter;

    public void Turn(Color color, int max, string prefix = "Completed", string symbol = "■",int position = 0)
    {
        Console.SetCursorPosition(0, position);
        Console.Write($"{prefix} {ComputeSpinner(_counter, max, symbol)}", color);
        _counter = _counter == max ? 0 : _counter + 1;
    }

    public string ComputeSpinner(int nmb, int max, string symbol)
    {
        var spinner = new StringBuilder();
        if (nmb == 0)
            return "\r ";

        spinner.Append($"[{nmb}%] [");
        for (var i = 0; i < max; i++)
        {
            spinner.Append(i < nmb ? symbol : ".");
        }

        spinner.Append("]");
        return spinner.ToString();
    }
}


public static void Main(string[] args)
    {
        var progressBar= new ConsoleSpinner();
        for (int i = 0; i < 1000; i++)
        {
            progressBar.Turn(Color.Aqua,100);
            Thread.Sleep(1000);
        }
    }

0

これが、s soshと0xA3の答えに対する私の見解です。スピナーの更新中にコンソールをユーザーメッセージで更新でき、経過時間インジケーターも表示されます。

public class ConsoleSpiner : IDisposable
{
    private static readonly string INDICATOR = "/-\\|";
    private static readonly string MASK = "\r{0} {1:c} {2}";
    int counter;
    Timer timer;
    string message;

    public ConsoleSpiner() {
        counter = 0;
        timer = new Timer(200);
        timer.Elapsed += TimerTick;
    }

    public void Start() {
        timer.Start();
    }

    public void Stop() {
        timer.Stop();
        counter = 0;
    }

    public string Message {
        get { return message; }
        set { message = value; }
    }

    private void TimerTick(object sender, ElapsedEventArgs e) {
        Turn();
    }

    private void Turn() {
        counter++;
        var elapsed = TimeSpan.FromMilliseconds(counter * 200);
        Console.Write(MASK, INDICATOR[counter % 4], elapsed, this.Message);
    }

    public void Dispose() {
        Stop();
        timer.Elapsed -= TimerTick;
        this.timer.Dispose();
    }
}

使用法は次のようなものです:

class Program
{
    static void Main(string[] args)
    {
        using (var spinner = new ConsoleSpiner())
        {
            spinner.Start();
            spinner.Message = "About to do some heavy staff :-)"
            DoWork();
            spinner.Message = "Now processing other staff".
            OtherWork();
            spinner.Stop();
        }
        Console.WriteLine("COMPLETED!!!!!\nPress any key to exit.");

    }
}

-1

このSetCursorPositionメソッドはマルチスレッドシナリオで機能し、他の2つのメソッドは機能しません

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.