ラベル C# の投稿を表示しています。 すべての投稿を表示
ラベル C# の投稿を表示しています。 すべての投稿を表示

TikaOnDotnetでファイル内の文字列を抽出する方法

 以下のSampleのとおり、TextExtractor.Extract()メソッドで、ファイル内の文字列を取得できます。

※Shift-Jisのテキストファイル(.txt)は、UTF-8に変換しないと抽出に失敗するので要注意。

  1. public void TikaExtractorTest() {
  2. var txtExtractor = new TextExtractor();
  3.  
  4. var path = @"C:\Temp\Test.xlsx";
  5. var content = txtExtractor.Extract(path);
  6.  
  7. Debug.WriteLine(content.Text);
  8. }

C#で文字コードを自動判定する方法

 以下のSampleのとおり、ReadJEnc.JP.GetEncoding()メソッドで文字コードを自動判定することができます。

実装例

public void DetectingEncodeTest() {
    var path = @"C:\Temp\Test.txt";

    byte[] bytes = null;
    using (var fs = new FileStream(path, FileMode.Open)) {
        bytes = new byte[fs.Length];
        fs.Read(bytes, 0, bytes.Length);
    }
    string str = null;
    var encode = ReadJEnc.JP.GetEncoding(bytes, bytes.Length, out str);

    Debug.WriteLine(encode.ToString());
}

実行結果

ShiftJIS

FlexLuceneで日付フィルタリングを実現する方法

 ポイントは、以下の2点です。

  • ドキュメントの日付を、同一フィールド名でLongPointとStoredFieldを使って登録する。(17行目付近)
  • 検索時にLongPoint.NewRangeQueryを使って、絞込みを行う点です。(32行目)

実装例

  1. public void LongPointTest() {
  2. var analyzer = new WhitespaceAnalyzer();
  3. var iwc = new IndexWriterConfig(analyzer);
  4.  
  5. iwc.SetOpenMode(IndexWriterConfigOpenMode.CREATE);
  6.  
  7. //テスト用インデックス作成---------------------------------------------
  8. DateTime baseDate = DateTime.Parse("2020/07/16 08:00:00");
  9. var ram = new RAMDirectory();
  10. var writer = new IndexWriter(ram, iwc);
  11. try {
  12. for (int i = 0; i < 10; i++) {
  13. var doc = new Document();
  14. doc.Add(new TextField("text", "hoge foo", FieldStore.YES));
  15. DateTime tmp = baseDate.AddDays(i);
  16. long l = long.Parse(tmp.ToString("yyyyMMddHHmmss"));
  17. //日付文字列をlong値で保持
  18. doc.Add(new LongPoint("date", l));
  19. //long値をストアするには、同じフィールド名でStoredFieldとして指定する必要がある。
  20. doc.Add(new StoredField("date", l));
  21.  
  22. writer.AddDocument(doc);
  23. }
  24. } finally {
  25. writer.Close();
  26. }
  27.  
  28. //検索------------------------------------------------------------
  29. TermQuery tq = new TermQuery(new Term("text", "foo"));
  30. //日付範囲の条件
  31. Query rq = LongPoint.NewRangeQuery("date", 20200717000000, 20200719000000);
  32.  
  33. BooleanQueryBuilder b = new BooleanQueryBuilder();
  34. b.Add(tq, BooleanClauseOccur.MUST); //AND条件
  35. b.Add(rq, BooleanClauseOccur.FILTER); //AND条件(スコアリングに関与しない)
  36. Query q = b.Build();
  37.  
  38. DirectoryReader dr = DirectoryReader.Open(ram);
  39. IndexSearcher searcher = new IndexSearcher(dr);
  40. ScoreDoc[] hits = searcher.Search(q, 100).ScoreDocs;
  41. for (int i = 0; i < hits.Length; i++) {
  42. var doc = searcher.Doc(hits[i].Doc);
  43. Debug.WriteLine(DateTime.ParseExact(doc.Get("date"), "yyyyMMddHHmmss", null));
  44. }
  45. }

検索結果

2020/07/17 8:00:00
2020/07/18 8:00:00

C#でファイルの拡張プロパティを取得/編集する方法

 

  • NuGetでWindowsAPICodePack-Shellをインストール。
  • 以下のコードで取得/編集ができる。
    using Microsoft.WindowsAPICodePack.Shell;
    using Microsoft.WindowsAPICodePack.Shell.PropertySystem;

    public void FilePropertiesTest() {
        var file = ShellFile.FromFilePath(@"C:\Temp\test.jpg");
        //拡張プロパティ取得
        Console.WriteLine(file.Properties.System.Title.Value);
        Console.WriteLine(file.Properties.System.Author.Value);
        Console.WriteLine(file.Properties.System.Comment.Value);

        //拡張プロパティセット
        ShellPropertyWriter propertyWriter =  file.Properties.GetPropertyWriter();
        propertyWriter.WriteProperty(SystemProperties.System.Title, new string[] { "タイトル" });
        propertyWriter.WriteProperty(SystemProperties.System.Author, new string[] { "著者" });
        propertyWriter.WriteProperty(SystemProperties.System.Comment, new string[] { "コメント" });
        propertyWriter.Close();
    }

C#でEveryoneフルコントロール権限を付与

 

Everyoneフルコントロール権限を付与

public class ExchangeUtil : PublicWebApiUtil {
    /// Everyoneフルコントロール権限を付与
    /// 処理対象パス
    public static void AddFullControleRule(string filePath) {
        //EveryOneFullControle
        var rule = new FileSystemAccessRule("Everyone", FileSystemRights.FullControl, 
            InheritanceFlags.None, PropagationFlags.None, AccessControlType.Allow);

        //ファイルセキュリティオブジェクトを取得
        FileSecurity security = File.GetAccessControl(filePath);
        //権限付与
        security.AddAccessRule(rule);
        //変更したファイルセキュリティをファイルに設定
        File.SetAccessControl(filePath, security);
    }
}

C#でファイルがロックされているかどうか判定

 

ファイルがロックされているかどうか判定

  1. public class ExchangeUtil : PublicWebApiUtil {
  2. /// ファイルが開かれてロックされているかどうか
  3. /// true:ロックされている/false:されていない
  4. public static bool IsFileLocked(string path) {
  5. FileStream stream = null;
  6. if (!File.Exists(path)) {
  7. return false;
  8. }
  9. try {
  10. stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
  11. } catch {
  12. return true;
  13. }
  14. finally {
  15. if (stream != null) {
  16. stream.Close();
  17. }
  18. }
  19. return false;
  20. }
  21. }

C#でISO8601形式の継続時間文字列をTimeSpanに変換

 

ISO8601形式の継続時間文字列をTimeSpanに変換

  1. public class ExchangeUtil : PublicWebApiUtil {
  2. ///
  3. /// ISO8601形式の文字列をTimeSpanに変換します。
  4. ///
  5. /// ISO8601形式の文字列
  6. ///
  7. public static TimeSpan Iso8601ToTimeSpan(string iso8601) {
  8. if (iso8601 == null || iso8601 == "") {
  9. //0秒で返す。
  10. return new TimeSpan(0, 0, 0);
  11. }
  12. TimeSpan? ret = null;
  13. int index = iso8601.IndexOf("D");
  14. if (index > 0) {
  15. //日付部("P1D")が存在する場合
  16. string strDays = iso8601.Substring(0, index);
  17. TimeSpan days = new TimeSpan(int.Parse(strDays.Replace("P", "")), 0, 0, 0);
  18. ret = days;
  19.  
  20. //時間部が存在する場合
  21. index = iso8601.IndexOf("DT");
  22. if (index > 0) {
  23. TimeSpan other = XmlConvert.ToTimeSpan("PT" + iso8601.Substring(index + 2));
  24. ret = days.Add(other);
  25. }
  26. } else {
  27. ret = XmlConvert.ToTimeSpan(iso8601.Replace("P", "PT"));
  28. }
  29. return new TimeSpan(ret.Value.Days, ret.Value.Hours, ret.Value.Minutes, 0);
  30. }
  31. }

C#でランダムに色を生成する方法

 

ランダムに色を生成

  1. public class ExchangeUtil : PublicWebApiUtil {
  2. ///
  3. /// ランダムカラー取得
  4. ///
  5. /// シードとなる疑似乱数ジェネレータ
  6. ///
  7. public Color GetRandomColor(Random r) {
  8. int red = r.Next(256);
  9. int green = r.Next(256);
  10. int blue = r.Next(256);
  11.  
  12. return Color.FromArgb(red, green, blue);
  13. }
  14. }

淡い色だけ生成

  1. public class ExchangeUtil : PublicWebApiUtil {
  2.  
  3. /// 淡いランダムカラー取得
  4. ///
  5. /// シードとなる疑似乱数ジェネレータ
  6. ///
  7. public Color GetPaleRandomColor(Random r) {
  8. int red = r.Next(100, 256);
  9. int green = r.Next(100, 256);
  10. int blue = r.Next(100, 256);
  11.  
  12. return Color.FromArgb(red, green, blue);
  13. }
  14. }

C#でメール送信


メール送信サンプル

public class ExchangeUtil : PublicWebApiUtil {
    /// <summary>
    /// メール送信
    /// </summary>
    /// <param name="fromAddress">送信元アドレス</param>
    /// <param name="toAddress">送信先アドレス</param>
    /// <param name="subject">件名</param>
    /// <param name="body">メール本文</param>
    public void SendMail(string fromAddress, string toAddress, string subject, string body) {
        MailMessage msg = new MailMessage();
        SmtpClient sc = new SmtpClient();
        try {
            msg.From = new MailAddress(fromAddress, "表示名");
            msg.To.Add(new MailAddress(toAddress, ""));
            //件名
            msg.Subject = subject;
            //本文
            msg.Body = body;

            //SMTPサーバーなどを設定する
            sc.Host = this.SmtpAddress;
            sc.Port = this.SmtpPort;
            sc.DeliveryMethod = SmtpDeliveryMethod.Network;
            //メッセージを送信する
            sc.Send(msg);
        } finally {
            msg.Dispose();
            sc.Dispose();
        }
    }
}

C#で外為オンラインから為替レートを取得してみた

 為替レートを取得するサンプルです。

「Yahoo Finance」、「Google Finance」が廃止となったので、外為オンラインから取得してみました。


為替レート取得クラス

public class ExchangeUtil : PublicWebApiUtil {
    public enum CurrencyPairCode : int {
        //英ポンド/NZドル
        GBPNZD = 1,
        //カナダドル/NZドル
        CADJPY,
        //英ポンド/AUドル
        GBPAUD,
        //AUドル/円
        AUDJPY,
        //AUドル/NZドル
        AUDNZD,
        //ユーロ/カナダドル
        EURCAD,
        //ユーロ/米ドル
        EURUSD,
        //NZドル/円(jsonString);

        return ds.Tables[0];
    }

    // 指定した貨幣コードの始値を取得
    public Decimal GetOpenPrice(CurrencyPairCode currencyPairCode) {
        Decimal price = -1;

        DataTable exchangeTbl = GetExchangeRateTable();
        DataRow[] rows = (
            from row in exchangeTbl.AsEnumerable()
            let qOpenPrice = row.Field(EnumUtil.GetLabel(RateTableColIdx.Open))
            let qCurrencyPairCode = row.Field(EnumUtil.GetLabel(RateTableColIdx.CurrencyPairCode))
            where qCurrencyPairCode == EnumUtil.GetName(currencyPairCode)
            select row).ToArray();

        price = Decimal.Parse(rows[0][(int)RateTableColIdx.Open].ToString());

        return price;
    }

}

親クラス

public class PublicWebApiUtil {
    public PublicWebApiUtil() {
    }
    /// WebAPI呼び出し
    protected string GetResponseString(string url) {
        HttpWebRequest req = null;
        HttpWebResponse res = null;
        StreamReader sr = null;
        string jsonString = "";
        try {
            //サーバーからデータを受信する
            req = (HttpWebRequest)WebRequest.Create(url);
            res = (HttpWebResponse)req.GetResponse();
            sr = new StreamReader(res.GetResponseStream());
            //すべてのデータを受信する
            jsonString = sr.ReadToEnd();
        } finally {
            sr.Close();
            res.Close();
        }
        return jsonString;
    }
}

使い方

     public void GetPriceRate() {
         ExchangeUtil eu = new ExchangeUtil();
         //始値のレートを取得
         Decimal d = eu.GetOpenPrice(ExchangeUtil.CurrencyPairCode.USDJPY);
     }

C#でMP3, MP4のメタデータを取得/編集する方法

NuGetにtaglibという便利なライブラリがあるので、早速、インストールしてみた。
使い方は、以下のような感じ。
ジャケットイメージも付けれるので、貯まったメディアファイルの整理に役立ちそう。

  1. private void SaveTag() {
  2. var f = TagLib.File.Create(@"C:\Temp\対象.mp3");
  3.  
  4. //タイトル
  5. f.Tag.Title = "タイトル";
  6. //アーティスト
  7. f.Tag.Performers = new string[] { "タイトル名" };
  8. //ジャンル
  9. f.Tag.Genres = new string[] { "洋楽" };
  10. //歌詞
  11. f.Tag.Lyrics = "歌詞";
  12. //コメント
  13. f.Tag.Comment = "コメント";
  14.  
  15. //画像
  16. string imgPath = @"C:\Temp\カバー画像.jpeg";
  17. var ic = new System.Drawing.ImageConverter();
  18. var ba = (byte[])ic.ConvertTo(Image.FromFile(imgPath), typeof(byte[]));
  19. var byteVector = new TagLib.ByteVector(ba);
  20. var pic = new TagLib.Picture(byteVector);
  21. pic.Type = TagLib.PictureType.FrontCover;
  22. pic.Description = "Cover";
  23. pic.MimeType = System.Net.Mime.MediaTypeNames.Image.Jpeg;
  24. f.Tag.Pictures = new TagLib.IPicture[] { pic };
  25.  
  26. f.Save();
  27. }
  28.  

将来構想

貯まったMP3,MP4に対する情報を、Webから収集し、メタデータとして自動付与するツールを作成。
後は、全文検索ツールのPokuda Searchを使って、
見たいファイルにすぐにアクセスといったことを考え中。

これで、ファイル整理の手間も省けるかも。。。

C#でGoogle Maps APIを使う(2点間の距離を取得)

Google Maps APIを使って2点間の距離を取得するメソッドを実装してみました。

  1. public static double GetDistance(string origin, string destination, string apiKey) {
  2. double ret = 0;
  3. WebResponse response = null;
  4. try {
  5. string url = @"https://maps.googleapis.com/maps/api/distancematrix/json?origins=" +
  6. origin + "&destinations=" + destination + "&key=" + apiKey;
  7.  
  8. HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
  9. response = request.GetResponse();
  10. Stream dataStream = response.GetResponseStream();
  11. StreamReader sReader = new StreamReader(dataStream);
  12. string jsonString = sReader.ReadToEnd();
  13. Debug.Write(jsonString);
  14.  
  15. var jo = JObject.Parse(jsonString);
  16. JToken jt = jo.SelectToken("$.rows..elements..distance.value");
  17. ret = double.Parse(StringUtil.NullBlankToZero(jt.ToString()));
  18.  
  19. return ret;
  20. } finally {
  21. response.Close();
  22. }
  23. }

例えば「origin:東京」-「destination:大阪」を指定してリクエストを投げると、以下の形式のJSONが返されるので、
JSONPathを使って、距離の部分を抜き出して戻り値にセットしています。

●例)「東京」-「大阪」間でリクエストを投げた時のJSON取得結果
  1. {
  2. "destination_addresses" : [ "Osaka, Osaka Prefecture, Japan" ],
  3. "origin_addresses" : [ "Tokyo, Japan" ],
  4. "rows" : [
  5. {
  6. "elements" : [
  7. {
  8. "distance" : {
  9. "text" : "513 km",
  10. "value" : 512545
  11. },
  12. "duration" : {
  13. "text" : "6 hours 16 mins",
  14. "value" : 22563
  15. },
  16. "status" : "OK"
  17. }
  18. ]
  19. }
  20. ],
  21. "status" : "OK"
  22. }
  23.  

Simple Injectorを使ってみた


手軽に利用できるDIコンテナ「Simple Injector」を使ってみました。
パッケージ開発のアドオンの受け口などで活用できそうです。

●このデモで利用するクラス一覧
クラス/インタフェース 説明
Program.cs このクラスで以下の処理を行います。
・コンテナ生成
・注入オプジェクト登録
・コンテナの登録内容の検証
・InjectionTargetClassのメソッドを実行
IProxy.cs 注入オブジェクトのインタフェースです。
HogeProxy.cs 注入オブジェクトの具象クラス。
FooProxy.cs 注入オブジェクトの具象クラス。
BarClass.cs 注入オブジェクトの具象クラス。(IProxyは実装していない。)
InjectionTargetClass.cs オブジェクト注入対象のクラスです。
このクラスで注入されたオブジェクトを利用します。
コンストラクタの引数に注目してください。
コンテナに登録されたオブジェクトが渡されます。

●Program.cs
  1. using SimpleInjector;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7.  
  8. namespace SimpleInjectorInConsoleApp {
  9. class Program {
  10.  
  11. private static Container container;
  12.  
  13. static void Main(string[] args) {
  14. container = new Container();
  15.  
  16. //デフォルトのLifestyleを確認
  17. Console.WriteLine("デフォルトのLifestyle : " + container.Options.DefaultLifestyle.ToString());
  18.  
  19. container.Register(Lifestyle.Singleton);
  20. //登録をFooProxyに切替えると、利用側のクラスを変更せずに処理を切り替えれます。
  21. //container.Register(Lifestyle.Singleton);
  22. //コンストラクタにRegister()していないTypeの引数を渡す方法
  23. container.Register(() => new BarClass("bar"), Lifestyle.Singleton);
  24. container.Verify();
  25. // Useコンテナに登録していないが、コンストラクタインジェクションを行って必要な
  26. // オブジェクトを設定してインスタンスを作成してくれる。 (auto-wiring)
  27. var a = container.GetInstance();
  28. a.Write();
  29. //自動でコンソールが閉じるの防ぐ
  30. Console.ReadLine();
  31. }
  32. }
  33. }

●IProxy.cs
  1.  
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7.  
  8. namespace SimpleInjectorInConsoleApp {
  9. ///
  10. /// インタフェース
  11. ///
  12. public interface IProxy {
  13. void Write();
  14. }
  15. }

●HogeProxy.cs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7.  
  8. namespace SimpleInjectorInConsoleApp {
  9. ///
  10. /// 注入クラスHoge
  11. ///
  12. class HogeProxy : IProxy {
  13. public void Write() {
  14. Console.WriteLine("hoge");
  15. }
  16. }
  17. }

●FooProxy.cs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7.  
  8. namespace SimpleInjectorInConsoleApp {
  9. ///
  10. /// 注入クラスFoo
  11. ///
  12. class FooProxy : IProxy {
  13. public void Write() {
  14. Console.WriteLine("foo");
  15. }
  16. }
  17. }

●BarClass.cs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6.  
  7. namespace SimpleInjectorInConsoleApp {
  8. ///
  9. /// 注入クラスBar
  10. /// ※ IProxyを実装していない
  11. ///
  12. public class BarClass {
  13. private string val = "";
  14. public BarClass(string str) {
  15. val = str;
  16. }
  17.  
  18. public void Write() {
  19. Console.Write(val);
  20. }
  21. }
  22. }

●InjectionTargetClass.cs
  1. using SimpleInjector;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7.  
  8. namespace SimpleInjectorInConsoleApp {
  9. ///
  10. /// オブジェクト注入対象クラス
  11. ///
  12. public class InjectionTargetClass {
  13. private readonly IProxy _proxy;
  14. private readonly Container _container;
  15. private readonly BarClass _dummy;
  16.  
  17. public InjectionTargetClass(IProxy proxy, Container container, BarClass dummy) {
  18. _proxy = proxy;
  19. _container = container;
  20. _dummy = dummy;
  21. }
  22.  
  23. public void Write() {
  24. _proxy.Write();
  25. Console.WriteLine("注入コンテナのDefaultLifestyle : " +
  26. _container.Options.DefaultLifestyle);
  27. _dummy.Write();
  28. }
  29. }
  30. }

●実行結果

Microsoft Teamsにエラーログを出力するLog4Net Custom Appenderを作ってみた。


Custom AppenderクラスはAppenderSkeletonを継承し、Append()メソッドをオーバライドして作成。
IncomingWebhooksのURLをlog4net.configから取得できるようにプロパティを作成しておく。

●Custom Appenderクラスは以下のとおり。
 MSTeamsUtil()の実装についてはこちらを参考にして下さい。
  1. public class MSTeamsAppender : AppenderSkeleton {
  2.     //Microsoft TeamsのコネクタURL
        public string IncomingWebhookURL { getset; }  
  3.     // Microsoft Teamsにログ追記
        protected override void Append(LoggingEvent loggingEvent) {
            var mstu = new MSTeamsUtil();
            mstu.PostPlainMessage(IncomingWebhookURL, RenderLoggingEvent(loggingEvent));
        }
    }
●log4net.configの設定方法は以下のとおり
  1. <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <log4net>
        <appender name="MSTeamsAppender" type="FxCommonLib.Log4NetAppender.MSTeamsAppender, FxCommonLib">
          <incomingWebhookURL value="https://outlook.office.com/・・・" />
          <layout type="log4net.Layout.PatternLayout">
            <ConversionPattern value="%username %d [%t] %-5p %c - %m%n" />
          </layout>
          <filter type="log4net.Filter.LevelRangeFilter">
            <levelMin value="WARN" />
            <levelMax value="FATAL" />
          </filter>
        </appender>
        <root>
          <level value="INFO" />
          <appender-ref ref="MSTeamsAppender" />
        </root>
      </log4net>
    </configuration>
こんな感じでログが投稿されます。

Microsoft TeamsにC#でメッセージ投稿してみた


最近、無償提供されたMicrosoft Teamsにプログラムからメッセージ投稿してみたいと思い、実装してみた。
システム監視、センサなどから情報投稿など、いろいろ利用できそう。

まずは、Microsoft TeamsのIncoming Webhook APIのURLを取得。
取得方法の詳細はこちら

○Microsoft Teamsに平文メッセージを投稿するためのユーティリティメソッド
public class MSTeamsUtil {
    // Microsoft Temasに平文メッセージを投稿
    public void PostPlainMessage(string webhookURL, string message) {
        using (var client = new WebClient()) {
            var param = new Dictionary();
            // Textパラメータは必須
            param["Text"] = message;
            var json = JsonConvert.SerializeObject(param);

            client.Headers.Add(HttpRequestHeader.ContentType, "application/json;charset=UTF-8");
            client.Encoding = Encoding.UTF8;
            client.UploadString(webhookURL, json);
        }
    }
}
○利用側のソース
public class MSTeamsUtilTest {
    //取得したImcoming WebhooksのURLをセット
    private string _webhookURL = "https://outlook.office.com/webhook/・・・";

    public void PostPlainMessageTest() {
        var mst = new MSTeamsUtil();
        mst.PostPlainMessage(_webhookURL, "hoge hoge");
    }
}
最初、HttpClientで実装しようとしたが、PostAsAsync()実行時にエラー(ExceptionもCatchできない状態)となった。
エラーの原因は、よく解らないので、追々調査してみることに。

代わりにWebClientに変更して実装してみるとメッセージ投稿に成功した。

なお、HttpClientを利用する時はリクエストの度にインスタンス生成・破棄してはならにとのこと。
TCPコネクションが都度、張られ、パフォーマンスを低下させるとのこと。

WebClientではリクエストの度にインスタンス生成・破棄してもTCPコネクションが使い回されるため問題ない。

【参考】
C# HTTPクライアントまとめ
不適切なインスタンス化のアンチパターン

厳選 Visual Studioの便利なショートカット

  エラー箇所にジャンプ 「Ctrl + Shift + F12」 ブレークポイント 設定/解除 「F9」 有効化/無効化 「Ctrl + F9」 ViEmu特有 「:ls」:バッファナンバーのリストを表示。 「:b2」:バッファ2のファイルを開く。 「:n」:次のバッファのファ...