воскресенье, 18 декабря 2011 г.

DisplayText для ComboBox элементов в WinForms

Многие стандартные контролы в WinForms при отображении списков элементов объектов (контролы типа ComboBox), используют возвращаемое этими объектами значение метода ToString(). Вообще, использование метода ToString() в этих целях является стандартным подходом и используется также в других библиотеках WinForms, например в DevExpress контролах.

Интересно, сколько различных способов существует для придания спискам объектов нужного представления. Кто-то наследуется от класса, который нужно отобразить, и переопределяет его метод ToString(). Иногда, что на мой вгляд хуже всего, пытаются в классах модели определить их представление и уже в них самих переопределяют ToString(). Кто-то использует словари (Dictionary) для сопоставления отображаемого текста с объектами. Можно использовать Binding.

Есть еще парочка известных мне методов, но я предпочитаю варант предсталенный ниже. Это некоторая общая обертка (Generic Wrapper), которая содержит всего два свойства: объект и его желаемое отображаемое значение:

class DisplayItem {
     public DisplayItem(T value, String dispText) {
         Value = value;
         DisplayText = dispText;
     }

     public T Value { get; set; }
     public String DisplayText { get; set; }

     public override string ToString() {
         return DisplayText ;
     }
}

среда, 11 мая 2011 г.

Пустое содержимое в FolderBrowserDialog

Во время создания пользовательского окна в проекте установки (Visual Studio, Setup-Project) у меня возникли проблемы при отображении содержимого окна FolderBrowserDialog:

Быстрый поиск по инету указал на то, что пользоваться этим окно можно лишь в STA-потоке. Обычно этот параметер задается в начальной точке приложения в атрибуте STAThread:
[STAThread] static void Main() { ... }.

Но как указать модель STA в приложении, в котором не владеешь стартовой точкой, в таком как Setup-проект? Использовал следующий трюк, где я вызываю FolderBrowserDialog в новом потоке, для которого задаю STA-модель перед его старом. Сработало.

// Обработчик события кнопки выбора пути
private void dbPathButton_Click(object sender, EventArgs e) {

// Нормальный подход, но не работает
// FolderBrowserDialog fbd = new FolderBrowserDialog();
// fbd.SelectedPath = dbPathTextBox.Text;
// if (fbd.ShowDialog(this) == System.Windows.Forms.DialogResult.OK)
// dbPathTextBox.Text = fbd.SelectedPath;

// Пришлось делать так:
pathBuff = dbPathTextBox.Text;
Thread thr = new Thread(ShowFolderBrowserDialog);
thr.SetApartmentState(ApartmentState.STA);
thr.Start();
thr.Join();
dbPathTextBox.Text = pathBuff;
}

String pathBuff = String.Empty;

public void ShowFolderBrowserDialog() {
FolderBrowserDialog fbd = new FolderBrowserDialog();
fbd.SelectedPath = pathBuff;
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
pathBuff = fbd.SelectedPath;
}

среда, 2 февраля 2011 г.

ToString Or Empty If Null. Строковое значение объекта. Маленькие хитрости

Недавно мне понадобилось получать строковые значения некоторых объектов, т.е. нужно было получать результат вызова метода ToString(). Если объект нулевой (null) нужно было получать пустую строку. Задача осложнялась тем, что таких объектов было много. А в некоторых случаях нужно было получить строковое значение членов объектов (свойств), которые, в свою очередь, также могли быть нулевыми.

Но буду последователен и посмотрим, как бы решалась задача для простого типа, например String:
String obj = "value";
// ..
String str = (obj == null) ? String.Empty : obj.ToString();


* This source code was highlighted with Source Code Highlighter.

Как уже было сказано, проверок на null оказалось слишком много, так что код начал терять читабельность. Пришлось вводить метод-расширение. За счет этого удалось сократить код и повысить его восприятие:
String obj = "value";
// ..
String str = obj.xStringOrEmptyIfNull();


* This source code was highlighted with Source Code Highlighter.

Метод-расширение выглядит так:
public static class ExtensionsString
{
  public static String xStringOrEmptyIfNull(this Object obj) {
    return (obj != null) ? obj.ToString() : String.Empty;
  }
}


* This source code was highlighted with Source Code Highlighter.

Все очень просто, но не забываем про вторую часть задачи, где нужно получать строковое значение свойства объекта. Вот классы для примера:
public class Person
{
  public String Name { get; set; }
  public Language Language { get; set; }
}

public class Language
{
  public String LangName { get; set; }
  public int Level { get; set; }
}


* This source code was highlighted with Source Code Highlighter.

Имея объект класса Person нужно определить название языка, либо получить пустую строку, если свойство язык равно нулю. Решение в лоб:
String lang = (p.Language != null) ? p.Language.LangName.xStringOrEmptyIfNull() : String.Empty;

* This source code was highlighted with Source Code Highlighter.

Выглядит не очень, и это несмотря на использование метода-расширения, существенно сокращающего код. Так вот, мне удалось сократить код до слудующей конструкции:
String lang = p.Language.xStringOrEmptyIfNull(x => x.LangName);

* This source code was highlighted with Source Code Highlighter.

Уже намного лучше. Вот так выглядит метод-расширение:
public static String xStringOrEmptyIfNull<T>(this T t, Func<T, Object> del) {
  return (t == null) ? String.Empty : del(t).xStringOrEmptyIfNull();
}


* This source code was highlighted with Source Code Highlighter.

Весь секрет в делегатае Func<..>. Кто еще не очень с ним знаком, советую на него более пристально посмотреть. Вся магия LINQ построена на нем и я сам иногда прибегаю к его использованию, например, так как это сделано в примере выше.