Visual Studio C#: часто задаваемые вопросы (FAQ) Печать
Добавил(а) microsin   

В этой статье опубликованы ответы на часто появляющиеся вопросы при программировании в среде C# от Microsoft.

System.Console.WriteLine("Hello, World");

См. также: преобразование даты, времени в строку Q014, преобразование чисел в строку Q015, преобразование строк в числа Q022.

По новым правилам не нужно указывать ключевое слово void в параметрах для процедуры, если у неё отсутствуют параметры.

// CS1536.cs
class a
{ public static int x( void )
// эта строка вызовет ошибку CS1536 // Чтобы устранить ошибку, попробуйте определить функцию так: // public static int x() { return 0; }   public static void Main() { }
} 

String strVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); 

Такая ошибка возникала при двойном клике на файле солюшена *.sln. Если просто запустить Visual Studio 2010 ярлыком из меню "Пуск", то Visual Studio 2010 запускается без ошибки, и позволяет открыть тот же файл *.sln.

Нужно в свойствах ListBox поменять свойство Anchor (привязка к границам) на Top, Bottom, Left, Right. Для того, чтобы отделить некоторую часть формы от других элементов пользовательского интерфейса, применяйте элемент Panel, на котором размещайте другие элементы интерфейса.

В среде Visual Studio C# удобно делать пакет установки, не нужно разбираться с инсталляторами программы (внешние дополнительные инсталляторы теперь не нужны), которую Вы пишете. Для этого в меню достаточно выбрать в меню Построение (Build) -> Опубликовать (Release). Это так называемая технология ClickOnce [8]. В папке Release сразу появится подпапка app.publish, в которой будет лежать готовый инсталлятор setup.exe, содержащий все необходимые для установки программы компоненты.

Также см. вопрос Q073.

Проблема обычно возникает, когда необходимо получить доступ на запись к одному и тому же ресурсу асинхронно из нескольких потоков. Например, Вы имеете на форме ListBox, в которые задумали выводить сообщения из обработчика события, который обрабатывается в другом потоке. У меня такое произошло, когда понадобилось отображение данных в ListBox, принятых в обработчике события поступления данных по USB.

Почему такая проблема возникает: ListBox создается в основном потоке (thread), запускающей GUI окна программы. Другие потоки могут быть созданы для поддержки обмена данными с внешними устройствами (сеть Ethernet, COM-порт, USB и т. п.). Эти другие потоки могут обрабатывать события приема данных асинхронно с выполнением основного потока. Поэтому если эти потоки попытаются что-то записать в ListBox, то это может привести к конфликту с записью в ListBox данных из основного потока GUI интерфейса программы.

Как проблему можно решить: для ListBox создаются подпрограммы (методы), через которые выводятся данные в ListBox. Данные выводятся в ListBox только через эти подпрограммы, больше никак. Эти подпрограммы делаются защищенными (thread-safe) для вызова из любых потоков через delegate, InvokeRequired, Invoke.

См. также:
- справку в MSDN: Control.InvokeRequired - свойство, Control.Invoke - метод (Delegate), How to: Make Thread-Safe Calls to Windows Forms Controls
- Making Windows Forms thread safe
- Ссылки [9, 10]
- Q101

В C# уже нельзя, как раньше, беспечно задавать константы операторами #define, и добавлять их h-файлы (как было принято в языках C и C++). Для C# нельзя использовать заголовки (файлы с расширением *.h или *.hpp), и директива #define не может использоваться для задания констант.

Для использования #define теперь такие правила:

1. Добавлять определения констант #define можно только в начале файла, иначе получите ошибку CS1032.
2. Директива define может использоваться только для задания ключей компиляции, но не для задания констант, иначе получите ошибку CS1025: Требуется однострочный комментарий или признак конца строки.

[Правила определения констант]

Константы в C# задаются как обычные переменные, только с помощью ключевого слова const:

const byte USBCAN_SUCCESSFUL = 0x00;

Однако если Вы попытаетесь сделать это вне пространства имен класса, то получите ошибку CS0116: Пространство имен не может непосредственно содержать такие члены, как поля или методы. Константы нужно определять не только внутри скобок одной из секций namespace, но и в каком-то определенном классе. Например так (определение константы USBCAN_SUCCESSFUL):

namespace MyApp
{
   public partial class Form1 : Form
   {
      USBcanServer CANsrv = new USBcanServer();
      byte bRet = 0; //код возврата вызовов методов USBcanServer
 
      const byte USBCAN_SUCCESSFUL = 0x00;
 
      public Form1()
      {
         InitializeComponent();
      }
 
      private void Form1_Load(object sender, EventArgs e)
      {
         applog.write("[START]");
         bRet = CANsrv.InitHardware();
         if (USBCAN_SUCCESSFUL != bRet)
            applog.write("InitHardware error");
      }
   }
}

Вместо #define удобно также использовать перечисления (enum). Вот пример задания масок для бит сигналов:

      public enum canreq
      {
         //биты масок сигналов
         ALSN25   = (1 << 8),
         ALSN50   = (1 << 9),
         ALSN75   = (1 << 10),
         ALSEN    = (1 << 11),
         KRL475   = (1 << 12),
         KRL525   = (1 << 13),
         KRL575   = (1 << 14),
         KRL625   = (1 << 15),
         KRL675   = (1 << 16),
         KRL725   = (1 << 17),
         KRL775   = (1 << 18),
         KRL825   = (1 << 19),
         KRL875   = (1 << 20),
         KRL925   = (1 << 21),
         TRC425   = (1 << 22),
         TRC475   = (1 << 23),
         TRC575   = (1 << 60),
         TRC725   = (1 << 61),
         TRC775   = (1 << 62)
      }

[Как определять глобальные константы]

Чтобы определить константы, которые видны в любом классе пространства имен приложения, нужно создать отдельный класс. Этот класс с константами обязательно должен быть размещен после кода класса формы, иначе конструктор формы не запустится - несмотря на то, что приложение компилируется без ошибок. Если попытаться запустить такое скомпилированное приложение, то возникнет ошибка MissingManifestResourceException (см. вопрос Q019).

Пример правильного определения глобальных констант в файле Form1.cs с помощью глобального статического класса CNST:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
 
namespace MyApp
{
   public partial class Form1 : Form
   {
      public Form1()
      {
         InitializeComponent();
      }
 
      private void Form1_Load(object sender, EventArgs e)
      {
         ...
      }
      ...
   }
 
   static class CNST
   {
      //Флаги ошибки:
      public const UInt32 ERR_NOTFOUND_H411           = (1 << 1);
      public const UInt32 CALIBR_ERR_MODE             = (1 << 2);
      public const UInt32 CALIBR_ERR_FORMAT_PARAM     = (1 << 3);
      public const UInt32 CALIBR_ERR_FREQ_INPUT       = (1 << 4);
      public const UInt32 CALIBR_ERR_ALLREADY_STARTED = (1 << 5);
      public const UInt32 CALIBR_ERR_START_FAIL       = (1 << 6);
      public const UInt32 CALIBR_ERR_FREQ_DETECTED    = (1 << 7);
      public const UInt32 CALIBR_ERR_SPECTR           = (1 << 8);
      public const UInt32 CALIBR_ERR_VOLTAGE          = (1 << 9);
      public const UInt32 CALIBR_ERR_BREAK_BY_USER    = (1 << 10);
      public const UInt32 CALIBR_ERR_UNKNOWN          = (1 << 30);
   }
}

Теперь можно пользоваться константами в любом месте пространства имен namespace MyApp через префикс класса CNST:

UInt32 myvar = CNST.CALIBR_ERR_FREQ_INPUT | CNST.CALIBR_ERR_SPECTR;

На примере добавления события приема сообщения CAN (класс-обертка USBcanServer над функциями библиотеки UcanDotNET.dll для модулей USB CAN компании SYSTEC):

CANsrv.CanMsgReceivedEvent += new USBcanServer.CanMsgReceivedEventEventHandler(CANsrv_CanMsgReceivedEvent);

И еще добавьте обработчик события приема сообщения CANsrv_CanMsgReceivedEvent:

private void CANsrv_CanMsgReceivedEvent(byte bChannel_p)
{ applog.write("Data arrived");
}

Для упрощенного добавления обработчика выполните следующие шаги:

- в коде Form1_Load напечатайте CANsrv. (в конце поставьте точку);
- нажмите Ctrl+Space, из появившегося списка методов и событий выберите CanMsgReceivedEvent;
- допечатайте пробел, + и =. Появится подсказка в виде текста:
"new USBcanServer.CanMsgReceivedEventEventHandler(CANsrv_CanMsgReceivedEvent); (Нажмите клавишу TAB для вставки)"
- нажмите клавишу TAB, и в коде автоматически будет создана строка:

CANsrv.CanMsgReceivedEvent += new USBcanServer.CanMsgReceivedEventEventHandler(CANsrv_CanMsgReceivedEvent);

После этого останется вручную создать процедуру CANsrv_CanMsgReceivedEvent. Полный код см. в [2].

Пример конвертации структуры tCanMsgStruct с языка C++:

typedef struct _tCanMsgStruct
{ DWORD m_dwID; // CAN Identifier BYTE m_bFF; // CAN Frame format (BIT7=1: 29BitID / BIT6=1: RTR-Frame / BIT5=1: Tx echo) BYTE m_bDLC; // CAN Data Length Code BYTE m_bData[8]; // CAN Data DWORD m_dwTime; // Time in ms
} tCanMsgStruct;

На C# будет так (только для компиляции в небезопасном режиме, с опцией /unsafe):

unsafe public struct tCanMsgStruct
{ UInt32 m_dwID; // CAN Identifier byte m_bFF; // CAN Frame format (BIT7=1: 29BitID / BIT6=1: RTR-Frame / BIT5=1: Tx echo) byte m_bDLC; // CAN Data Length Code fixed byte m_bData[8]; // CAN Data byte m_dwTime; // Time in ms
}

Если код компилируется в безопасном режиме, то на C# структура tCanMsgStruct должна быть определена так:

using System.Runtime.InteropServices;
...
[StructLayout(LayoutKind.Explicit, Pack=1, Size=15)]
public struct tCanMsgStruct
{ [FieldOffset(0)] public UInt32 m_dwID; [FieldOffset(4)] public byte m_bFF; [FieldOffset(5)] public byte m_bDLC; [FieldOffset(6)] public byte m_bData; [FieldOffset(14)] public byte m_dwTime;
}

Экземпляр структуры определяется так:

tCanMsgStruct msg = new tCanMsgStruct;

Если не нужно точное соответствие положения полей в памяти, то структуры можно определять обычным способом, через ключевое слово struct. Такие структуры на C# почти что классы, и могут иметь даже собственные конструкторы, в которых можно инициализировать массивы, входящие в структуру как поле. Как и классы, в составе полей такие структуры могут иметь классы, атрибуты и методы. Пример структуры с конструктором:

public struct tFirmware
{ public int minadr; public int maxadr; public byte[] data; public tFirmware (int dummy) { minadr = 65535; maxadr = 0; data = new byte[65536]; //Заполнение массива данных файла байтами FF. Это соответствует чистому // состоянию памяти FLASH микроконтроллера AVR. for (int idx = 0; idx < data.Length; idx++) data[idx] = 0xFF; }
} 
..
tFirmware firmware = new tFirmware();
//заполнение массива данными прошивки
for (int idx = 0; idx < bytes_in_line; idx++)
{ fw.data[hexadr + idx] = GetHexLineByte(s, idx); //модификация минимального и максимального адреса прошивки if ((hexadr + idx) < fw.minadr) fw.minadr = hexadr + idx; if ((hexadr + idx) > fw.maxadr) fw.maxadr = hexadr + idx;
}

Пример определения экземпляров массивов:

int[] myarray2;
int[] myArray3 = new int[2] {1,2} UcanDotNET.USBcanServer.tCanMsgStruct[] msg = new UcanDotNET.USBcanServer.tCanMsgStruct[10];

Если компилируется небезопасный код (применяется опция /unsafe), то можно определять массивы так:

unsafe fixed byte m_bData[8]; 

[Многомерные массивы]

Определение массива 2x8 байт:

  public byte [,] buf = new byte [2, 8];

UInt32 - беззнаковое целое, 32 разряда, заменяет DWORD. Удобно также использовать определения других беззнаковых целых byte, UInt16, UInt64 (соответственно 8, 16, 64 бита).

DateTime currtime = DateTime.Now;
string tmptxt = String.Format("{0:yyMMdd hh:mm:ss}", currtime);

Форматированный вывод в строку (то, что на C++ делает printf с параметрами форматирования %) в C# можно делать через метод Format, который есть у класса string. Тип форматирования зависит не только от строки формата, но и от типа выводимых параметров.

Строка формата задается теперь не символом %, а фигурными скобками. Первое число, которое идет после открывающей фигурной скобки, задают номер выводимого в строку параметра. Если после этого числа стоит двоеточие, то за ним идут символы до закрывающей фигурной скобки, которые определяют формат вывода параметра. Для того, чтобы понять, как пользоваться Format, проще рассмотреть несколько простых примеров.

[Целые десятичные числа]

int  a = 1;
byte b = 2;
string tmptxt = String.Format("a={0} b={1} ", a, b);

[Целые числа в HEX-формат]

byte bval = 0xAA;
UInt16 u16 = 0x12AB;
 
//Если в формате применить маленькую букву x, то буквенные
// шестнадцатеричные цифры будут выводиться маленькими буквами:
string tmptxt = String.Format("bval=0x{0:x2}", bval);
 
//Если в формате применить большую букву X, то буквенные
// шестнадцатеричные цифры будут выводиться БОЛЬШИМИ буквами:
string tmptxt = String.Format("bval=0x{0:X2}", bval);
 
//Ещё примеры:
strvalue = string.Format("{0:x4}", u16);           // -> "12ab"
strvalue = string.Format("{0:X4}", u16);           // -> "12AB"
strvalue = string.Format("0x{0:X5}", u16);         // -> "0x012AB"
strvalue = string.Format("0x{0:X2} 0x{1:X2}",
                          (UInt16)(u16>>2), u16);  // -> "0x12 0xAB"

[Числа с плавающей точкой]

float fvalue = 123.456;
 
// Вывод числа float с нужным количеством знаков после запятой:
strvalue = string.Format("F0", fvalue);   //123.456 -> "123"
strvalue = string.Format("F1", fvalue);   //123.456 -> "123,5"
strvalue = string.Format("F3", fvalue);   //123.456 -> "123,456"

[Вывод строки]

string strvalue = "Hello";
 
// Вывод строки:
strvalue = string.Format("{0}, World!", strvalue); // -> "Hello, World!"

См. также: преобразование даты, времени в строку Q014, преобразование строк в числа Q022.

По умолчанию Windows на русском языке число с плавающей точкой выводит в формат с использованием запятой:

float fvalue = 123.456;
strvalue = string.Format("F1", fvalue);   //123.456 -> "123,5"

Но как быть, если вместо запятой нужно выводить точку?

using System.Globalization;
 
float fvalue = 123.456;
 
CultureInfo ci = new CultureInfo("en-us");
// Эквивалент sprintf(strval, "%.2f", fvalue):
string strval = fvalue.ToString("F2", ci));  // strval = "123.45"

Полностью аналогичного функционала static добиться нельзя. На C# невозможно создать внутри метода класса статическую переменную, чтобы она сохраняла свое значение между вызовами метода. Чтобы достичь этой цели, нужно объявить обычную глобальную переменную класса, и она будет видна не только этому методу класса, но и всем методам класса, и будет сохранять свое значение между вызовами метода.

На языке C# значение ключевого слова static поменялось. Это слово можно использовать только для глобальных переменных класса, и оно будет означать следующее - переменная класса, которая объявлена с ключевым словом static, будет иметь общее значение между всеми экземплярами этого класса. Т. е. она будет "глобальной" для всех экземпляров этого класса.

Подробнее про значение ключевого слова static на C# см. статью [7].

Меню Проект -> Добавить форму Windows... -> Форма Windows Forms -> OK. По умолчанию будет добавлена форма Form2.

[Отображение формы при запуске программы]

В модуле Program.cs добавьте код для отображения второй формы:

...
static void Main()
{
   Application.EnableVisualStyles();
   Application.SetCompatibleTextRenderingDefault(false);
   Form2 frm = new Form2(); 
   frm.Show();
   Application.Run(new Form1());
}
...

После запуска программы отобразятся сразу две формы, Form1 и Form2. Подробности см. в [4].

[Запуск формы действием пользователя]

Предположим, что нужно отобразить вторую форму только для какой-то цели, например для отображения лога. В этом случае нужно создать объект формы и вызвать его метод Show:

private void открытьЛогToolStripMenuItem_Click(object sender, EventArgs e)
{
   Form2 frm = new Form2();
   frm.Show();
}

См. также Q100.

MissingManifestResourceException

Проблема возникла после попытки добавить глобальный класс констант в пространство имен приложения (в данном примере calibr) перед классом определения формы. Так неправильно, при запуске приложения будет происходить ошибка MissingManifestResourceException, и конструктор формы не будет работать:

namespace calibr
{
   static class Constants
   {
      //Флаги ошибки:
      public const UInt32 ERR_NOTFOUND         = (1 << 1);
      public const UInt32 ERR_MODE             = (1 << 2);
      public const UInt32 ERR_FORMAT_PARAM     = (1 << 3);
      public const UInt32 ERR_ALLREADY_STARTED = (1 << 5);
      public const UInt32 ERR_START_FAIL       = (1 << 6);
      public const UInt32 ERR_BREAK_BY_USER    = (1 << 10);
      public const UInt32 ERR_UNKNOWN          = (1 << 30);
   }
 
   public partial class Form1 : Form
   {
      public Form1()
      {
         InitializeComponent();
      }
 
      private void Form1_Load(object sender, EventArgs e)
      {
         ...
      }
      ...
   }
}

Решение проблемы: нужно перенести класс констант за определение класса формы. Вот так правильно:

namespace calibr
{
   public partial class Form1 : Form
   {
      public Form1()
      {
         InitializeComponent();
      }
 
      private void Form1_Load(object sender, EventArgs e)
      {
         ...
      }
      ...
   }
 
   static class Constants
   {
      //Флаги ошибки:
      public const UInt32 ERR_NOTFOUND         = (1 << 1);
      public const UInt32 ERR_MODE             = (1 << 2);
      public const UInt32 ERR_FORMAT_PARAM     = (1 << 3);
      public const UInt32 ERR_ALLREADY_STARTED = (1 << 5);
      public const UInt32 ERR_START_FAIL       = (1 << 6);
      public const UInt32 ERR_BREAK_BY_USER    = (1 << 10);
      public const UInt32 ERR_UNKNOWN          = (1 << 30);
   }
}

На языке C# переменное количество параметров передается функции в виде безразмерного массива. На примере класса log - в него добавлен метод write, которому можно передавать в виде параметров любое количество строк:

namespace MyApp
{ class log { public void write(params string[] args) { DateTime currtime = DateTime.Now; string tmptxt;   tmptxt = String.Format("{0:yyMMdd hh:mm:ss} ", currtime); for (int i = 0; i < args.Length; i++) { tmptxt = tmptxt + args[i]; }   try { using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"log.txt", true)) { file.WriteLine(tmptxt); file.Close(); } } catch { } } }
}

Пример использования функции с переменным числом параметров (запись в лог):

int iVal = 1476;
float fPi = 3.14;
byte bVal = 0xAA;   ...
log applog = new log();   ...
applog.write("Hello, World!"); applog.write("iVal=", iVal.ToString()); applog.write("fPi=", iVal.ToString(), " bVal=", bVal.ToString()); ...

Пример:

...
private void Form1_Load(object sender, EventArgs e)
{ this.Location.X = 1; //ошибка CS1612 ...

Ответ: изменять свойство Location нужно через вспомогательную переменную следующим образом:

   ...
   Point tmpLocation = this.Location;
   tmpLocation.X = 1;
   tmpLocation.Y = 1;
   this.Location = tmpLocation;
   ...

Похожим образом нужно менять и размер окна формы, так как прямым присвоением Form.Size.Width и Form.Size.Height этого сделать нельзя (появится ошибка CS1612). Менять размер формы нужно так:

this.Size = new System.Drawing.Size(newWidth, newHeight);

См. методы класса Convert - ToBoolean, ToByte, ToChar, ToDecimal, ToDouble, ToInt16, ToInt32, ToInt64, ToUInt16, ToUInt32, ToUInt64 и другие.

[Простой случай преобразования в целое число со знаком]

int iVal = Convert.ToInt32("1234");

[Преобразование строки символов в HEX-формате]

string tmptxt = "0x89AB";
UInt16 val16 = (UInt16)Convert.ToInt16(tmptxt, 16);

См. также:

• Преобразование даты, времени в строку Q014.
• Преобразование чисел в строку Q015.

На примере сохранения положения и размеров окна при выходе из программы и восстановления параметров окна при запуске программы:

private void Form1_Load(object sender, EventArgs e){
   //чтение координат и размеров окна
   Microsoft.Win32.RegistryKey key;
   key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey("MyApp-subkey");
   if (null != key.GetValue("Form1-X"))
      {
         Point tmpLocation = this.Location;
         tmpLocation.X = Convert.ToInt32(key.GetValue("Form1-X").ToString());
         tmpLocation.Y = Convert.ToInt32(key.GetValue("Form1-Y").ToString());
         this.Location = tmpLocation;
         this.Size = new System.Drawing.Size(Convert.ToInt32(key.GetValue("Form1-Width").ToString()),
                                             Convert.ToInt32(key.GetValue("Form1-Height").ToString()));
      }
      key.Close();
      ...
   private void Form1_FormClosed (object sender, FormClosedEventArgs e)
   {
      //запись координат и размеров окна
      Microsoft.Win32.RegistryKey key;
      key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey("MyApp-subkey");
      key.SetValue("Form1-X", Location.X);
      key.SetValue("Form1-Y", Location.Y);
      key.SetValue("Form1-Width", Size.Width);
      key.SetValue("Form1-Height", Size.Height);
      key.Close();
      ...

По умолчанию, когда Вы создаете ComboBox (путем перетаскивания его на форму с Панели элементов), свойство DropDownStyle установлено в значение DropDown. Совершенно неочевидно, что DropDown соответствует разрешенному редактированию текущего элемента. Чтобы запретить редактирование, измените свойство DropDownStyle на DropDownList.

Чтобы при запуске программы в ComboBox отображалось нужное значение из списка Items, программно поменяйте свойство SelectedIndex.

Подробности по ComboBox см. в [3].

Очистка структуры делается с помощью ключевого слова default (тут надо указать тип очищаемой структуры). Пример:

      [StructLayout(LayoutKind.Explicit, Pack = 1, Size = 8)]
      public struct TAlsenStr
      {
         [FieldOffset(0)]
         public UInt16 CentralFrequency;  // Центральная частота, десятые доли герца
         [FieldOffset(2)]
         public UInt32 ValueRMS;          // СКЗ сигнала, сотые доли мВ
         [FieldOffset(6)]
         public byte CodeChannel1;        // Номер кодовой комбинации первого подканала
         [FieldOffset(7)]
         public byte CodeChannel2;        // Номер кодовой комбинации второго подканала
      }
 
      TAlsenStr ALSEN;
      //работа со структурой АЛСЕН, присвоение значений полям:
      ALSEN.CentralFrequency = 1745;
      ALSEN.ValueRMS = 3000;
      ...
      //очистка структуры, всем полям будут присвоены нули:
      ALSEN = default(TAlsenStr);

Несмотря на то, что ошибка расшифрована в MSDN, там не приводится метод её решения, и смысл ошибки на первый взгляд может быть не очевиден. Возможная причина ошибки - при объявлении класса [namespace.]класс1/тип пропущено ключевое слово public. Например, если дает ошибку следующее объявление класса:

      public MyClass classVar = new MyClass();   //тут выскакивает ошибка CS0052
namespace MyApp
{ class MyClass { //далее определение класса ...

то попробуйте исправить определение класса MyClass, добавив public:

namespace MyApp
{ public class MyClass { //далее определение класса ... 

На языке C# модификатор static используется для объявления статического члена, принадлежащего собственно типу, а не конкретному объекту. Применение static к переменной класса означает, что эта переменная сохраняет свое значение для всех экземпляров этого класса. То есть эта переменная как бы общая для всех экземпляров этого класса. Например, если мы определим класс MyClass, и внутри него определим переменную static int cnt, то эту переменную можно использовать для подсчета созданных экземпляров класса MyClass, потому что переменная cnt будет иметь одно и то же значение для всех созданных экземпляров MyClass. Для C/C++ static имеет другое значение, потому что предназначено для сохранения значения переменной между вызовами функции, и для ограничения видимости переменной в пределах модуля.

Модификатор static можно использовать с классами, полями, методами, свойствами, операторами, событиями и конструкторами, но нельзя — с индексаторами, деструкторами или типами, отличными от классов. На статический член нельзя ссылаться через экземпляр, а можно только через имя типа. Подробности смотрите в MSDN, раздел "static (Справочник по C#)".

Такое может произойти, если рекурсивно закрывается одна форма из другой, в результате закрытие 1-й формы вызовет обработчик FormClosed 1-й формы, который вызовет обработчик FormClosed 2-й формы, который в свою очередь вызовет обработчик FormClosed 1-й формы, и так далее по циклу, пока стек приложения не переполнится. Например, Ваше приложение имеет несколько форм - Form1, Form2, Form3, и нужно, чтобы закрытие одной формы привело к закрытию всего приложения. Если Вы напишете код, который будет закрывать все формы подряд в каждом обработчике FormClosed, то это приведет к вышеуказанной ошибке. Вот пример ошибочного кода (Form1 главная форма, которая запускается методом Application.Run):

      private void Form1_FormClosed(object sender, FormClosedEventArgs e)
      {
         //тут действия, выполняемые при закрытии приложения
         ...
      }
 
      private void Form2_FormClosed(object sender, FormClosedEventArgs e)
      {
         Program.Form3.Close();  //тут произойдет ошибка System.StackOverflowException
         Program.Form1.Close();
      }
 
      private void Form3_FormClosed(object sender, FormClosedEventArgs e)
      {
         Program.Form2.Close();  //тут произойдет ошибка System.StackOverflowException
         Program.Form1.Close();
      }

А вот так нужно исправить код, чтобы не возникала ошибка System.StackOverflowException в System.Windows.Forms.dll:

      private void Form1_FormClosed(object sender, FormClosedEventArgs e)
      {
         //тут действия, выполняемые при закрытии приложения
         ...
      }
 
      private void Form2_FormClosed(object sender, FormClosedEventArgs e)
      { //закрытие Form1 завершит программу, т. е. будут автоматически закрыты Form1, и Form3
         Program.Form1.Close();
      }
 
      private void Form3_FormClosed(object sender, FormClosedEventArgs e)
      {  //закрытие Form1 завершит программу, т. е. будут автоматически закрыты Form1, и Form2
         Program.Form1.Close();
      } 

Используйте метод Clear. Пример:

namespace MyApp
{ public partial class Form1 : Form { public Form1() { frmlog = new log(this.GetType().ToString()); InitializeComponent(); }   public void Clean() { System.Drawing.Graphics formGraphics = this.CreateGraphics(); //серый цвет, составленный из компонентов R, G, B Color formcolor = Color.FromArgb(235, 234, 219);   formGraphics.Clear(formcolor); }   .. }
} .. Clean(); //очистка поверхности формы

В выражении частное и делитель имеют разные, несовместимые типы. Попробуйте и частное, и делитель привести к типу float, для этого добавьте префикс (float), который обеспечит явное приведение типа переменной.

float var1, result;
int var2;  ...
result = var1 / var2;             //ошибка CS0019
result = var1 / (float)var2;      //OK

Нужно во время разработки задать у формы свойства MinimumSize и MaximumSize на нужные значения, и сделайте их одинаковыми. Также задайте свойство формы MaximizeBox в значение false. После этого пользователь, запустив программу, не сможет поменять размер окна, и не сможет развернуть окно на весь экран.

Второй способ зафиксировать размер формы во время выполнения - задайте обработчик события OnResize, в котором установите свойства Width и Height в нужное значение.

      private void Form1_Resize(object sender, EventArgs e)
      {
         Width  = 640;
         Height = 480;
      }

Используйте метод MeasureString класса System.Drawing.Graphics. Пример:

      public float GetTextWidthInPixels(string measuredstring)
      {
         const string FontName = "Lucida Console";
         const int FontSize = 8;
 
         SizeF stringSize = new SizeF();
         Font stringFont = new Font(FontName, FontSize);
         int stringWidth = 2000; // Установка максимальной ширины строки.
         System.Drawing.Graphics formGraphics = this.CreateGraphics();
         stringSize = formGraphics.MeasureString(measuredstring, stringFont, stringWidth);
         return stringSize.Width;
      }

Этим же способом можно узнать и высоту текста в пикселах (для нашего примера это будет stringSize.Height).

Решить проблему можно, если отключить для отладки возможность IntelliTrace. Делается это следующим образом - меню Сервис -> пункт Параметры -> разверните узел IntelliTrace -> щелкните Общие -> снимите флажок Включить IntelliTrace -> нажмите кнопку OK.

Используйте метод ShowDialog(), подробности см. в [4].

См. также вопрос "Как запустить внешнее приложение?".

В программе могут быть и невизуальные классы, у которых нет окон, т. е. во время разработки к ним не привязана форма Form. Поэтому бросить компонент с панели элементов в такой класс не получится.

c-sharp-timer-component

Однако можно создать экземпляр класса (компонент) во время выполнения программы, что решит проблему. На примере таймера Timer с Панели Элементов, который добавляется программно в невизуальный класс MyClass:

using System;
using System.Collections.Generic;
using System.Linq;using System.Text;
namespace MyApp
{ class MyClass { ... //переменная - хранилище для экземпляра класса таймера private System.Windows.Forms.Timer mytimer; //счетчик времени в единицах 100 мс public UInt32 time100stamp = 0;   //////////////////////////////////////////////////////////////// /// Конструктор класса MyClass public MyClass() { ... //создание экземпляра класса таймера во время выполнения mytimer = new System.Windows.Forms.Timer(); //интервал таймера 100 мс mytimer.Interval = 100; //добавление события на срабатывание таймера mytimer.Tick += new System.EventHandler(this.mytimer_Tick); //запуск таймера mytimer.Start(); ... }   //////////////////////////////////////////////////////////////// /// Событие 100-мс таймера, которое просто инкрементирует /// счетчик времени. private void mytimer_Tick(object sender, EventArgs e) { time100stamp++; }   ... }
}

Используйте this.GetType().ToString(), получите что-то типа "myApp.Form1".

См. также Q053.

Предположим, что наш проект называется myApp. В таблице перечислены методы выполнения этих действий.

Методы

Пояснения

System.AppDomain.CurrentDomain.FriendlyName

Имя исполняемого файла в форме myApp.vhost.exe

System.AppDomain.CurrentDomain.BaseDirectory

Каталог, где находится исполняемый файл (полный путь каталога).

System.Reflection.Assembly.GetExecutingAssembly().FullName

Получим строку типа "myApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"

System.Reflection.Assembly.GetExecutingAssembly().Location

Полный путь до исполняемого файла в виде c:\VisualStudioProjects\myApp\bin\Debug\myApp.exe

Path.GetFileNameWithoutExtension(fullname)

Получение имени исполняемого файла без пути и расширения, получим что-то типа строки "myApp".

Примеры:

//изменение расширения файла
using System.IO;
string logfilename;
logfilename = System.Reflection.Assembly.GetExecutingAssembly().Location; logfilename = Path.ChangeExtension(logfilename, ".log");
//Как получить имя исполняемого файла без расширения?
using System.IO;

string
fullname = System.Reflection.Assembly.GetExecutingAssembly().Location;
string name_without_ext = Path.GetFileNameWithoutExtension(fullname);

Если цикл длинный, то программа начинает заметно тормозить как саму себя (интерфейс программы работает плохим откликом), так и компьютер. Если Вы запустили отдельный поток для каких-нибудь действий (например, ждете данные от сокета, или последовательного порта), то цикл без задержки нежелателен, нужно отдавать ресурсы процессора другим потокам и программам. Делается такая операция обычно с помощью Sleep(ms). Пример введения задержки (например, для приостановки цикла) на 10 миллисекунд:

System.Threading.Thread.Sleep(10);

Пример таких ошибок:

Ошибка 17 Не удалось опубликовать из-за того, что не удалось построить проект. 1
Ошибка 18 При подписи произошла ошибка: Не удалось подписать bin\Release\app.publish\\setup.exe. SignTool Error: The signer's certificate is not valid for signing.
SignTool Error: An error occurred while attempting to sign: bin\Release\app.publish\\setup.exe C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets 3987

Решение проблемы: зайдите в меню Проект -> Свойства проекта, снимите галочку "Подписать манифесты ClickOnce". После запуска публикации система выдаст запрос: "Приложение подписано не тем ключом, что существующее на сервере приложение. Перезаписать его?", ответьте утвердительно.

Известно, что объединение (union) на языках C и C++ позволяет интерпретировать одни и те же данные по-разному. Например, 32-разрядные данные (int) можно представить как два 16-разрядных слова (short) или как 4 отдельные байта (unsigned char).

typedef union _CANdata
{ u32 d32 [2]; u16 d16 [4]; u8 d8 [8];
}TCANdata;

К сожалению, C# не поддерживает объединения C/C++. Однако с той же целью можно использовать StructLayout(LayoutKind.Explicit) и атрибуты FieldOffset. Предположим, что у Вас есть структура из 64 бит данных, и Вам нужно представить её либо как 8 байт (byte), либо как два целых 32-разрядных числа (int). Атрибут FieldOffset регулирует смещение нужных нам частей данных. Например:

using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Explicit)]
struct MyArray
{ //здесь начинаются 8 байт данных [FieldOffset(0)] public byte Byte1; [FieldOffset(1)] public byte Byte2; [FieldOffset(2)] public byte Byte3; [FieldOffset(3)] public byte Byte4; [FieldOffset(4)] public byte Byte5; [FieldOffset(5)] public byte Byte6; [FieldOffset(6)] public byte Byte7; [FieldOffset(7)] public byte Byte8; //здесь те же 8 байт интерпретируются как два 32-битные числа [FieldOffset(0)] public int Int1; [FieldOffset(4)] public int Int2;
}

[Пример1: проблема с ulong и int]

При попытке задать в перечислении 64-битную величину получаю ошибку CS0266 "Не удается неявно преобразовать тип "ulong" в "int". Существует явное преобразование (возможно, пропущено приведение типов) C:\csharpprj\protocol.cs ...".

      public enum request_bits
      {
         chnmask = 0x3F,
         //биты масок сигналов
         ARS75 = (1 << 8),
         ARS125 = (1 << 9),
         ARS175 = (1 << 10),
         ARS225 = (1 << 11),
         ARS275 = (1 << 12),
         ARS325 = (1 << 13),
         reserved = (0x3FFFFFFFFFFFF << 14)  //ошибка CS0266
      }

Решение проблемы: по умолчанию перечисление (enum) на C# подразумевает использование целых чисел (int). Чтобы исправить ошибку, добавьте явное указание типа для перечисления (можно указать типы byte, sbyte, short, ushort, int, uint, long или ulong):

      public enum request_bits : ulong
      {
         chnmask = 0x3F,
         //биты масок сигналов
         ARS75 = (1 << 8),
         ARS125 = (1 << 9),
         ARS175 = (1 << 10),
         ARS225 = (1 << 11),
         ARS275 = (1 << 12),
         ARS325 = (1 << 13),
         reserved = ((ulong)0x3FFFFFFFFFFFF << 14)
      }

[Пример2: проблема с типом byte]

Вот такой простой код неожиданно дает ошибку CS0266:

byte b1, b2;
 
b1 = 0;
b2 = b1+1;    //ошибка CS0266...

Проблема тут в том, что оператор + в дает в результате тип int. Решение проблемы:

byte b1, b2;
 
b1 = 0;
b2 = (byte)(b1+1);

Для сравнения используйте метод String.Compare. Он возвращает целое число, которое показывает их относительное положение в порядке сортировки. Результат равен нулю, если сравниваемые строки одинаковы. Простые примеры:

if (0 == String.Compare(strA, strB, true))
{ //строки strA и strB совпадают без учета регистра ..
}if (0 == String.Compare(strA, strB))
{ //Сюда попали, если строки равны по критерию // сортировки. Внимание: на сравнение при таком // вызове влияют правила учета регистра, связанные // с языком и региональными параметрами. //Поэтому этот вариант перегрузки String.Compare // использовать нежелательно. ..
}

(Даниил Захаров): Деструктор в С# несколько отличается от деструктора в С++.

В С++ он вызывается напрямую механизмами языка. Это происходит так же быстро, как и вызов метода из кода пользователя (ну, пожалуй, совсем ненамного дольше).

В С# деструкторы вызываются не пользовательским кодом, а средой CLR. Код C# потому и называется управляемым, что по сути он запускается под управлением среды. Поэтому, к примеру, здесь нет указателей - среда сама выполняет операции с указателями автоматически и не поддерживает код, который их использует. Механизм использования указателей все же оставлен в целях совместимости, но при этом классы, их использующие, надо помечать ключевым словом unsafe, разрешать выполнение этого unsafe кода в свойствах проекта. Но самое главное - среда не будет управлять этим кодом - она передаст ему управление и будет дожидаться, когда получит его обратно.

То же самое относится и к деструкторам. Деструкторы призваны освободить ресурсы, занимаемые классом. Но класс не может освободить ресурсы, которыми фактически не владеет. А по факту владеет он только теми кусками, которыми управляет сам. Поэтому деструктор вызывается не пользовательским кодом, а из CLR, то есть снаружи. Но не сразу, а только после того, как среда отметит, что на экземпляр класса больше не ссылается ни одной переменной. Отсюда возникает задержка - только после второго прохода сборщика мусора. И отсюда потеря производительности - мы заставляем сборщик мусора отслеживать еще и деструкторы.

Моя практика показала, что при уничтожении около полусотни объектов с деструкторами (причем до крайности простыми - из статической переменной вычиталась единица) комп "подвисает" так, что это становится заметно пользователю. Замеры показали диапазоны 18 .. 47 мс, т. е. почти всегда "терялся" один кадр на экране, а в некоторых случаях - до двух/трех (при частоте смены кадров в 30 Гц).

См. также Q052.

Использование директивы using объявляет пространство поиска используемых имен в библиотеках C#, что позволяет упростить и сократить текст программы. Для примера - использование using System.IO позволяет обойтись без постоянного упоминания префикса System.IO:

//Без using System.IO:
..
if (!System.IO.File.Exists("readme.txt"))
{ //..
}
//С использованием using System.IO:
using System.IO; ..
if (!File.Exists("readme.txt"))
{ //..
}

В консольном и GUI-приложении можно создать и отобразить окно с помощью класса MessageBox. В этом окне можно отобразить текст, кнопки и символы, которые информируют пользователя о чем-то и/или дают ему указания. При этом работа приложения блокируется до тех пор, пока пользователь не закроет окно (стандартная особенность модального окна). Более подробно об использовании MessageBox, о его свойствах и методах см. справку MSDN (запрос для Google MessageBox site:msdn.microsoft.com). Простейший пример вызова MessageBox:

using System.Windows.Forms;
 
..
MessageBox.Show("Hello World!");

MessageBoxHelloWorld-C-sharp

Если при попытке компиляции выводится ошибка о том, что System.Windows.Forms не найдено в пространстве имен ('Forms' does not exist in the namespace system.windows), то добавьте на него ссылку в проекте. Для этого сделайте правый клик на дерево файлов проекта (Solution Tree), выберите в контекстном меню Добавить ссылку..., в появившемся окне "Добавить ссылку" перейдите на закладку .NET, прокрутите список и выберите в нем System.Windows.Forms, нажмите OK.

Еще один пример подпрограммы вывода модального диалогового окна с выходом из приложения, если установлен в true параметр exit:

private void Err(string message, bool exit)
{
   MessageBoxButtons buttons = MessageBoxButtons.OK;
   DialogResult result;
 
   // Отображение окна MessageBox с сообщением message:
   result = MessageBox.Show(message, "Ошибка", buttons);
   if (result == System.Windows.Forms.DialogResult.OK)
   {
      // Закрытие родительской формы:
      if (exit)
         this.Close();
   }
}

Пример:

При подписи произошла ошибка: Не удалось подписать binReleaseapp.publishsetup.exe.
SignTool Error: The signer's certificate is not valid for signing.
SignTool Error: An error occurred while attempting to sign: binReleaseapp.publishsetup.exe имя_проекта

Как исправить: зайдите в свойства проекта (Project -> Properties...), перейдите в раздел Подписывание (Signing), нажмите кнопку "Создать тестовый сертификат..." (create test certificate). Появится окно запроса пароля сертификата. Оставьте поля пустыми и нажмите OK, после чего опубликуйте проект.

Процесс по шагам:

1. Откройте Панель элементов, выберите PictureBox, бросьте на форму.
2. Выберите свойства pictureBox1 -> BackgroundImage, нажмите справа кнопку с многоточием, откроется окно выбора ресурса. Выберите уже имеющийся ресурс, или нажмите внизу кнопку Импорт..., и выберите нужную картинку (формата BMP или PNG).
3. По умолчанию свойство BackgroungImageLayout установлено в Tile, что означает, что картинка будет размножена мозаикой по всей площади pictureBox1 (границы этой площади можно раздвинуть мышкой). Чтобы картинка была только одна, выберите значение None для свойства BackgroungImageLayout. Также свойство SizeMode установите в AutoSize, если хотите, чтобы размеры pictureBox1 автоматически подстраивались под размер картинки.
4. Чтобы поменять картинку runtime (во время выполнения программы), нужно присвоить свойство BackgroundImage, пример:

if (!opened)
{
    picDoor1.BackgroundImage = WindowsFormsApplication1.Properties.Resources.door_closed;
}
else { picDoor1.BackgroundImage = WindowsFormsApplication1.Properties.Resources.door_open; }

Здесь door_closed и door_disabled - ссылки на импортированные картинки door_closed.png и door_open.png.

door-closed door-open

Пример:

byte pina, mask;
pina = dev.dev.ReadByte(ATMegaAddresses.PINA); mask = (1 << ATMegaBits.PB0);
// error CS0029: Cannot implicitly convert type 'int' to 'byte'
// (неявное преобразование типа "int" в "bool" невозможно):
if (pina & mask) { ... }

Причина ошибки в том, что результат побитовых операций всегда типа int. Для того, чтобы исправить ошибку, требуется явное преобразование типа:

//А так нормально, без ошибки:
if (0!=(byte)(pina & mask)) { ... }
//так тоже можно:
if (0 != (pina & mask)) { ... }

Программное рисование производится с помощью методов класса System.Drawing.Graphics. Вот пример, как надо рисовать закрашенный круг:

/// < summary>
/// Рисует над картинкой picture кружок радиусом radius.
/// < /summary>
/// < param name="picture">< /param>
/// < param name="radius">< /param>
private void ShowKnock(PictureBox picture, int radius) { Point center = new Point(picture.Left + (picture.Width / 2), picture.Top - 25); System.Drawing.Graphics graphics = this.CreateGraphics(); System.Drawing.Rectangle rectangle; SolidBrush Brush; //Красный кружок Brush = new SolidBrush(System.Drawing.Color.Red); rectangle = new System.Drawing.Rectangle(center.X - radius, center.Y - radius, radius * 2, radius * 2); graphics.FillEllipse(Brush, rectangle); }

Как нарисовать черную линию:

System.Drawing.Pen myPen;
myPen = new System.Drawing.Pen(System.Drawing.Color.Black);
System.Drawing.Graphics formGraphics = this.CreateGraphics();
formGraphics.DrawLine(myPen, 0, 0, 200, 200);
myPen.Dispose();
formGraphics.Dispose();

Пример рисования не заштрихованных эллипса и прямоугольника:

private void DrawIt()
{
    System.Drawing.Graphics graphics = this.CreateGraphics();
    System.Drawing.Rectangle rectangle = new System.Drawing.Rectangle(
       50, 50, 150, 150);
    graphics.DrawEllipse(System.Drawing.Pens.Black, rectangle);
    graphics.DrawRectangle(System.Drawing.Pens.Red, rectangle);
}

В VB такая операция происходит присвоением object=nothing, на C++ применяется оператор delete. Как явно удалять объекты на C#?

В C# можно сделать аналогичное действие object=null, но в действительности присвоение object=null не удаляет объект, а просто делает недействительной ссылку на него. В .NET это дает сигнал сборщику мусора (garbage collector) удалить объект из памяти. Но Вы не можете знать, когда конкретно это произойдет.

В C# при создании объекта оператором new память для него выделяется из кучи (heap). Вы также можете Объект удалится автоматически тем же сборщиком мусора, когда управление выйдет за пределы существование объекта (например, когда завершается функция, которая создала объект оператором new). Но опять-таки неизвестно, в какой момент произойдет освобождение памяти, связанное с удалением объекта.

Если в C# у объекта есть метод Dispose(), то его нужно обязательно вызвать до того, как управление выйдет из сферы действия объекта. Вы можете использовать ключевое слово using, чтобы сделать эту операцию автоматически, например:

using (Graphics gr = Graphics.FromBitmap(bm))
{
   // тут какие-то действия
   ...
}// здесь будет автоматически вызван gr.Dispose()

Принудительно запустить процедуру сбора мусора можно вызовом GC.Collect(), но это делать не рекомендуется.

См. также Q045.

Для произвольного класса:

Type myType = typeof(MyClass);
// Получение строки namespace класса myClass. Console.WriteLine("Namespace: {0}.", myType.Namespace);

Для строки:

Type myType = typeof(MyClass);
var n = myType.Namespace;

Для метки WinForm:

Type myType = typeof(MyClass);
namespaceLabel.Text = myType.Namespace;

Если у Вас есть экземпляр x класса A внутри namespace B, то можете использовать:

string s = x.GetType().Namespace;

В строке s не будет содержаться "B". Вы можете также использовать x.GetType().Name, чтобы получить имя типа, или x.GetType().FullName чтобы получить полное имя, вместе с B.

Другой вариант, поместите в Вашу сборку функцию:

public static string GetCurrentNamespace()
{
    return System.Reflection.Assembly.GetExecutingAssembly().EntryPoint.DeclaringType.Namespace;
}

Если хотите, чтобы эта функция находилась в библиотеке, которая используется Вашей программой, то определите функцию так:

[System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.NoInlining)]
public static string GetCurrentNamespace() { return System.Reflection.Assembly.GetCallingAssembly().EntryPoint.DeclaringType.Namespace; }

См. также Q038.

[Вариант 1]

Чтобы в Windows Forms нарисовать метку с вертикальным текстом, нужно переопределить событие Paint, и рисовать в нем метку вертикально. Имейте в виду, что GDI оптимизировано для горизонтального рисования меток. Если Вы повернете текст (даже если угол поворота будет кратен 90 градусов), то возможно это будет работать не очень качественно.

Возможно лучшим решением будет нарисовать текст (или поручить метке нарисовать себя) в растровую картинку (bitmap), и затем отобразить эту картинку повернутой.

Ниже приведен код, который рисует Custom Control с вертикальным текстом. Имейте в виду, что текст ClearType не работает, если текст не горизонтальный.

using System.Drawing.Drawing2D;
using System.Windows.Forms;

public partial class VerticalLabel : UserControl { public VerticalLabel() { InitializeComponent(); }
private void VerticalLabel_SizeChanged(object sender, EventArgs e) { GenerateTexture(); }
private void GenerateTexture() { StringFormat format = new StringFormat(); format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Center; format.Trimming = StringTrimming.EllipsisCharacter;
Bitmap img = new Bitmap(this.Height, this.Width); Graphics G = Graphics.FromImage(img);
G.Clear(this.BackColor);
SolidBrush brush_text = new SolidBrush(this.ForeColor); G.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit; G.DrawString(this.Name, this.Font, brush_text, new Rectangle(0, 0, img.Width, img.Height), format); brush_text.Dispose();
img.RotateFlip(RotateFlipType.Rotate270FlipNone);
this.BackgroundImage = img; } }

Можно повернуть текст в событии OnPaint, или в методе Paint:

private void uc1_Paint(object sender, PaintEventArgs e)
{
    string Name;
    var g = e.Graphics;
    g.DrawString(Name, new Font("Tahoma", 8), Brushes.Black, 0, 0,
    new StringFormat(StringFormatFlags.DirectionVertical));
}

[Вариант 2]

Создайте свой класс myLabel, который может поворачивать текст. Этот класс можно будет перетаскивать в форму приложения прямо из панели компонентов ToolBox.

using System.Drawing;

class myLabel:System.Windows.Forms.Label { public int RotateAngle { get; set; } // чтобы повернуть текст public string NewText { get; set; } // чтобы нарисовать текст protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) { Brush b =new SolidBrush(this.ForeColor); e.Graphics.TranslateTransform(this.Width / 2, this.Height / 2); e.Graphics.RotateTransform(this.RotateAngle); e.Graphics.DrawString(this.NewText, this.Font,b , 0f, 0f); base.OnPaint(e); } }

Для этого класса можно установить свойства:

mylbl.Text = "";              // текст, который можно поменять свойством NewText
mylbl.NewText = "Hello";
mylbl.AutoSize = false;       // подстройка размера под текст
mylbl.ForeColor = Color.Red;  // цвет текста
mylbl.RotateAngle = -90;      // угол поворота

Улучшенная реализация, поддерживающая автоматическое изменение размеров виджета:

public class RotatingLabel : System.Windows.Forms.Label
{
   private int m_RotateAngle = 0;
   private string m_NewText = string.Empty;
public int RotateAngle { get { return m_RotateAngle; } set { m_RotateAngle = value; Invalidate(); } } public string NewText { get { return m_NewText; } set { m_NewText = value; Invalidate(); } }
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) { Func< double, double > DegToRad = (angle) => Math.PI * angle / 180.0; Brush b = new SolidBrush(this.ForeColor); SizeF size = e.Graphics.MeasureString(this.NewText, this.Font, this.Parent.Width); int normalAngle = ((RotateAngle % 360) + 360) % 360; double normaleRads = DegToRad(normalAngle); int hSinTheta = (int)Math.Ceiling((size.Height * Math.Sin(normaleRads))); int wCosTheta = (int)Math.Ceiling((size.Width * Math.Cos(normaleRads))); int wSinTheta = (int)Math.Ceiling((size.Width * Math.Sin(normaleRads))); int hCosTheta = (int)Math.Ceiling((size.Height * Math.Cos(normaleRads))); int rotatedWidth = Math.Abs(hSinTheta) + Math.Abs(wCosTheta); int rotatedHeight = Math.Abs(wSinTheta) + Math.Abs(hCosTheta); this.Width = rotatedWidth; this.Height = rotatedHeight; int numQuadrants = (normalAngle >= 0 && normalAngle < 90) ? 1 : (normalAngle >= 90 && normalAngle < 180) ? 2 : (normalAngle >= 180 && normalAngle < 270) ? 3 : (normalAngle >= 270 && normalAngle < 360) ? 4 : 0; int horizShift = 0; int vertShift = 0; if (numQuadrants == 1) { horizShift = Math.Abs(hSinTheta); } else if (numQuadrants == 2) { horizShift = rotatedWidth; vertShift = Math.Abs(hCosTheta); } else if (numQuadrants == 3) { horizShift = Math.Abs(wCosTheta); vertShift = rotatedHeight; } else if (numQuadrants == 4) { vertShift = Math.Abs(wSinTheta); } e.Graphics.TranslateTransform(horizShift, vertShift); e.Graphics.RotateTransform(this.RotateAngle); e.Graphics.DrawString(this.NewText, this.Font, b, 0f, 0f); base.OnPaint(e); } }

Отличный, рабочий пример есть в MSDN (ключевая строка для поиска ICustomFormatter.Format Method site:msdn.microsoft.com).

binaryformatter.cs

using System;
using System.Globalization;
//using System.Numerics;
 
public class BinaryFormatter : IFormatProvider, ICustomFormatter
{
   // Реализация IFormatProvider.GetFormat
   publicobjectGetFormat(Type formatType)
   {
      // Определение, какой пользовательский объект форматирования запрашивается.
      if (formatType == typeof(ICustomFormatter))
         return this;
      else
         return null;
   }
 
   // Форматирование числа в двоичном виде (binary, B), восьмеричном (octal, O)
   // или шестнадцатеричном (hexadecimal, H).
   public string Format(string format, object arg, IFormatProvider formatProvider)
   {
      // Обработка строки формата.
      int baseNumber;
      
      // Обработка null или пустой строки формата, строки с указателем точности
      // (precision specifier).
      string thisFmt = String.Empty;
      // Распаковка первого символа в строек формата (precision specifier
      // не поддерживается).
      if (! String.IsNullOrEmpty(format))
         thisFmt = format.Length > 1 ? format.Substring(0, 1) : format;
 
      // Получение байтового массива, представляющего числовое значение.
      byte[] bytes;
 
      if (arg is sbyte)
      {
         string byteString = ((sbyte) arg).ToString("X2");
         bytes = newbyte[1] { Byte.Parse(byteString, System.Globalization.NumberStyles.HexNumber ) };
      }
      else if (arg is byte)
      {
         bytes = newbyte[1] { (byte) arg };
      }
      else if (arg is short)
      {
         bytes = BitConverter.GetBytes((short) arg);
      }
      else if (arg is int)
      {
         bytes = BitConverter.GetBytes((int) arg);
      }
      else if (arg is long)
      {
         bytes = BitConverter.GetBytes((long) arg);
      }
      else if (arg is ushort)
      {
         bytes = BitConverter.GetBytes((ushort) arg);
      }
      else if (arg is uint)
      {
         bytes = BitConverter.GetBytes((uint) arg);
      }
      else if (arg is ulong)
      {
         bytes = BitConverter.GetBytes((ulong) arg);
      }
      //else if (arg is BigInteger)
      //{
      // bytes = ((BigInteger) arg).ToByteArray();
      //}
      else
      {
         try
         {
            return HandleOtherFormats(format, arg);
         }
         catch (FormatException e)
         {
            throw new FormatException(String.Format("The format of '{0}' is invalid.", format), e);
         }
      }
  
      switch (thisFmt.ToUpper())
      {
      case"B":
         baseNumber = 2;
         break;
      case"O":
         baseNumber = 8;
         break;
      case"H":
         baseNumber = 16;
         break;
      // Обработка не поддерживаемых строк формата.
      default:
         try
         {
            return HandleOtherFormats(format, arg);
         }
         catch (FormatException e)
         {
            throw new FormatException(String.Format("The format of '{0}' is invalid.", format), e);
         }
      }
 
      // Возврат отформатированных строк.
      string numericString = String.Empty;
 
      for (int ctr = bytes.GetUpperBound(0); ctr >= bytes.GetLowerBound(0); ctr--)
      {
         string byteString = Convert.ToString(bytes[ctr], baseNumber);
         if (baseNumber == 2)
            byteString = new String('0', 8 - byteString.Length) + byteString;
         else if (baseNumber == 8)
            byteString = new String('0', 4 - byteString.Length) + byteString;
         else  // База 16.
            byteString = new String('0', 2 - byteString.Length) + byteString;
         numericString += byteString + " ";
      }
      return numericString.Trim();
   }
 
   private string HandleOtherFormats(string format, object arg)
   {
   if (arg is IFormattable)
      return ((IFormattable)arg).ToString(format, CultureInfo.CurrentCulture);
   else if (arg != null)
      return arg.ToString();
   else
      return String.Empty;
   }
}

Пример использования:

label1.Text = String.Format(new BinaryFormatter(), "{0:B}", value);

См. также: преобразование даты, времени в строку Q014, преобразование чисел в строку Q015, преобразование строк в числа Q022.

'Необработанное исключение типа "System.BadImageFormatException" произошло в имя_приложения.exe

Дополнительные сведения: Была сделана попытка загрузить программу, имеющую неверный формат. (Исключение из HRESULT: 0xXXXXXXXX)'

System.BadImageFormatException

Чаще всего проблема из-за того, что вызов функции (метода) потребовал вызова DLL, скомпилированной под другую систему. Например, программа работает на 64-битной версии Windows, а DLL сделана для 32-битной версии. Что можно сделать:

1. Найти DLL, которая скомпилирована под Вашу операционную систему.

2. Если у Вас 64-битная система, а загружаемая DLL 32-битная, то можно изменить в свойствах тип конечной платформы на 32-битную. Для этого откройте свойства проекта, перейдите в раздел Построение, и выберите в выпадающем списке "Конечная платформа:" вариант x86 (см. скриншот ниже). После этого пересоберите проект.

Csharp change platform

Для этого достаточно поменять свойство формы Text. Пример:

[Текст модуля myform.cs]

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
 
namespace myprog
{
   /// < summary >
   /// Главная форма/окно программы.
   /// < /summary >
   public partial class myform : Form
   {
      ...
      private void UpdateForm(bool force_update)
      {
         //рисование заголовка формы
         Text = "Это будет новый текст на плашке формы";
         ...
      }
   ...

Можно также к свойству Text обращаться через объект this:

         this.Text = "Это будет новый текст на плашке формы";

Для этого создайте событие FormClosing, и в его обработчике присвойте FormClosingEventArgs.Cancel значение true. Пример:

      private void myform_FormClosing(object sender, FormClosingEventArgs e)
      {
         e.Cancel = true;
      }

После этого на форме будет отображаться стандартная кнопка с крестиком, но она перестанет действовать. Подробнее про поведение события закрытия формы см. [6].

Можно ли в C# декларировать переменную таким образом, чтобы в каждом классе (представленном модулем *.cs) можно было получить доступ к содержимому этой переменной без необходимости создавать экземпляр какого-либо класса? Другими словами, если ли в C# глобальные переменные, не привязанные к какому-либо экземпляру класса? Есть ли какая-то альтернатива обмениваться данными между классами через одну внешнюю переменную?

В C# не существует такого понятия, как глобальная переменная, и точка. Однако есть некий аналог в виде модификатора public static, который можно применять к классам и к отдельным членам класса. Короче говоря, используйте класс типа public static, и получайте доступ к нему где угодно и откуда угодно.

public static class MyStaticValues
{
   public static bool MyStaticBool {get;set;}
}
 
public static class Globals
{
    public static String s_Name = "Mike";    // Эту переменную можно модифицировать в коде
    public const Int32 VALUE = 10;           // Эту нельзя
}

Можно использовать переменные этого класса в любом месте, когда они находятся в одном и том же пространстве имен (namespace), что и ссылающийся на переменные код. Можно объявить MyStaticValues или Globals без пространства имен (что поместит эти статические классы в глобальное пространство имен приложения, application namespace), или Вы работаете с разными пространствами имен, то можете указать нужное пространство следующей директивой:

string name = Globals.s_Name;

[Как использовать аналоги глобальных переменных в C#]

Сначала хорошенько подумайте - действительно ли Вам нужна глобальная переменная. Скорее всего решение должно быть принято, исходя из рассмотрения техзадания на разрабатываемое программное обеспечение. Обычно статические переменные могут пригодиться для таких объектов в программе, которые имеются исключительно в одном экземпляре, их не нужно создавать и удалять динамически, и если эти объекты подразумеваются для использования несколькими разными классами в программе. Имейте в виду, что иногда проблемы, возникающие из-за конкуренции доступа к глобальным переменным (особенно актуально в сложных системах), бывает трудно отладить, поэтому будьте готовы к трудностям.

Итак, если положительное решение принято и глобальные переменные все-таки нужны, то действуйте примерно по такой схеме:

1. Поместите все глобальные переменные в один static класс (для удобства управляемости кодом).
2. Используйте внутри этого класса обертки get/set для доступа к "глобальным" переменным. Это предоставит дополнительный механизм для отладки проблем, возникающих из-за конкурентного доступа.

Вот базовый пример для такого класса:

public class Globals
{
    private static bool _expired;
    public static bool Expired 
    {
        get
        {
            // Чтение обычно очень простое:
            return _expired;
        }
        set
        {
            // В этом месте можно добавить логику, отслеживающую
            // проблемы конкурентного доступа (race conditions),
            // вывод в лог или другие методы отладки.
            _expired = value;
        }
    }
    // По ситуации можно расширить этот пример static-методами Read-Modify-Write
    // (чтение-модификация запись) для поддержки целостности данных при 
    // конкурентном доступе (поддержка атомарности). Конкретное решение
    // зависит от Ваших потребностей.
    ...
}

Использование переменных этого класса из других классов (находящихся в том же пространстве имен namespace) может быть таким:

// Чтение:
bool areWeAlive = Globals.Expired;
 
// Запись:
Globals.Expired = true;

Хорошая информация по теме static классов C# есть в статье "Статика в C#", опубликованной на Хабрахабр (ключевые слова для поиска Статика в C# site:habrahabr.ru).

Пример определения дочернего класса TMyClass внутри родительского класса myform:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
 
namespace myprog
{
   public partial class myform : Form
   {
      public class TMyClass
      {
         public TMyClass()
         {
            //Это конструктор дочернего класса
            ...
         }
         ...
      }
//Определение массива классов из 7 элементов: public TMyClass [] classarray = new TMyClass [7]; ... public myform() { //Это конструктор родительского класса InitializeComponent(); ... } //Вот так нужно правильно инициализировать массив классов TMyClass внутри // события загрузки формы (родительский класс myform). Альтернативно то же // самое можно выполнить в конструкторе родительского класса: private void myform_Load(object sender, EventArgs e) { for (int idx = 0; idx < 7; idx++) { //Вызов конструкторов TMyClass: classarray[idx] = new TMyClass(); } } } }

После этого можно обращаться к экземплярам классов TMyClass по индексу (вызывать его методы, обращаться к свойствам). Если не сделать инициализацию элементов массива, то при запуске программы и попытке доступа к экземплярам классов из массива получите ошибку "ссылка на объект не указывает на экземпляр объекта".

У меня была следующая проблема: KeyDown срабатывает нормально, пока не добавлена на форму кнопка. Как только кнопка добавлена, KeyDown перестает работать.

[Решение 1]

Нужно установить у формы свойство KeyPreview=true. Этот совет можно найти на большинстве форумов, однако он помогает не всегда - возможно, это зависит от версии библиотек Visual Studio C#. Мне не помогло (Microsoft Visual Studio 2010 Версия 10.0.40219.1 SP1Rel).

[Решение 2]

Нужно переопределить метод ProcessCmdKey формы. Для этого просто добавьте в класс формы следующий код:

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
   switch (keyData)
   {
      case Keys.Space:
         //Действия при нажатии пробела:
         ...
         return true;
      case Keys.Left:
         //Действия при нажатии кнопки "влево":
         ...
         return true;
      case Keys.Right:
         //Действия при нажатии кнопки "вправо":
         ...
         return true;
      //Тут можно добавить обработку других нажатий...
      ...
   }
   return base.ProcessCmdKey(ref msg, keyData);
}

Обратите внимание, что при возврате true из метода ProcessCmdKey не будет отправлено событие нажатия на элемент управления, на котором установлен фокус.

Окно имеет высоту (свойство Height) и ширину (свойство Width), которые определяют размеры окна формы. Эти свойства можно читать или менять программно. Однако есть еще и "клиентская" область формы, которая меньше. По ширине она меньше на ширину бордюров окна, а по высоте она меньше еще и на размер верхней плашки (title bar).

[Свойство ClientSize]

У свойства ClientSize формы есть поля только для чтения - Width и Height, по значению которых можно определить размеры клиентской области формы (ширину и высоту соответственно).

[Метод SetClientSizeCore]

Метод SetClientSizeCore принимает 2 параметра - x и y, и позволяет установить размеры клиентской области формы.

Пример, в котором возникает ошибка (Невозможно получить доступ к нестатическому члену внешнего типа ...):

namespace myprog
{
   public partial class myform : Form
   {
      public int timestamp;
      ...
      public class TChildClass
      {
         public int time;
         public TChildClass()
         {
            time = timestamp;    //ошибка CS0038
         }
      }
      ...
   }
   ...
}

[Варианты решения проблемы]

1. Можно применить к переменной или методу, к которому нельзя получить доступ, атрибут static:

namespace myprog
{
   public partial class myform : Form
   {
      public static int timestamp;  //это устранит ошибку CS0038
      ...
      public class TChildClass
      {
         public int time;
         public TChildClass()
         {
            time = timestamp;
         }
      }
      ...
   }
   ...
}

2. Вместо вложенных классов (nesting) применить наследование (inheritance). Родительский класс должен иметь тип доступа public, чтобы можно было применить наследование:

public class myform : Form
{
    public int timestamp;
    ...
}
 
public class TChildClass : myform
{
   public class TChildClass
   {
      public int time;
      public TChildClass()
      {
         time = timestamp;
      }
   }
}

3. Создать экземпляр класса, который содержит нужную переменную или метод:

namespace myprog
{
   public class TParentClass
   {
      public int timestamp;
      ...
 
      public class TChildClass
      {
         public int time;
         public TChildClass()
         {
            TParentClass pc = new TParentClass();
            time = pc.timestamp;
         }
      }
      ...
   }
   ...
}

При попытке обновить содержимое полосы статуса приложения Windows Forms ничего не происходит. Вот пример кода, который по замыслу должен показывать текст, но на самом деле текст не отображается:

private void textBox1_TextChanged(object sender, EventArgs e)
{
   statusStrip1.Text = "Попытка вывести текст";
   statusStrip1.Refresh();
}

[Решение проблемы

Свойство Text класс StatusStrip получает при наследовании из класса ToolStrip (который в свою очередь наследует его из класса Control). Но у свойства Text класса ToolStrip нет визуального эффекта, просто так почему-то спроектирован класс ToolStrip. Поэтому присваивать значение свойству Text класса StatusStrip бесполезно. Поначалу это может запутать.

Для того, чтобы на самом деле вывести текст в строку статуса StatusStrip, необходимо добавить к StatusStrip элемент StatusLabel. В дизайнере формы это делается следующим образом, см. скриншот: 

StatusStrip add StatusLabel

По умолчанию будет создан элемент класса toolStripStatusLabel1, доступный напрямую из кода формы. Для отображения текста нужно менять его свойство Text, при этом метод Refresh для StatusStrip вызывать не обязательно. Пример:

namespace myApp
{
   public partial class Form1 : Form
   {
      ...
 
      public void msg(string message)
      {
         toolStripStatusLabel1.Text = message;
         logfile.write(message);
      }
   }
}

Решение взято отсюда: Reading/writing an INI file site:stackoverflow.com.

Создайте класс IniFile.cs и добавьте его в проект:

using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
 
// Поменяйте имя MyApp на соответствующее Вашему приложению:
namespace MyApp
{
    class IniFile
    {
        string Path;
        string EXE = Assembly.GetExecutingAssembly().GetName().Name;
 
        [DllImport("kernel32", CharSet = CharSet.Unicode)]
        static extern long WritePrivateProfileString(string Section,
                                                     string Key,
                                                     string Value,
                                                     string FilePath);
 
        [DllImport("kernel32", CharSet = CharSet.Unicode)]
        static extern int GetPrivateProfileString(string Section,
                                                  string Key,
                                                  string Default,
                                                  StringBuilder RetVal,
                                                  int Size,
                                                  string FilePath);
 
        public IniFile(string IniPath = null)
        {
            Path = new FileInfo(IniPath ?? EXE + ".ini").FullName.ToString();
        }
 
        public string Read(string Key, string Section = null)
        {
            var RetVal = new StringBuilder(255);
            GetPrivateProfileString(Section ?? EXE, Key, "", RetVal, 255, Path);
            return RetVal.ToString();
        }
 
        public void Write(string Key, string Value, string Section = null)
        {
            WritePrivateProfileString(Section ?? EXE, Key, Value, Path);
        }
 
        public void DeleteKey(string Key, string Section = null)
        {
            Write(Key, null, Section ?? EXE);
        }
 
        public void DeleteSection(string Section = null)
        {
            Write(null, null, Section ?? EXE);
        }
 
        public bool KeyExists(string Key, string Section = null)
        {
            return Read(Key, Section).Length > 0;
        }
    }
}

[Открытие ini-файла]

Открыть ini-файл можно тремя способами:

// Создаст и загрузит INI-файл в той же папке, где находися исполняемый файл.
// INI-файл получит имя EXE.ini (здесь EXE это имя исполняемого файла до
// расширения *.exe):
var ini = new IniFile();
 
// Или укажите определенное имя в текущей директории:
var ini = new IniFile("Settings.ini");
 
// Или укажите определенное имя в определенной директории:
var ini = new IniFile(@"C:\Settings.ini");

В результате Вы получите объект IniFile, после чего можете воспользоваться его методами Read, Write, KeyExists, DeleteKey, DeleteSection.

[Запись парамеров в ini-файл]

Предположим, нам нужно получить INI-файл такого вида:

[SECTION]
MyPath=c:\temp

Для этого можно использовать следующий код:

ini.Write("MyPath", "c:\\temp", "SECTION");

Если не указывать секцию (последний параметр), то будет автоматически создана секция INI-файла с именем namespace приложения (в данном примере namespace MyApp).

ini.Write("MyPath", "c:\\temp");

[Чтение параметров из ini-файла]

Пример чтения параметра MyPath из секции SECTION:

string strPath = ini.Read("MyPath", "SECTION");

Если не указывать имя секции (последний параметр), то в качестве имени секции будет использоваться имя namespace приложения (в данном примере namespace MyApp).

string strPath = ini.Read("MyPath");

[Проверка существования параметра]

Если параметр MyPath не существует в секции SECTION, то он будет создан и записан:

if(!ini.KeyExists("MyPath", "SECTION"))
{
    ini.Write("MyPath", "c:\\temp", "SECTION");
}

[Удаление параметра]

ini.DeleteKey("MyPath", "SECTION");

[Удаление секции]

ini.DeleteSection("SECTION");

Пусть у нас есть вот такой файл, таблица частот (test.xml):

<?xml version="1.0" encoding="Windows-1251"?>
<config>
# K - частота, Гц
# V - уровень напряжения, V
<OBJECT K="10" V="0.2" />
<OBJECT K="50" V="3.30" />
<OBJECT K="1476" V="12" />
</config>

Ниже показан пример загрузки этого файла в таблицу dataGridView1. Для использования этого кода добавьте в начало модуля директиву using System.Xml.

string fromfile = "xml\\test.xml";
XmlDocument xDoc = new XmlDocument();
try
{
   // Загрузка XML-файла:
   xDoc.Load(fromfile);
   // Получение корневого элемента (в нашем примере это config):
   XmlElement xRoot = xDoc.DocumentElement;
   // Очистка таблицы:
   dataGridView1.Rows.Clear();
   dataGridView1.Refresh();
   // Отображение имени файла на плашке Form1:
   this.Text = fromfile;
   //Заполнение dataGridView1 частотами из XML:
   foreach (XmlNode xnode in xRoot)
   {
      // Цикл по элементам OBJECT, каждый такой элемент соответствует
      // одной строке в таблице:
      if ("OBJECT" == xnode.Name)
      {
         DataGridViewRow row = (DataGridViewRow)dataGridView1.Rows[0].Clone();
         row.HeaderCell.Value = String.Format("{0}", dataGridView1.RowCount);
         // Цикл по столбцам строки, считывание параметров K и V:
         for (int i = 0; i < xnode.Attributes.Count; i++)
         {
            if (i < 2)
               row.Cells[i].Value = xnode.Attributes[i].Value.ToString();
         }
         // Добавление строки:
         dataGridView1.Rows.Add(row);
         dataGridView1.Refresh();
      }
   }
}
catch (Exception ex)
{
   logfile.write("Ошибка " + ex.Message);
}

Полученный результат:

Csharp XML read test

Важное замечание: у метода Load (filename) объекта XmlDocument есть особенность поведения под Windows XP, если в качестве filename передавать не полный, а относительный путь. По крайней мере этот глюк у меня наблюдался на Visual Studio 2010 (.NET Framework версии 4.6.01590 SP1Rel), возможно в последующих версиях это исправлено. Проблема заключается в следующем: в Windows XP первая передача в Load относительного имени файла происходит успешно, но этот каталог запоминается. Все последующие вызовы Load с передачей относительного имени заканчиваются неудачей, потому что к файл ищется по пути, полученному сложением текущего каталога с переданным относительным путем. Под Windows 7 такой проблемы нет, относительные пути всегда отрабатывают без проблем.

Для обхода этой ошибки приходится в метод Load передавать полное имя файла, тогда все нормально работает и под Windows XP, и под Windows 7:

try
{
   // Загрузка XML-файла:
   xDoc.Load(AppDomain.CurrentDomain.BaseDirectory + fromfile);
   ...

ToolStripComboBox это выпадающий список выбора, который можно встроить как элемент меню.

[Как динамически добавлять элементы списка]

ToolStripComboBox MyMenuItemToolStripComboBox = new System.Windows.Forms.ToolStripComboBox();
for (int i = 0; i < 10; i++)
{
   MyMenuItemToolStripComboBox.Items.Add("COM" + i.ToString());
}

[Как запретить редактирование элементов списка]

ToolStripComboBox MyMenuItemToolStripComboBox = new System.Windows.Forms.ToolStripComboBox();
...     //тут могут быть добавлены элементы списка ToolStripComboBox
MyMenuItemToolStripComboBox.DropDownStyle = ComboBoxStyle.DropDownList;

[Текущий элемент списка]

Текущий элемент списка ToolStripComboBox доступен на чтение и запись через свойство SelectedIndex. Нумерация элементов начинается с нуля.

Как проверять выбранный пользователем элемент списка:

private void MyMenuItemToolStripComboBox_Click(object sender, EventArgs e)
{
   string comval = "COM" + MyMenuItemToolStripComboBox.SelectedIndex.ToString();
   ini.Write("port", comval, "DEVICE");
}

Как устанавливать текущий элемент программно:

string port = ini.Read("port", "DEVICE");
for (int i = 0; i < 10; i++)
{
   if (port == ("COM" + i.ToString()))
   {
      MyMenuItemToolStripComboBox.SelectedIndex = i;
      break;
   }
}

1. Добавьте файл, который нужно устанавливать вместе с приложением, в дерево файлов проекта (Обозреватель решений). Для этого можно просто перетащить файл в корневую папку проекта, отображаемую в Обозревателе решений.

2. Измените свойство файла "Действие при построении" в значение "Содержание" (по умолчанию там указано "Нет").

3. Откройте свойства проекта, зайдите в раздел "Публикация". Кликните на кнопку "Файлы приложения...". Убедитесь, что нужный файл был добавлен в список таблицы публикуемых файлов. В строке таблицы этого файла должны быть установлены следующие значения:

Состояние публикации: Включить (Авто)
Группа загрузки: (Требуется)
Хеш: Включить

Так же настраивается публикации папки вместе с содержащимися в ней файлами. Перетащите публикуемую папку в Обозреватель решений и повторите шаги 2 и 3. На шаге 3 следует проверить, что в таблице публикуемых файлов находятся все файлы, содержащиеся в публикуемой папке.

После попытки запуска инсталлятора (setup.exe) приложение не устанавливается, в логе установки выводится сообщение:

Указанная сборка не установлена в системе. (Исключение из HRESULT: 0x800736B3)

или:

Отказано в доступе. (Исключение из HRESULT: 0x80070005 (E_ACCESSDENIED))

Решение проблемы:

1. Удалите папку 2.0, которая находится в профиле пользователя. На Windows 7 и Windows Vista эта папка находится в каталоге C:\Users\имя_пользователя\AppData\Local\Apps\. На Windows XP эта папка находится в каталоге c:\Documents & Settings\имя_пользователя\LocalSettings\Apps\. При удалении папки могут потребоваться права администратора. Если удалить папку не получается, то перезагрузите компьютер, закройте все приложения и с правами администратора повторите попытку удаления папки 2.0.

2. Если после удаления папки 2.0 проблема не исчезла, то запустите с правами администратора команду:

rundll32 dfshim CleanOnlineAppCache

На примере библиотек .NET Ivi.Visa и NationalInstruments.Visa:

"Не удалось найти имя типа или пространства имен "Ivi" (пропущена директива using или ссылка на сборку?)"
"Не удалось найти имя типа или пространства имен "NationalInstruments" (пропущена директива using или ссылка на сборку?)"

Разверните в Обозревателе решений папки проекта, выполните правый клик на папке "Ссылки", выберите "Добавить ссылку...". Откроется окно диалога выбора библиотек. На закладке .NET найдите нужные библиотеки (в нашем примере это Ivi.Visa Assembly и National Instruments VISA, можно выбирать несколько библиотек сразу, удерживая клавишу Ctrl) и кликните OK.

Csharp app add links

В папке "Ссылки" появятся новые библиотеки, и ошибка "Не удалось найти ..." исчезнет.

Откройте свойства проекта, перейдите в раздел Отладка -> Параметры запуска -> в окне "Аргументы командной строки:" укажите нужные аргументы.

Необходимо было в момент запуска программы заполнить выпадающий список ComboBox нужными значениями (имена COM-портов) и потом выбрать из этого списка нужный, чтобы он по умолчанию отображался после полной загрузки программы. Все это я делал в обработчике события загрузки формы Form1_Load. Однако в при попытке присвоить значение свойству SelectedIndex срабатывало исключение типа "System.NullReferenceException": Ссылка на объект не указывает на экземпляр объекта (Object reference not set to an instance of an object).

private void Form1_Load(object sender, EventArgs e)
{
   ...
   MyComboBox.SelectedIndex = idx;  // Ошибка System.NullReferenceException!

Причина ошибки в том, что в момент загрузки формы визуальные элементы еще не полностью инициализированы. Решение проблемы: устанавливать SelectedIndex с помощью вызова события SelectedIndexChanged:

private int desiredidx = -1;
 
private void Form1_Load(object sender, EventArgs e)
{
   ...
   if (-1 != desiredidx)
      MyComboBox_SelectedIndexChanged(MyComboBox, new EventArgs());
   ...
 
private void MyComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
   if (-1 != desiredidx)
   {
      MyComboBox.SelectedIndex = desiredidx;
      desiredidx = -1;
   }
   MyComboBox.Owner.Hide();
}

В состав Visual Studio 2010 входит простой, но иногда довольно неудобный способ для создания дистрибутива (публикации) приложения C#. Это так называемый инструментарий ClickOnce (доступен из меню Построение -> Опубликовать). Такой способ создания дистрибутива всем хорош, но его основное неудобство заключается в том, что невозможно никаким способом задать место установки приложения на компьютере пользователя. Приложение установится автоматически в папку, находящуюся в пределах профиля пользователя (так называемый кэш приложения, Application Cache). Путь до каталога установки приложения получается очень длинным и сложным, на Windows 7 и Windows 10 это будет что-то наподобие C:\ Users\ имя_пользователя\ AppData\ Local\ Apps\ 2.0\ 9HOERDLL.1LY\ KOHZZR9V.QHE\ cali..tion_e314c8db98ca16b5_0001.0000_1b1f3d3fa0feae4c\, или на старых компьютерах с Windows XP это папка c:\ Document and Settings\ имя_пользователя\..

Причина такого решения в том, что решаются многие проблемы безопасности - для установки и работы приложения не требуется наличие прав администратора. Т. е. пользователь с пониженными правами сам может установить, удалить или обновить приложение, вмешательство администратора не требуется. Пользователь с пониженными правами и приложения, работающие от его имени (в том числе трояны и вирусы), никак не могут нарушить содержимое таких системных папок, как %SystemRoot%, %ProgramFiles%, %ProgramFiles(x86)%, %ProgramData% и т. п.

За все надо платить - выигрываем в безопасности и в простоте, проигрываем в прозрачности и в том, чтобы применять совсем другие способы для хранения получения дисковых данных, которые не привязываются к полному пути на диске. К тому же если на компьютере с приложением работает несколько пользователей с одним и тем же приложением, установленным через ClickOnce, то получается несколько копий приложения.

Недостатки ClickOnce следующие:

• При установке очень сложный путь до места установки приложения.
• При обновлении приложения его место расположения на диске постоянно меняется.
• Несколько копий одного и того же приложения на диске, если его установили несколько разных пользователей компьютера.

Таким образом, если Вас не устраивает установка ClickOnce, то нет никакого другого выхода, как инсталляция путем ручного копирования скомпилированного приложения в нужную папку, или создание дистрибутива другим способом, например через создание пакета *.msi (Microsoft Windows Installer).

См. также:

Обзор установки приложения через ClickOnce
• Where do I put my data to keep it safe from ClickOnce updates? site:robindotnet.wordpress.com
• Create Setup and Deployment Project in Visual Studio 2008/2010 site:c-sharpcorner.com
• Deploying WPF Application with ClickOnce Deployment Techniques site:c-sharpcorner.com

[Способ 1, проверка имени закладки]

private void tab1_SelectedIndexChanged(object sender, EventArgs e)
{
   // Здесь tabname это имя проверяемой закладки
   if (tab1.SelectedTab == tab1.TabPages["tabname"])
   {
      // Ваш код
      ...
   }
}

или вот так:

if (tabControl.SelectedTab.Name == "tabname" )
{
   // Ваш код
   ...
}

[Способ 2, проверка свойства SelectedTab]

if (tabControl1.SelectedTab == someTabPage)
{
   // Ваш код
   ...
}

[Способ 3, проверка закладки по индексу]

if (tabControl1.Controls[5] == tabControl1.SelectedTab)
   MessageBox.Show("Выбрана закладка 5");

[Программное переключение закладок]

Закладки можно программно выбирать методом SelectTab, у которого есть 3 перезагрузки:

public void SelectTab(int index);
public void SelectTab(string tabPageName);
public void SelectTab(TabPage tabPage);

Предположим, есть TabControl с именем tabApp, и две закладки tab1 и tab2. При каком-то событии нужно запретить выбор закладки tab1.

1. Сначала нужно установить свойство Enabled у закладки tab1 в значение false.

tab1.Enabled = false;

2. Для tabApp нужно зарегистрировать событие Selecting:

private void tabApp_Selecting(object sender, TabControlCancelEventArgs e)
{
   e.Cancel = !e.TabPage.Enabled;
}

В результате та закладка, у которой свойство Enabled == false, не будет выбрана.

Идею взял отсюда: Автоматическая прокрутка в конец TextBox при добавлении текста site:devnuances.com. Принцип простой - нужно взять длину текста из свойства Text.Length, и затем переставить позицию каретки (курсора ввода) в конец этого текста. Удобнее всего это делать в обработчике события TextChanged экземпляра объекта RichTextBox. Код обработчика:

      private void myTextBox_TextChanged(object sender, EventArgs e)
      {
         myTextBox.SelectionStart = myTextBox.Text.Length;
         myTextBox.ScrollToCaret();
      }

Имейте в виду: чтобы автоматическая прокрутка при добавлении текста работала, TextBox должен получить фокус (можно реализовать вызовом метода Focus). Пример:

      myTextBox.AppendText(tmptxt);
      myTextBox.Focus();

Иногда нужно удалить из конца строки символы возврата каретки (CR, 0x0d, '\r') и перевода строки (LF, 0x0a, '\n'). Проще всего это реализовать с помощью метода TrimEnd. В качестве параметра он принимает список символов, которые нужно удалить в конце строки. Пример:

public void write(params string[] args)
{
   DateTime currtime = DateTime.Now;
   string tmptxt;
 
   tmptxt = String.Format("{0:yyMMdd hh:mm:ss} [{1}] ", currtime, classname);
   for (int i = 0; i < args.Length; i++)
   {
      tmptxt = tmptxt + args[i];
   }
 
   using (System.IO.StreamWriter file = new System.IO.StreamWriter(logfilename, true))
   {
      char[] charsToTrim = {'\r', '\n'};
      file.WriteLine(tmptxt.TrimEnd(charsToTrim));
      file.Close();
   }
}

Пример такого сообщения: 'Не удалось найти тип "UsbLibrary.UsbHidPort". Проверьте, что есть ссылка на сборку, содержащую этот тип. Если этот тип является частью разрабатываемого проекта, убедитесь в том, что проект успешно скомпилирован с использованием параметров вашей текущей платформы для любого процессора.

Содержимое стека и номер строки для этой ошибки недоступны.'

VisualStudio CSharp Form Designer type not found

При этом сам проект нормально компилируется, но открыть форму в режиме визуального конструктора не получается, что создает определенные неудобства.

Чаще всего подобная ошибка происходит при перемещении модулей кода или библиотек в проекте, когда в файле *.pdb осталась ссылка на старое размещение файла на диске. Шаги для устранения ошибки:

1. Сделайте резервную копию всех файлов проекта.

2. Для устранения ошибки чаще всего достаточно удалить файл *.pdb, он содержится в подкаталоге Debug или Release проекта, в зависимости от выбранной конфигурации. Закройте Visual Studio, удалите файл *.pdb, и заново откройте Visual Studio.

3. Если ошибка не исчезла, то удалите в тексте кода формы (обычно это файл Form1.Designer.cs) все ссылки на используемый класс (в данном примере UsbHidPort) и библиотеку (в данном примере UsbLibrary), после чего форма в конструкторе начнет нормально открываться. Затем заново добавьте используемую библиотеку в проект, и добавьте обратно в код формы строчки, которые Вы удалили.

Учет реального времени часто требуется для обслуживания таймаутов, например для процесса обмена данными через USB или COM-порт. Обычно более-менее сложное приложение всегда многопоточное, и поэтому отслеживать прошедшее реальное время простыми вызовами Sleep (см. Q040) не получится.

Есть несколько способов отсчитывать глобальное время. Мне больше всего нравится делать это с помощью тиков операционной системы Windows. В библиотеке C# .NET для этого есть удобное свойство Now.Ticks структуры System.DataTime. Значение Ticks будет содержать глобальное, постоянно увеличивающееся реальное время (64-битное целое число), выраженное в тиках, относительно момента времени 0 часов 0 минут 0 секунд 1 января 0001 года. Каждый тик соответствует 100 наносекунд. Таким образом, чтобы получить абсолютное время в микросекундах, нужно Ticks поделить на 10, а чтобы получить время в миллисекундах, нужно поделить Ticks на 10000. И конечно, абсолютное время в секундах будет равно Ticks/10000000.

Если мы можем в любой момент получить абсолютное текущее время, то отсчет таймаута становится простым. Достаточно запомнить Ticks в момент начала отсчета таймаута, и потом постоянно проверять текущее значение Ticks на разницу с начальным запомненным значением. Как только разница между этими двумя значениями превысит заданный порог, то можно считать, что произошел таймаут.

Можно также использовать другой похожий способ - задать сначала значение счетчика таймаута, и потом в каждой итерации проверки отнимать от счетчика прошедшее абсолютное время. Таймаут истек, когда счетчик дошел до нуля. Ниже приведен простой пример такой обработки таймаута обмена через последовательный порт с помощью DataTime.Ticks в потоке BackgroundWorker.

#region Поток для обработки обмена данными
void bwDoWork(object sender, DoWorkEventArgs e)
{
   BackgroundWorker worker = sender as BackgroundWorker;
   Int64 timeoutms = 0;
   Int64 timestamplast = DateTime.Now.Ticks;
   queryStopWork = false;
   int state = CNST.SENDPACKET;
   
   // Начальная настройка обмена:
   RxReset();
   worker.ReportProgress(state);
   while (!queryStopWork)
   {
      switch (state)
      {
      case CNST.SENDPACKET:
         //Отправка каких-то данных:
         Send();
         //Таймаут отслеживается относительно начала
         //отправки команды. В этом примере отслеживается
         //таймаут в 1 секунду:
         timeoutms = 1000;
         state = CNST.WAITRESPOND;
         worker.ReportProgress(state);
         break;
      case CNST.WAITRESPOND:
         //Ожидание ответа на отправленный пакет:
         if (timeout > 0)
         {
            //Таймаут приема не прошел, продолжаем прием:
            string rx = Received();
            //Тут обработка приянятых даных:
            if (0 != rx.Length)
            {
               ...
               if (rx.Length == PACKET_SIZE)
               {
                  state = CNST.RXDONE;
               }
               worker.ReportProgress(state);
            }
         }
         else
         {
            //Таймаут приема, ошибка:
            state = CNST.ERRTIMEOUT;
            worker.ReportProgress(state);
         }
         break;
      case CNST.ERRTIMEOUT:
      case CNST.RXDONE:
         queryStopWork = true;
         break;
      }
      Thread.Sleep(20);
      //Декремент таймаута, если он установлен:
      Int64 timestampcurr = DateTime.Now.Ticks;
      if (timeout > 0)
      {
         timeout -= (timestampcurr/10000 - timestamplast/10000);
      }
      timestamplast = timestampcurr;
   }
}
 
void bwProgressChanged(object sender, ProgressChangedEventArgs e)
{
   //Тут код, работающий с визуальными компонентами формы приложения.
   ...
}
 
void bwRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
   StripStatus.Text = "Обмен завершен";
}
#endregion

1. Во-первых, для получения возможности обращения к произвольному полю класса формы (ListBox, Button, любой другой элемент графического интерфейса или любые другие поля класса формы) оно должно быть объявлено с ключевым словом public (общий класс доступа). Имейте в виду, что по умолчанию все бросаемые в форму элементы управления получают класс доступа private (ограниченный класс доступа).

Посмотреть определение элементов графического интерфейса можно в файле имя_класса_формы.Designer.cs (например Form1.Designer.cs). Напрямую изменять атрибуты доступа непосредственно в этом файле не рекомендуется, вместо этого выберите свойства нужного компонента интерфейса, и измените визуальным редактором его свойство Modifiers на значение Public (доступны варианты Public, Protected, Protected Internal, Internal, Private).

2. Во-вторых, добавьте в дочерний класс формы поле, которое будет содержать ссылку на родительскую форму. Инициализируйте это поле значением this сразу после создания экземпляра дочернего класса. Назовите поле понятным образом, что-то наподобие ParentForm. Пример, как это делается, см. в [9].

3. Обращайтесь к данным формы из дочернего класса через поле ParentForm.

4. Для обращения к данным формы из стороннего потока (не относящегося к GUI формы) требуется предпринимать специальные меры по обеспечению безопасности доступа к данным со стороны разных потоков. Подробнее см. [9].

Для этого достаточно использовать обычное приведение типа, т. е. добавить к байту префикс (char). При преобразовании будет использоваться кодировка UTF8. Пример преобразования массива байт bytearr в строку символов str:

byte[] bytearr = new byte {'H', 'e', 'l', 'l', 'o', 0};
string str = "";
 
for (int i=0; bytearr[i]!=0; i++)
   str += (char)(bytearr[i]);

Используйте перекодировщик GetEncoding. Пример преобразования массива байт, где закодирована строка в кодировке ANSI, в строку в кодировке UTF-8:

string Decode(byte[] data)
{
   return Encoding.GetEncoding(1251).GetString(data);
}

Пример запуска командной строки PowerShell:

System.Diagnostics.Process pProcess = new System.Diagnostics.Process();
//Командная строка, в данном примере запускается интерпретатор Power Shell Windows:
pProcess.StartInfo.FileName = @"powershell";
//Аргументы командной строки (просмотр текстового лога):
pProcess.StartInfo.Arguments = "Get-Content logfile.log -encoding UTF8 -Wait";
//pProcess.StartInfo.UseShellExecute = false;
//pProcess.StartInfo.RedirectStandardOutput = true;
//pProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
//pProcess.StartInfo.CreateNoWindow = true;  //позволяет не отображать окно
pProcess.Start();

Пример запуска браузера Chrome для открытия файла HTML:

using System.Diagnostics;     // для класса Process
...
 
Process процесс = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = "chrome.exe";
startInfo.Arguments = "Отчет.html";
процесс.StartInfo = startInfo;
процесс.Start();

См. также How do I start a process from C#? site:stackoverflow.com.

Для использования этого кода не забудьте добавить в начало файла модуля using System.IO;.

//Параметр, передаваемый в конструктор FileInfo, должен содержать путь
// до имени очищаемого файла. Он может быть как полным, так и
// относительным, если файл находится в текущем каталоге приложения:
FileInfo fi = new FileInfo("c:/temp/mylog.txt");
using (TextWriter txtWriter = new StreamWriter(fi.Open(FileMode.Truncate)))
{
   txtWriter.Write("");
   txtWriter.Close();
}

Метод RunWorkerAsync для запуска BackgroundWorker может быть вызван без параметров, тогда в тело потока DoWork никакие параметры не будут переданы. Но у метода RunWorkerAsync также есть перезагрузка, которая может принять в качестве аргумента object (т. е. переменную любого типа). Таким образом можно передать в поток любую переменную: int, string или даже пользовательский тип.

Пример запуска потока с передачей в него строки:

//Создание экземпляра потока и настройка его параметров:
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += new DoWorkEventHandler(bwDoWork);
//Необязательные обработчики прогресса и завершения потока (их можно не добавлять):
bw.ProgressChanged += new ProgressChangedEventHandler(bwProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwRunWorkerCompleted);
...
 
//Запуск потока с передачей в него параметра:
string mystr = "bla-bla";
bw.RunWorkerAsync(mystr);
...

Пример получения в теле потока этой переданной строки:

void bwDoWork(object sender, DoWorkEventArgs e)
{
   //Строка argstr получит значение "bla-bla":
   string argstr = (string)e.Argument;
   logfile.write("Поток bw стартовал с аргументом " + argstr);
   while(true)
   {
      ...
      Thread.Sleep(100);
   }
}

Несколько параметров можно передать в поток через массив или пользовательский тип (класс или структуру).

См. также Sending Arguments To Background Worker? site:stackoverflow.com.

Такая проблема обычно возникает в момент закрытия основной формы, т. е. в момент выхода из программы по желанию пользователя (когда он к примеру нажал Alt+F4). В этот момент во избежания разных ошибок нужно корректно завершить все потоки, или дождаться завершения одного их цикла. Конечно, это можно осуществить с помощью установки и проверки глобальных флагов, но однако, как это ни странно, проще всего воспользоваться свойством WorkerSupportsCancellation и методом CancelAsync(), которые доступны у класса потока BackgroundWorker.

Свойство WorkerSupportsCancellation по умолчанию находится в значении false, т. е. поток не поддерживает асинхронное завершение с помощью вызова CancelAsync(). Чтобы можно было в любой момент завершить поток вызовом CancelAsync(), установите в true свойство WorkerSupportsCancellation. Это можно сделать либо в визуальном редакторе (если Вы создали объект BackgroundWorker перетаскиванием его на форму), либо в тексте программы (если Вы создаете объект BackgroundWorker динамически, во время работы программы). Ниже приведен пример создания динамического создания потока (для упрощения код обработчиков не приводится).

BackgroundWorker bw = new BackgroundWorker();
//Поток будет поддерживать асинхронное завершение:
bw.WorkerSupportsCancellation = true;
//Поток будет поддерживать взаимодействие с GUI программы:
bw.WorkerReportsProgress = true;
//Добавление тела потока:
bw.DoWork += new DoWorkEventHandler(bwDoWork);
//Добавление обработчика события прогресса:
bw.ProgressChanged += new ProgressChangedEventHandler(bwProgressChanged);
//Добавление обработчика события завершения потока:
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwRunWorkerCompleted);

Теперь в момент закрытия основной формы можно просто вызвать метод CancelAsync(), и поток завершится автоматически, не нужно дожидаться его завершения:

void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
   bw.CancelAsync();
   logfile.write("[EXIT]");
}

Рассмотрим простой пример, когда может происходить подобная ситуация.

public void Init()
{
   bw = new BackgroundWorker();
   bw.WorkerSupportsCancellation = true;
   bw.DoWork += new DoWorkEventHandler(bw_DoWork);
   bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
 
void bw_DoWork(object sender, DoWorkEventArgs e)
{
   if (bw.CancellationPending == true)
   {
      e.Cancel = true;
   }
   else
   {
      e.Result = abd();
   }
}
 
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
   if(e.Cancelled)
   {
      lbltext.content="Canceled";
   }
   else
   {
      lbltext.content="Completed";
   }
}
 
private void btncan_Click(object sender, RoutedEventArgs e)
{
   bw.CancelAsync();
}

private void btnstart_Click(object sender, RoutedEventArgs e)
{
    bw.RunWorkerAsync();
}

В этом примере медленная функция abd() выполняет некую фоновую работу в потоке, и есть необходимость быстрого завершения её работы. Предполагалось, что это было бы можно вызовом bw.CancelAsync(), но на самом деле ничего подобного не происходит. Почему?

Когда Вы вызвали bw.CancelAsync(), то этим просто установили в true флаг bw.CancellationPending. По умолчанию это ничего не завершает. Вам нужно обработать завершение вручную. Но Вы не можете просто так сделать это в своем коде, потому что при клике на кнопку в интерфейсе GUI, когда нужно завершить поток, возможны 3 случая:

• Работает какая-то медленная функция внутри цикла потока, и по этой причине завершение не произойдет.
• Медленная функция начала свою работу, и поток этим заблокировался. Он ждет завершения этой функции, и продолжает свое выполнение. Если в циклах функции будет осуществляться проверка флага CancellationPending с целью ускоренного завершения работы, то таким способом можно быстро завершить работу потока и вызвать тем самым событие RunWorkerCompleted.
• Довольно мало вероятный случай - Вы были быстры как метеор, и кликнули на кнопку завершения поток перед входом в блок проверки if-else, перед запуском медленной функции. Тогда флажок CancellationPending установится в true, и медленная функция не начнет свою работу, поток нормально завершится.

Если Вы хотите использовать асинхронное завершение потока, то когда делаете запуск в цикле какого-то медленного вычисления, разбейте эти вычисления на отдельные повторяющиеся итерации цикла, и проверяйте флаг завершения на каждой итерации. Например:

void bw_DoWork(object sender, DoWorkEventArgs e)
{
   List< Foo > results = new List< Foo >();
   // В этом месте может быть любой цикл - foreach, while
   for(int i = 0; i < steps_count; i++)
   {
      // Проверка статуса перед каждой итерацией.
      if (bw.CancellationPending == true) 
      {
         e.Cancel = true;
         return;    // Остановка работы, если было запрошено завершение потока
      }
      results.Add(abd());  // добавление результатов вычислений
   }
   e.Result = results;     // возврат всех результатов
}

Расширение *.pdb произошло от сокращения Program DataBase, т. е. база данных программы. Она создается при компиляции приложения, и используется для отладки и хранения информации о состоянии проекта. Этот файл по умолчанию создается как для конфигурации Debug, так и для конфигурации Release. Для конфигурации Release размер этого файла обычно немного меньше. Если Вы не хотите создавать этот файл (например, когда для конфигурации Release он не нужен), откройте свойства проекта, перейдите в раздел настроек Построение, кликните на кнопку Дополнительно... (Build -> Advanced...), и в выпадающем списке "Отладочная информация:" (Debug Info:) выберите none.

VisualStudio disable PDB file

Из-за установок библиотек различных версий может появиться предупреждение примерно такого вида (отображается в окне результатов компиляции "Вывод"):

ResolveAssemblyReferences:
  Будет создан список исключений профиля TargetFramework.
C:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(1605,5): warning MSB3247:
Обнаружены конфликты между различными версиями одной и той же зависимой сборки.

Для устранения конфликта нужно получить больше информации о причине, для этого поменяйте уровень выдачи сообщений сборки проекта на "Подробный" (Detailed). 

1. Откройте диалог Options (Tools -> Options... или Сервис -> Параметры...).

2. В левой части окна находится дерево параметров, раскройте узел Projects and Solutions (Проекты и решения), после чего выберите раздел настроек Build and Run (Построение и запуск).

3. Установите в выпадающем списке MSBuild project build output verbosity (Степень подробности сообщений при построении проекта MSBuild) в значение "Подробные" (по умолчанию там задано "Обычные"). Это делается по-разному в зависимости от версии Visual Studio:

   Для VS2012, VS2013 или VS2015 установите "Diagnostics".
   Для VS2010 "Detailed" ("Подробные").
   Для VS2008 или более старых версий Normal.

4. Пересоберите проект, чтобы увидеть подробный лог сборки. 

Проанализируйте сообщений MSBuild. Сообщение ResolveAssemblyReferences, которое относится к генерации предупреждения MSB3247, должно помочь Вам понять причину проблемы. 

В моем конкретном случае причина была в SDK старой версии, который был установлен после Visual Studio. Переустановкой Visual Studio проблема была решена. 

Для диагностики можно также использовать следующее: 

• Просмотрите свойства (Properties) каждой ссылки на библиотеку. Все ссылки, используемые в проекте, находятся в папке Ссылки (References).
• Откройте свойства проекта и проверьте версии в секции Ссылки (References).
• Откройте файл проекта с помощью текстового редактора.
• Используйте .Net Reflector.
• Консольная утилита с открытым исходным кодом AsmSpy (а также AsmSpyPlus с графическим интерфейсом, доступная на GitHub) поможет разобраться с конфликтом версий сборок.

Ошибка проекта winusb_cs в Visual Studio 2010:

error MSB3323: Не удалось найти сертификат подписи манифеста в хранилище сертификатов.

Ошибка обычно возникает, когда Вы переименовали проект, подписаный сертификатом, или когда компилируете чужой, подписанный сертификатом проект. Как исправить: откройте свойства проекта, перейдите в раздел "Подписывание" (Signing) и снимите галочку с опции "Подписать манифесты ClickOnce" (Sign the ClickOnce manifests).

После установки Visual Studio 2019 Community на Windows 7 x64 система потребовала перезагрузки (она в процессе установки тихой сапой поставила обновления, никого не спросясь). Перезагрузился, при старте системы долго висел черный графический экран, после чего система позволила сделать логин. И оказалось, что все программы перестали запускаться с выдачей ошибки 0xC0000005. Повторная перезагрузка не помогла.

Гугление показало, что не я первый сталкиваюсь с этой проблемой. Помогла ссылка [11]. Суть в том, что нужно удалить обновления, запустив следующие команды в консоли с правами администратора:

wusa.exe /uninstall /kb:2859537
wusa.exe /uninstall /kb:2872339
wusa.exe /uninstall /kb:2882822

В моем случае глючным было третье обновление kb:2882822, потому что первые 2 почему-то не были установлены (об этом сообщила утилита wusa.exe).

Что интересно - после удаления этих обновлений и перезагрузки проблема не только исчезла, но и Visual Studio 2019 Community нормально заработала.

Чтобы этого не происходило, необходимо вставить в тело цикла (который находится в DoWork) вызовы Sleep, чтобы дать время потоку формы, который обрабатывает работу визуальных компонентов GUI:

System.Threading.Thread.Sleep(1);

В качестве параметра Sleep принимает длительность в миллисекундах.

String ИмяФайлаИзДаты()
{
   DateTime currtime = DateTime.Now;
   return String.Format("{0:yyMMdd-hh-mm-ss}тест.xlsx", currtime);
}

Результат вычитания между двумя значениями DateTime автоматически получает тип TimeSpan. Для преобразования TimeSpan в строку используют его метод ToString со строкой формата. Пример:

TimeSpan Длительность = ВремяКонцаТеста - ВремяНачалаТеста;
СтрокаСтатуса.Text = "Время теста " + Длительность.ToString(@"hh\:mm\:ss");

Для отображения часов в 12-часовом формате надо указывать для часов формат hh, а для отображения в 24-часовом формате формат HH.

String ДекодироватьВремя(DateTime время)
{
   // Отображение времени в 12-часовом формате:
   //return время.ToString("hh:mm:ss");
   // Отображение времени в 23-часовом формате:
   return время.ToString("HH:mm:ss");
}

Пример заполнения в коде загрузки формы Form1_Load списка меню именами COM-портов в системе, с привязкой к элементу (пункту) меню обработчика клика МенюПортКлик:

void МенюПортКлик(object sender, EventArgs e)
{
   foreach (ToolStripMenuItem mi in МенюПорт.DropDownItems)
      mi.Checked = false;
   ToolStripMenuItem ПунктМенюГдеГалочка = (ToolStripMenuItem)sender;
   ПунктМенюГдеГалочка.Checked = true;
   serialport.Open(ПунктМенюГдеГалочка.Text);
   if (serialport.IsOpen)
      СтрокаСтатуса.Text = "Порт " + ПунктМенюГдеГалочка.Text + " свободен";
   else
      СтрокаСтатуса.Text = "Порт " + ПунктМенюГдеГалочка.Text + " занят";
}

private void Form1_Load(object sender, EventArgs e) { // Расстановка элементов формы: panel1.Top = menuStrip1.Location.Y + menuStrip1.Size.Height; panel2.Top = panel1.Bottom; panel2.Height = ClientSize.Height - (menuStrip1.Size.Height + panel1.Height + СтрокаСтатуса.Height); ...
// Построение списка портов: string[] ports = SerialPort.GetPortNames(); int itemidx = 0; foreach (string portname in ports) { logfile.write(portname); МенюПорт.DropDownItems.Add(portname); МенюПорт.DropDownItems[itemidx].Click += МенюПортКлик; itemidx++; } ... }

См. предыдущий вопрос "Q097. DropDownItems: как программно заполнить элементы меню?".

После оператора cath нужно убрать пустые скобки.

1. Сначала надо переопределить конструктор отображаемой второй формы, чтобы он принимал два параметра - верхнюю и нижнюю позиции формы (T и L). Также надо определить две переменные, где будут храниться эти позиции (vTop и vLeft).

2. В обработчик события Shown формы надо добавить код, модифицирующий поля Left и Top формы новыми координатами, которые были переданы в конструкторе.

Пример кода класса формы для отображения лога:

using System;
using System.Windows.Forms;
 
namespace myprogram
{
   public partial class FormLog : Form
   {
      private int vLeft, vTop;
 
      public FormLog(int L, int T)
      {
         InitializeComponent();
         vLeft = L;
         vTop = T;
      }
 
      private void FormLog_Shown(object sender, EventArgs e)
      {
         Left = vLeft;
         Top = vTop;
      }
   }
}

См. также Q018.

Такая ошибка у меня возникла при попытке обновить ListBox в событии OnChanged экземпляра класса FileSystemWatcher.

...
watcher = new FileSystemWatcher();
watcher.Path = ".";//watcher.Filter = "*.txt";
watcher.Filter = "logapp.txt";
watcher.NotifyFilter = NotifyFilters.Size;
watcher.Changed += OnChanged;
watcher.EnableRaisingEvents = true;
...
 
private void OnChanged(object source, FileSystemEventArgs e)
{
   // Здесь по аргументу e можно определить, что произошло:
   // был ли файл изменен, создан или удален.
   listBox1.Items.Add(String.Format("File: {0} {1}",
                                    e.FullPath, e.ChangeType));
}

Csharp InvokeRequired MethodInvoker delegate

Проблему можно решить, если применить делегата и проверку InvokeRequired на том элементе управления, к которому нужен доступ из другого потока (в этом примере listBox1):

private void OnChanged(object source, FileSystemEventArgs e)
{
   if (listBox1.InvokeRequired)
   {
      MethodInvoker mi = delegate
      {
         OnChanged(source, e);
      };
      listBox1.Invoke(mi);
   }
   else
   {
      listBox1.Items.Add(String.Format("File: {0} {1}",
                                       e.FullPath, e.ChangeType));
   }
}

См. также [9, 10], Q008.

Попытка завершить поток потерпела неудачу - поток не находится в теле цикла DoWork, вызов метода CancelAsync, даже ожидание завершения потока не помогает - IsBusy всегда установлено в true.

...
if (bw.IsBusy)
{
   bw.CancelAsync();
   while (bw.IsBusy)
      Thread.Sleep(10);   // бесконечное зацикливание...
}

В чем причина, как исправить проблему?

Для завершения потока BackGroundWorker должно отработать событие RunWorkerCompleted оно не запустится, если не дать возможность приложению обработать события. Для этого в цикле ожидания надо вызвать Application.DoEvents():

...
if (bw.IsBusy)
{
   bw.CancelAsync();
   while (bw.IsBusy)
      Application.DoEvents():
}

См. также Q086, Q087.

В классе TabControl закладки представлены коллекцией экземпляров класса TabPage. У класса TabPage свойство Visible появилось, начиная с библиотеки .NET версии 5.0. Для более старых версий, чтобы скрыть закладку TabPage, можно использовать следующий трюк (myTab это экземпляр класса TabPage, а myTabCtrl это экзмпляр его родительского класса TabControl):

myTab.Parent = null;       // скрыть закладку myTab
myTab.Parent = myTabCtrl;  // отобразить закладку myTab 

[Ссылки]

1. ASP.NET and Other Tips.
2. C#: работа с USB-CAN адаптером SYSTEC.
3. C#: обзор ComboBox.
4: C#: методы Show и ShowDialog.
5. C#: желтые всплывающие подсказки (hint).
6. C#: как узнать, что пользователь кликнул на кнопку 'X' формы?
7. Ключевое слово static на языке C#.
8. Обзор установки приложения через ClickOnce.
9. C#: зачем нужны InvokeRequired и Invoke?
10Идеология C#: события и делегаты.
11. Ошибка 0xc0000005 Не запускаются программы в Windows 7 site:geekelectronics.org.