Как запретить прокрутку в ComboBox, когда курсор находится над ним и при этом продолжать
прокручивать главную форму. Дело в том, что при выборе элементов у меня создаются метки
и это не очень удобно, когда при прокрутке основного контента курсор мыши случайно
попадает на ComboBox.
private void comboBox1_MouseWheel(object sender, MouseEventArgs e)
{
((HandledMouseEventArgs)e).Handled = true;
// Прокручивать главную форму...
}
Здравствуйте. Столкнулся с проблемой. Делаю приложение на WPF с использованием паттерна
MVVM. Работу с базой данных осуществляю с помощью Entity Framework. Есть 2 таблицы:
Сотрудники(ФИО, id должности) и Должности(id, название). Я хочу, чтобы на форме с сотрудниками,
когда я выбираю одного из них, выводилась вся информация о нём (ФИО, должность и тд).
Это я осуществил, создав класс EmployeesViewModel:
class EmployeesViewModel : INotifyPropertyChanged
{
OpticsEntities db;
private Employee selectedEmployees;
public ObservableCollection Employees { get; set; }
public Employee SelectedEmployees
{
get { return selectedEmployees; }
set
{
selectedEmployees = value;
OnPropertyChanged("SelectedEmployees");
}
}
public EmployeesViewModel()
{
db = new OpticsEntities();
db.Employees.Load();
Employees = db.Employees.Local;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string prop = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
private RelayCommand addCommand;
private RelayCommand saveCommand;
private RelayCommand removeCommand;
private RelayCommand changeCommand;
public RelayCommand AddCommand
{
get
{
return addCommand ??
(addCommand = new RelayCommand(obj =>
{
Employee employees = new Employee();
Employees.Insert(0, employees);
SelectedEmployees = employees;
}));
}
}
public RelayCommand SaveCommand
{
get
{
return saveCommand ??
(saveCommand = new RelayCommand(obj =>
{
Employee employees = obj as Employee;
if (employees != null)
{
db.Employees.Add(employees);
db.SaveChanges();
}
}));
}
}
public RelayCommand ChangeCommand
{
get
{
return changeCommand ??
(changeCommand = new RelayCommand(obj =>
{
Employee employees = obj as Employee;
if (employees != null)
{
var changeEmployees = db.Employees.Find(employees.id);
employees = changeEmployees;
db.SaveChanges();
}
}));
}
}
}
и представление:
Всё отлично работает. Можно добавить нового сотрудника и сохранить.
Но проблема вот в чем. Поле "Должность" связано с таблицей "Должности" по внешнему
ключу (id). И я хочу, чтобы вместо текстового поля с id должности был ComboBox, в котором
бы находились названия всех должностей из таблицы "Должности". По умолчанию в нем была
бы та должность, которой соответствует сотрудник, но можно было бы открыть ComboBox
и присвоить ему другую. После нажатия на Save всё сохранилось бы в базе.
Максимум, что я смог сделать, это создать ComboBox, в котором будут отображаться
эти должности. Но для этого пришлось сделать еще одну коллекцию во ViewModel и обращаться
уже к ней в ItemsSource. Естественно, что бы я в ней не выбрал, никакой связи с сотрудником
не будет, так как данные не связаны получается.
Как мне сделать, чтобы в ComboBox отображались должности из таблицы Должности, и
при выборе одной из них она бы присваивалась полю Должность у Сотрудника? Напомню,
что таблицы связаны внешним ключом по id должности.
Ответы
Ответ 1
Тут достаточно много вариантов реализации, красивых и не очень, но если попробовать
сделать с участием дополнительной коллекции моделей представления для каждой должности,
как вы описали.
Пример искусственный, написан для демонстрации подхода к использованию тонкостей
привязки ComboBox к данным, ни в коем случае не является примером реализации правильного
MVVM.
Модели сущностей предметной области:
public class Employee
{
public string Name { get; set; }
public int PostID { get; set; }
}
public class Post
{
public int ID { get; set; }
public string Name { get; set; }
}
Модели представления:
public class EmployeeViewModel : NotificationObject
{
public EmployeeViewModel(Employee model, IEnumerable posts)
{
Model = model;
var postViewModels = new List();
foreach (var post in posts)
{
postViewModels.Add(new PostViewModel(post));
}
PostViewModels = postViewModels;
}
public Employee Model { get; set; }
public IEnumerable PostViewModels { get; private set; }
}
public class PostViewModel : NotificationObject
{
public PostViewModel(Post model)
{
Model = model;
}
public Post Model { get; set; }
}
Класс NotificationObject просто реализует INotifyPropertyChanged. В DataContext окна
попадает EmployeeViewModel.
Разметка:
Пример работающий, в Combobox будут отображаться названия должностей, при этом в
объект Employee при выборе в ComboBox будет присваиваться валидный идентификатор должности.
У ComboBox есть несколько крайне полезных свойств.
DisplayMemberPath - путь к свойству сущности в DataContext элемента списка ComboBox,
который будет отображаться в выпадающем списке и в основном теле ComboBox.
SelectedValue - наравне с SelectedItem можно биндиться к свойству SelectedValue,
значение которого берётся по определённому пути из SelectedItem.
SelectedValuePath - путь от SelectedItem, по которому берётся SelectedValue.
В моём примере при выборе в ComboBox в его свойство SelectedItem попадает PostViewModel,
а в свойство SelectedValue попадает уже искомый идентификатор должности. Это свойство
и забиндено на PostID сущности Employee.
Надеюсь, принцип понятен и удастся перенести это на ваш случай.
Как сделать поиск массиве и отображение в компоненте ComboBox? По примеру поисковика
Google.
Код:
private void comboBox1_KeyDown(object sender, KeyEventArgs e)
{
comboBox1.DataSource = null;
comboBox1.Items.Clear();
if ((e.KeyValue >= 34 && e.KeyValue <= 175))
{
string[] WRT1 = new string[] { "27", "Alex", "Gulynin1", "новый","сон"};
string[] arr = new string[WRT1.Length];
string str;
if (comboBox1.Text != "")
{
for (int i = 0; i < WRT1.Length; i++)
{
str = WRT1[i];
if (str.IndexOf(comboBox1.Text) != -1)
{
comboBox1.Items.Add(WRT1[i]);
}
}
}
}
}
Сейчас код не работает:
только со второго символа находит;
при нажатии клавиши отзеркаливается текст
Ответы
Ответ 1
Это называется автодополнение.
Автодополнение из элементов списка
Самый простой вариант: если нужно, чтобы так выбирался один из элементов списка.
Его можно реализовать так:
Заполнить элементы списка Items:
Это можно сделать как через конструктор так и через код
comboBox1.Items.AddRange(new object[] {
"27",
"Alex",
"Gulynin1",
"новый",
"сон"
});
Установить свойству AutoCompleteMode значение SuggestsAppend (предлагать и дополнять),
а свойству AutoCompleteSourse значение ListItems (выбирать из элементов списка).
comboBox1.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend;
comboBox1.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.ListItems;
В результате получим:
Автодополнение из другого списка
Если нужно, чтобы в самом выпадающем списке был один набор элементов, а в автодополнении
другой, то можете воспользоваться свойством AutoCompleteSource. Такое поведение может
понадобится если Ваш список поддерживает добавление новых элементов, например.
comboBox1.AutoCompleteCustomSource.AddRange(new string[] {
"27",
"Alex",
"Gulynin1",
"новый",
"сон"});
//и заполняем соответствующие свойства
comboBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
comboBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
В результате получится похожее поведение:
Добавление в список
Теперь можно по нажатию Enter добавлять выбранные элементы в выпадающий список ComboBox:
private void comboBox1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
var newString = comboBox1.Text;
if (!comboBox1.Items.Contains(newString))
{
comboBox1.BeginUpdate();
comboBox1.Items.Add(newString);
comboBox1.EndUpdate();
}
}
}
Ошибки
только со второго символа находит;
Это происходит потому что событие KeyDown выполняется сразу при нажатии на клавишу
(например, A) еще до того как обновился текст. Т.о. в обработчике событий при нажатии
«A» текст еще пустой и это условие не срабатывает:
if (comboBox1.Text != "")
Для того чтобы это исправить можете вместо события KeyDown обрабатывать событие TextChanged
и уже не обрабатывать клавиши, а сразу действовать в зависимости от набранного текста.
при нажатии клавиши отзеркаливается текст
Это происходит из-за того что при очистке списка
comboBox1.Items.Clear();
курсор автоматически устанавливается в начало строки. Этого можно избежать если для
очистки использовать следующий метод (источник):
public static void SafeClearItems(ComboBox comboBox)
{
foreach (var item in comboBox.Items.Cast
Имеется ComboBox, который хранит месяца года в текстовом формате. Как можно записать
номер месяца в DataTime?
Там где происходит добавление месяцев в список, нужный мне номер месяца сохранить
сразу не могу, так как нужен выбор пользователя.
for (int i = 1; i <= 12; i++)
{
cmbMonth.Items.Add(now.ToString("MMMM"));
now = now.AddMonths(1);
cmbMonth.Text = DateTime.Now.ToString("MMMM");
}
Нужно из cmbMonth.Text получить название месяца, что бы записать полноценную дату
выбранную пользователем. Но так-как месяца хранятся в виде текста, возникли проблемы.
Как можно решить? (Писать цикл с месяцами и их номерами не вариант, так как текст месяца
в ComboBox зависит от языка системы)
Ответы
Ответ 1
Для перевода из String в DataTime есть пара хороших методов Parse и ParseExact. Вот
второй и подойдет для данной задачи!
Суть следующая - Вы ему даете 3 аргумента:
string, где есть значения связанные со временем (будь то время, дата, месяц или еще что).
Некая маска, по которой метод ParseExact поймет, что находится в пункте 1.
Культура пользователя или указанная заранее.
Напишу пример к вашей задаче:
var m = "Декабрь";
DateTime dd = DateTime.ParseExact(m, "MMMM", System.Globalization.CultureInfo.CurrentCulture);
Console.WriteLine(dd);
Как видим я задал 3 параметра - месяц (декабрь), маска (полный формат месяца) и культура
(текущая культура пользователя).
Результатом программа выдаст:
01.12.2018 0:00:00
Надеюсь это решит вашу проблему. Удачи!
Ответ 2
Небольшой пример работы с комбобоксом, где в качестве элемента будет не строка, а
наш класс.
class MyForm : Form
{
public MyForm()
{
var cb = new ComboBox();
var currMonth = DateTime.Now.AddDays(-DateTime.Now.Day + 1);
for (var i = 0; i < 12; i++)
{
var dt = currMonth.AddMonths(i);
cb.Items.Add(new MonthItem { Id = dt.Month, Text = dt.ToString("MMMM") });
}
cb.DisplayMember = "Text";
cb.SelectedIndexChanged += (sender, args) =>
{
var m = (cb.SelectedItem as MonthItem);
if (m != null)
{
MessageBox.Show($"Selected {m.Text} with ID = {m.Id}");
}
};
this.Controls.Add(cb);
}
private class MonthItem
{
public int Id { get; set;}
public string Text { get; set;}
}
}
И как результат
Ответ 3
Вероятно, лучше хранить в комбобоксе непосредственно значения DateTime. При этом
настроить формат комбобокса для отображения в желаемом виде.
Значение cmbKuu.Text достаточно установить один раз, вынеся его из цикла. Кроме того,
следует предусмотреть тот почти невероятный случай, когда между выполнением двух операторов
произойдёт смена месяца на компьютере, в результате чего в cmbKuu может оказаться следующий
месяц. Поэтому не вызываем DateTime.Now дважды.
var now = DateTime.Now;
cmbMonth.FormattingEnabled = true;
cmbMonth.FormatString = "MMMM";
cmbKuu.Text = now.ToString("MMMM");
for (int i = 1; i <= 12; i++)
{
cmbMonth.Items.Add(now);
now = now.AddMonths(1);
}
Теперь в обработчике события можно получить выбранное значение DateTime и использовать
его свойства.
private void CmbMonth_SelectedIndexChanged(object sender, EventArgs e)
{
var dateTime = (DateTime)cmbMonth.SelectedItem;
// используем dateTime.Month
}
Добрый день/вечер, столкнулся с задачей сделать контекстный поиск в Windows.Controls.ComboBox.
Поиск производится по любому вхождению.
Например имеем: Вася, Федя, Вика
Набираем "в", получаем: Вася, Вика; "ва" остается Вася; "дя" - Федя.
Надеюсь смысл понятен.
Доработал класс ComboBox:
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
using ProgrammingWeapons;
namespace Telemetria.UI
{
public class ComboBoxFinder : ComboBox
{
private bool _isAutoFilter = true;
public bool IsAutoFilter {
get { return _isAutoFilter; }
set { _isAutoFilter = value; }
}
private readonly List
Ответы
Ответ 1
У меня работает так:
XAML:
Code-behind:
void OnComboboxTextChanged(object sender, RoutedEventArgs e)
{
CB.IsDropDownOpen = true;
// убрать selection, если dropdown только открылся
var tb = (TextBox)e.OriginalSource;
tb.Select(tb.SelectionStart + tb.SelectionLength, 0);
CollectionView cv = (CollectionView)CollectionViewSource.GetDefaultView(CB.ItemsSource);
cv.Filter = s =>
((string)s).IndexOf(CB.Text, StringComparison.CurrentCultureIgnoreCase) >= 0;
}
Результат:
Имеем XAML: ABCDEABCDABCABA а вот теперь начинается магия, выбираем элемент ABC в ComboBox и удалим с конца один символ, заливка белая, т.е. ComboBox считает что элемент выбран, а в тексте ComboBox говориться что выбран AB, хотя в TextBlock указывается что выбран элемент ABCDE Итог получаем: Выбираем один элемент, меняем текст на другой, а в SelectItem ComboBox получаем вообще третий элемент, причем пользователь об этом даже не узнает... (TextBlock в проекте нет, он создан лишь для демонстрации).
Ответ
В стиль ComboBox добавим Setter и опишем сам конвертер, который будет в том числе работать и с DisplayMemberPath у ComboBox public class HelpConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length >= 2 && values[0] is IEnumerable && values[1] is string)
{
string displayString = null;
if (values.Length >= 3 && values[2] is string)
displayString = (string)values[2];
IEnumerable collection = (IEnumerable)values[0];
IEnumerator enumerator = collection.GetEnumerator();
string equalsText = (string)values[1];
while (enumerator.MoveNext())
{
string elementDisplayString = null;
if (string.IsNullOrWhiteSpace(displayString))
elementDisplayString = enumerator.Current?.ToString();
else
{
if (enumerator.Current != null)
{
System.Reflection.PropertyInfo p = enumerator.Current.GetType().GetProperty(displayString);
if (p != null && p.CanRead)
elementDisplayString = p.GetValue(enumerator.Current)?.ToString();
}
}
if (equalsText.Equals(elementDisplayString, StringComparison.InvariantCultureIgnoreCase))
return enumerator.Current;
}
}
return null;
} public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Если кто-нибудь нашел более изящное решение, то прошу ответить, спасибо.
Здравствуйте. Столкнулся с проблемой. Делаю приложение на WPF с использованием паттерна MVVM. Работу с базой данных осуществляю с помощью Entity Framework. Есть 2 таблицы: Сотрудники(ФИО, id должности) и Должности(id, название). Я хочу, чтобы на форме с сотрудниками, когда я выбираю одного из них, выводилась вся информация о нём (ФИО, должность и тд). Это я осуществил, создав класс EmployeesViewModel: class EmployeesViewModel : INotifyPropertyChanged
{
OpticsEntities db;
private Employee selectedEmployees;
public ObservableCollection Employees { get; set; }
public Employee SelectedEmployees
{
get { return selectedEmployees; }
set
{
selectedEmployees = value;
OnPropertyChanged("SelectedEmployees");
}
} public EmployeesViewModel()
{
db = new OpticsEntities();
db.Employees.Load();
Employees = db.Employees.Local;
} public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged([CallerMemberName]string prop = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
} private RelayCommand addCommand;
private RelayCommand saveCommand;
private RelayCommand removeCommand;
private RelayCommand changeCommand; public RelayCommand AddCommand
{
get
{
return addCommand ??
(addCommand = new RelayCommand(obj =>
{
Employee employees = new Employee();
Employees.Insert(0, employees);
SelectedEmployees = employees;
}));
}
} public RelayCommand SaveCommand
{
get
{
return saveCommand ??
(saveCommand = new RelayCommand(obj =>
{
Employee employees = obj as Employee;
if (employees != null)
{
db.Employees.Add(employees);
db.SaveChanges();
}
}));
}
} public RelayCommand ChangeCommand
{
get
{
return changeCommand ??
(changeCommand = new RelayCommand(obj =>
{
Employee employees = obj as Employee;
if (employees != null)
{
var changeEmployees = db.Employees.Find(employees.id);
employees = changeEmployees;
db.SaveChanges();
}
}));
}
}
}
и представление: Всё отлично работает. Можно добавить нового сотрудника и сохранить.
Но проблема вот в чем. Поле "Должность" связано с таблицей "Должности" по внешнему ключу (id). И я хочу, чтобы вместо текстового поля с id должности был ComboBox, в котором бы находились названия всех должностей из таблицы "Должности". По умолчанию в нем была бы та должность, которой соответствует сотрудник, но можно было бы открыть ComboBox и присвоить ему другую. После нажатия на Save всё сохранилось бы в базе. Максимум, что я смог сделать, это создать ComboBox, в котором будут отображаться эти должности. Но для этого пришлось сделать еще одну коллекцию во ViewModel и обращаться уже к ней в ItemsSource. Естественно, что бы я в ней не выбрал, никакой связи с сотрудником не будет, так как данные не связаны получается. Как мне сделать, чтобы в ComboBox отображались должности из таблицы Должности, и при выборе одной из них она бы присваивалась полю Должность у Сотрудника? Напомню, что таблицы связаны внешним ключом по id должности.
Ответ
Тут достаточно много вариантов реализации, красивых и не очень, но если попробовать сделать с участием дополнительной коллекции моделей представления для каждой должности, как вы описали. Пример искусственный, написан для демонстрации подхода к использованию тонкостей привязки ComboBox к данным, ни в коем случае не является примером реализации правильного MVVM. Модели сущностей предметной области: public class Employee
{
public string Name { get; set; }
public int PostID { get; set; }
} public class Post
{
public int ID { get; set; }
public string Name { get; set; }
}
Модели представления: public class EmployeeViewModel : NotificationObject
{
public EmployeeViewModel(Employee model, IEnumerable posts)
{
Model = model;
var postViewModels = new List();
foreach (var post in posts)
{
postViewModels.Add(new PostViewModel(post));
}
PostViewModels = postViewModels;
} public Employee Model { get; set; }
public IEnumerable PostViewModels { get; private set; }
} public class PostViewModel : NotificationObject
{
public PostViewModel(Post model)
{
Model = model;
} public Post Model { get; set; }
}
Класс NotificationObject просто реализует INotifyPropertyChanged. В DataContext окна попадает EmployeeViewModel Разметка: Пример работающий, в Combobox будут отображаться названия должностей, при этом в объект Employee при выборе в ComboBox будет присваиваться валидный идентификатор должности. У ComboBox есть несколько крайне полезных свойств.
DisplayMemberPath - путь к свойству сущности в DataContext элемента списка ComboBox, который будет отображаться в выпадающем списке и в основном теле ComboBox
SelectedValue - наравне с SelectedItem можно биндиться к свойству SelectedValue, значение которого берётся по определённому пути из SelectedItem
SelectedValuePath - путь от SelectedItem, по которому берётся SelectedValue
В моём примере при выборе в ComboBox в его свойство SelectedItem попадает PostViewModel, а в свойство SelectedValue попадает уже искомый идентификатор должности. Это свойство и забиндено на PostID сущности Employee Надеюсь, принцип понятен и удастся перенести это на ваш случай.
Имеется ComboBox, который хранит месяца года в текстовом формате. Как можно записать номер месяца в DataTime?
Там где происходит добавление месяцев в список, нужный мне номер месяца сохранить сразу не могу, так как нужен выбор пользователя. for (int i = 1; i <= 12; i++)
{
cmbMonth.Items.Add(now.ToString("MMMM"));
now = now.AddMonths(1);
cmbMonth.Text = DateTime.Now.ToString("MMMM");
}
Нужно из cmbMonth.Text получить название месяца, что бы записать полноценную дату выбранную пользователем. Но так-как месяца хранятся в виде текста, возникли проблемы. Как можно решить? (Писать цикл с месяцами и их номерами не вариант, так как текст месяца в ComboBox зависит от языка системы)
Ответ
Для перевода из String в DataTime есть пара хороших методов Parse и ParseExact. Вот второй и подойдет для данной задачи! Суть следующая - Вы ему даете 3 аргумента:
string, где есть значения связанные со временем (будь то время, дата, месяц или еще что).
Некая маска, по которой метод ParseExact поймет, что находится в пункте 1.
Культура пользователя или указанная заранее.
Напишу пример к вашей задаче: var m = "Декабрь";
DateTime dd = DateTime.ParseExact(m, "MMMM", System.Globalization.CultureInfo.CurrentCulture);
Console.WriteLine(dd);
Как видим я задал 3 параметра - месяц (декабрь), маска (полный формат месяца) и культура (текущая культура пользователя). Результатом программа выдаст: 01.12.2018 0:00:00
Надеюсь это решит вашу проблему. Удачи!