.NETプログラミング研究 第48号

.NET質問箱

「.NET質問箱」では、「どぼん!のプログラミング掲示板」に書き込まれた.NETプログラミングに関する投稿を基に、さらに考察を加え、Q&A形式にまとめて紹介します。

Windowsディレクトリを取得する

【質問】

Windowsディレクトリのパスを取得するにはどうしたらよいのでしょうか?Environment.GetFolderPathメソッドでは取得できないようですが...。

【回答】

まず考えられるのは、環境変数で定義されている"windir"の値を取得する方法です。環境変数の値を取得するためには、Environment.GetEnvironmentVariableメソッドまたは、Environment.ExpandEnvironmentVariablesメソッドを使用します。

それぞれのメソッドを使ってWindowsディレクトリのパスを取得する例を示します。

  1
  2
  3
  4
  5
  6
  7
  8
  9
Dim windir As String
 
'GetEnvironmentVariableメソッドによりWindowsディレクトリを取得
windir = System.Environment.GetEnvironmentVariable("windir")
Console.WriteLine(windir)
 
'ExpandEnvironmentVariablesメソッドによりWindowsディレクトリを取得
windir = System.Environment.ExpandEnvironmentVariables("%windir%")
Console.WriteLine(windir)
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
string windir;
 
//GetEnvironmentVariableメソッドによりWindowsディレクトリを取得
windir = 
    System.Environment.GetEnvironmentVariable("windir");
Console.WriteLine(windir);
 
//ExpandEnvironmentVariablesメソッドによりWindowsディレクトリを取得
windir = 
    System.Environment.ExpandEnvironmentVariables("%windir%");
Console.WriteLine(windir);

また、Windowsディレクトリはシステムディレクトリの親ディレクトリであると考えれば、Environment.GetFolderPathメソッドを使って次のようにできます。

  1
  2
  3
  4
  5
  6
  7
  8
'システムディレクトリを取得
Dim sysdir As String = _
    System.Environment.GetFolderPath( _
        System.Environment.SpecialFolder.System)
'Windowsディレクトリを取得
Dim windir As String = _
    System.IO.Path.GetDirectoryName(sysdir)
Console.WriteLine(windir)
  1
  2
  3
  4
  5
  6
  7
  8
//システムディレクトリを取得
string sysdir =
    System.Environment.GetFolderPath(
        System.Environment.SpecialFolder.System);
//Windowsディレクトリを取得
string windir =
    System.IO.Path.GetDirectoryName(sysdir);
Console.WriteLine(windir);

また、Win32 APIのGetWindowsDirectory関数を使用する方法もあります。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
<DllImport("kernel32", CharSet:=CharSet.Auto, SetLastError:=True)> _
Private Shared Function GetWindowsDirectory( _
    ByVal buffer As String, _
    ByVal length As Integer) As Integer
End Function
 
'Windowsディレクトリを取得する
Public Shared Function GetWindowsDirectoryPath() As String
    Dim buf As New String(" "c, 260)
    Dim len As Integer = GetWindowsDirectory(buf, 260)
    Return buf.Substring(0, len)
End Function
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
[DllImport("kernel32", CharSet=CharSet.Auto, SetLastError=true)]
private static extern uint GetWindowsDirectory(
    string buffer, uint length);
 
//Windowsディレクトリを取得する
public string GetWindowsDirectoryPath()
{
    string buf = new string(' ', 260);
    uint len = GetWindowsDirectory(buf, 260);
    return buf.Substring(0, (int) len);
}

裏技として、ニュースグループ「microsoft.public.dotnet.languages.vb」では、Environment.GetFolderPathメソッドで直接取得する方法が紹介されています。

この記事によると、Environment.SpecialFolder列挙体のメンバにWindowsディレクトリに相当するものが見つからないため、一見GetFolderPathメソッドではWindowsディレクトリのパスは取得できないように思われますが、次のような方法によりそれが可能になるということです。

  1
  2
  3
  4
  5
  6
Dim windir As String
 
'Windowsディレクトリを取得
windir = System.Environment.GetFolderPath( _
    CType(&H24, System.Environment.SpecialFolder))
Console.WriteLine(windir)
  1
  2
  3
  4
  5
  6
  7
string windir;
 
//Windowsディレクトリを取得
windir = 
    System.Environment.GetFolderPath(
        (System.Environment.SpecialFolder) 0x24);
Console.WriteLine(windir);

しかしこの方法は残念ながら私の環境では(.NET Framework 1.1)成功しませんでした。現在はこの方法は使用できないようです。

○この記事の基になった掲示板のスレッド

2つのフォルダの同期を行う

【質問】

フォルダをコピーする際に、コピー元のファイルがコピー先に存在しないか、コピー先のファイルより新しい場合に限りコピーするようにしたいのですが、どうすればよいのでしょうか?また、2つのフォルダのファイル構成が同じになるようにミラーリング(同期)したいのですが、どうすればできますか?

【回答】

まずフォルダのコピーについては、私のサイトの「フォルダをコピーする」をご覧ください。

ここではこのコードに手を加えることにします。

まず、File.ExistsメソッドとFile.GetLastWriteTimeメソッドを使い、コピー元のファイルがコピー先に存在しないか、またはコピー先のファイルより新しいか調べ、ファイルをコピーするようにします。

さらにミラーリングをするために、コピー先にあってコピー元にないファイルを探して削除するためのメソッド(ここでは"DeleteNotExistFiles")を作成します。

このようにして作成された新たなCopyDirectoryメソッドは次のようなものです。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
''' <summary>
''' ディレクトリをコピーする
''' </summary>
''' <param name="sourceDirName">コピーするディレクトリ</param>
''' <param name="destDirName">コピー先のディレクトリ</param>
''' <param name="newerOnly">新しいファイルのみコピーする</param>
''' <param name="sync">sourceDirNameにないファイルを削除する</param>
Public Shared Sub CopyDirectory(ByVal sourceDirName As String, _
        ByVal destDirName As String, ByVal newerOnly As Boolean, _
        ByVal sync As Boolean)
    'コピー先のディレクトリがないときは作る
    If Not Directory.Exists(destDirName) Then
        Directory.CreateDirectory(destDirName)
        '属性もコピー
        File.SetAttributes(destDirName, File.GetAttributes(sourceDirName))
    End If
 
    'コピー先のディレクトリ名の末尾に"\"をつける
    If destDirName.Chars((destDirName.Length - 1)) <> _
            Path.DirectorySeparatorChar Then
        destDirName = destDirName + Path.DirectorySeparatorChar
    End If
 
    'コピー元のディレクトリにあるファイルをコピー
    Dim files As String() = Directory.GetFiles(sourceDirName)
    Dim f As String
    For Each f In files
        Dim destFileName As String = destDirName + Path.GetFileName(f)
        'コピー先にファイルが存在し、
        'コピー元より更新日時が古い時はコピーする
        If Not newerOnly Or _
            Not File.Exists(destFileName) Or _
            File.GetLastWriteTime(destFileName) < _
                File.GetLastWriteTime(f) Then
            File.Copy(f, destFileName, True)
        End If
    Next f
 
    'コピー先にあってコピー元にないファイルを削除
    If sync Then
        DeleteNotExistFiles(sourceDirName, destDirName)
    End If
 
    'コピー元のディレクトリにあるディレクトリについて、
    '再帰的に呼び出す
    Dim dirs As String() = Directory.GetDirectories(sourceDirName)
    Dim dir As String
    For Each dir In dirs
        CopyDirectory(dir, destDirName + Path.GetFileName(dir), _
            newerOnly, sync)
    Next dir
End Sub
 
''' <summary>
''' destDirNameにありsourceDirNameにないファイルを削除する
''' </summary>
''' <param name="sourceDirName">比較先のフォルダ</param>
''' <param name="destDirName">比較もとのフォルダ</param>
Private Shared Sub DeleteNotExistFiles( _
        ByVal sourceDirName As String, ByVal destDirName As String)
    'destDirNameにありsourceDirNameにないファイルを削除する
    Dim files As String() = Directory.GetFiles(destDirName)
    Dim f As String
    For Each f In files
        If Not File.Exists(Path.Combine( _
                sourceDirName, Path.GetFileName(f))) Then
            File.Delete(f)
        End If
    Next f
 
    'destDirNameにありsourceDirNameにないフォルダを削除する
    Dim folders As String() = Directory.GetDirectories(destDirName)
    Dim folder As String
    For Each folder In folders
        If Not Directory.Exists(Path.Combine( _
                sourceDirName, Path.GetFileName(folder))) Then
            Directory.Delete(folder, True)
        End If
    Next folder
End Sub
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
/// <summary>
/// ディレクトリをコピーする
/// </summary>
/// <param name="sourceDirName">コピーするディレクトリ</param>
/// <param name="destDirName">コピー先のディレクトリ</param>
/// <param name="newerOnly">新しいファイルのみコピーする</param>
/// <param name="sync">sourceDirNameにないファイルを削除する</param>
public static void CopyDirectory(
    string sourceDirName, string destDirName,
    bool newerOnly, bool sync)
{
    //コピー先のディレクトリがないときは作る
    if (!Directory.Exists(destDirName))
    {
        Directory.CreateDirectory(destDirName);
        //属性もコピー
        File.SetAttributes(destDirName, 
            File.GetAttributes(sourceDirName));
    }
 
    //コピー先のディレクトリ名の末尾に"\"をつける
    if (destDirName[destDirName.Length - 1] !=
        Path.DirectorySeparatorChar)
        destDirName = destDirName + Path.DirectorySeparatorChar;
 
    //コピー元のディレクトリにあるファイルをコピー
    string[] files = Directory.GetFiles(sourceDirName);
    foreach (string file in files)
    {
        string destFileName = destDirName + Path.GetFileName(file);
        //コピー先にファイルが存在し、
        //コピー元より更新日時が古い時はコピーする
        if (!newerOnly ||
            !File.Exists(destFileName) ||
            File.GetLastWriteTime(destFileName) <
            File.GetLastWriteTime(file))
        {
            File.Copy(file, destFileName, true);
        }
    }
    
    //コピー先にあってコピー元にないファイルを削除
    if (sync)
        DeleteNotExistFiles(
            sourceDirName, destDirName);
 
    //コピー元のディレクトリにあるディレクトリについて、
    //再帰的に呼び出す
    string[] dirs = Directory.GetDirectories(sourceDirName);
    foreach (string dir in dirs)
        CopyDirectory(dir, destDirName + Path.GetFileName(dir),
            newerOnly, sync);
}
/// <summary>
/// destDirNameにありsourceDirNameにないファイルを削除する
/// </summary>
/// <param name="sourceDirName">比較先のフォルダ</param>
/// <param name="destDirName">比較もとのフォルダ</param>
private static void DeleteNotExistFiles(
    string sourceDirName, string destDirName)
{
    //destDirNameにありsourceDirNameにないファイルを削除する
    string[] files = Directory.GetFiles(destDirName);
    foreach (string file in files)
    {
        if (!File.Exists(Path.Combine(
            sourceDirName, Path.GetFileName(file))))
            File.Delete(file);
    }
 
    //destDirNameにありsourceDirNameにないフォルダを削除する
    string[] folders = Directory.GetDirectories(destDirName);
    foreach (string folder in folders)
    {
        if (!Directory.Exists(Path.Combine(
            sourceDirName, Path.GetFileName(folder))))
            Directory.Delete(folder, true);
    }
}

次に使用法を示します。フォルダ"C:\test1"内の更新されたファイルをフォルダ"C:\test2"にコピーし、ミラーリングを行うには、次のようにします。

  1
CopyDirectory("C:\test1", "C:\test2", True, True)
  1
CopyDirectory("C:\\test1", "C:\\test2", true, true);

○この記事の基になった掲示板のスレッド

Visual Basic .NET, Visual C# Standard Editionでクラスライブラリを作成する

【質問】

Visual Basic .NET Standard Edition(または、Visual C# Standard Edition)を使っているのですが、クラスライブラリを作成できません。Standard Editionではクラスライブラリの作成ができないのでしょうか?

【回答】

(注:私はStandard Editionを使用したことがありませんので、ここで紹介する事柄は私が直接確認を取ったわけではないことをご了承ください。)

Visual Basic .NET Standard Edition(または、Visual C# Standard Edition)では「新しいプロジェクト」ダイアログに「クラスライブラリ」が表示されないため、Standard Editionではクラスライブラリを作成することができないように思われます。しかし掲示板でよねKENさんに紹介していただいたように、プロジェクトファイルを書き換えればStandard Editionでもクラスライブラリを作成できます。

具体的な方法は、まずプロジェクトのプロジェクトファイル("*.vbproj"または"*.csproj")をテキストエディタで開き、

OutputType = "???"

("???"は"WinExe"や"Exe"など)となっているところを

OutputType = "Library"

と書き換えるだけです。

また、この書き換えを行うためのマクロが「Planet Source Code」で公開されています。

このような書き換えが製品のライセンス等に違反しないかについて私は断言することはできませんが、問題があるという話を聞いたことがありませんので、大丈夫ではないかと思われます。

○この記事の基になった掲示板のスレッド

印刷プレビューダイアログの表示位置や表示倍率を指定する

【質問】

PrintPreviewDialogクラスにより印刷プレビューダイアログを表示させるときに、ダイアログの表示位置や表示倍率を指定することはできるでしょうか?

【回答】

PrintPreviewDialogクラスはFormクラスから派生していますので、基本的にはFormクラスと同様の方法で表示位置を指定することができます。フォームの位置を指定する方法に関して詳しくは、「フォームの位置と大きさを変更する」をご覧ください。

また表示倍率は、PrintPreviewDialogで使われているPrintPreviewControlオブジェクトを取得し、そのZoomプロパティを設定することにより変更が可能です。PrintPreviewControlオブジェクトはPrintPreviewDialog.PrintPreviewControlプロパティで取得できます(このプロパティはヘルプでは「このメンバは、.NET Framework インフラストラクチャのサポートを目的としています。独自に作成したコード内で直接使用することはできません。」と書かれています)。

表示位置(及びサイズ)と表示倍率を指定して印刷プレビューダイアログを表示する例を示します。ここでは、PrintDocument.PrintPageイベントハンドラ"pd_PrintPage"は省略しています。省略しないコードに関しては、「印刷プレビューを表示する」を参考にしてください。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
'PrintDocumentオブジェクトの作成
Dim pd As New System.Drawing.Printing.PrintDocument
'PrintPageイベントハンドラの追加
AddHandler pd.PrintPage, AddressOf pd_PrintPage
 
'PrintPreviewDialogオブジェクトの作成
Dim ppd As New PrintPreviewDialog
 
'はじめの表示位置を指定する
ppd.StartPosition = FormStartPosition.Manual
ppd.SetBounds(0, 0, 400, 400)
'表示倍率を2倍にする
ppd.PrintPreviewControl.Zoom = 2
 
'プレビューするPrintDocumentを設定
ppd.Document = pd
'印刷プレビューダイアログを表示する
ppd.ShowDialog()
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
//PrintDocumentオブジェクトの作成
System.Drawing.Printing.PrintDocument pd =
    new System.Drawing.Printing.PrintDocument();
//PrintPageイベントハンドラの追加
pd.PrintPage +=
    new System.Drawing.Printing.PrintPageEventHandler(pd_PrintPage);
 
//PrintPreviewDialogオブジェクトの作成
PrintPreviewDialog ppd = new PrintPreviewDialog();
 
//はじめの表示位置を指定する
ppd.StartPosition = FormStartPosition.Manual;
ppd.SetBounds(0, 0, 400, 400);
//表示倍率を2倍にする
ppd.PrintPreviewControl.Zoom = 2;
 
//プレビューするPrintDocumentを設定
ppd.Document = pd;
//印刷プレビューダイアログを表示する
ppd.ShowDialog();

○この記事の基になった掲示板のスレッド


ページ情報
  • 作成日 : 2010-03-16 (火) 23:13:01
  • 作成者 : DOBON!
  • 最終編集日 : 2010-03-16 (火) 23:13:01
  • 最終編集者 : DOBON!
[ トップ ]   [ 新規 | 子ページ作成 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]