#c# #wpf #combobox #binding #datagrid
#c# #wpf #выпадающий список #привязка #datagrid
Вопрос:
Я пытаюсь настроить следующую коллекцию плееров, и у меня возникают проблемы с отображением / обновлением коллекции данных. У меня есть 2 класса: CountryModel и PlayerModel Класс CountryModel определяется следующим образом:
public static class CountryModel
{
public class Country
{
public string Name { get; set; }
public string Abbr { get; set; }
public byte[] Flag { get; set; }
}
public static async Task<ObservableCollection<Country>> GetCountriesAsync()
{
// Get all country names from system
var ThreeLetterMapping = CultureInfo.GetCultures(CultureTypes.SpecificCultures)
.Select(ci => new RegionInfo(ci.LCID))
.GroupBy(ri => ri.EnglishName)
.ToDictionary(g => g.Key, g => g.First().ThreeLetterISORegionName);
// Create observablecollection to contain list of country names
ObservableCollection<Country> getNames = new ObservableCollection<Country>();
// Run task of getting every country files
await Task.Run(() =>
{
// Get all files from Resources directory
string[] Files = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory @"Resources");
foreach (string Name in Files)
{
// Add only country name without extension if png format
if (Path.GetExtension(Name) == ".png" amp;amp; !Name.Contains("BCA"))
{
byte[] image = System.IO.File.ReadAllBytes(Name);
// Country name without extension
string CountryName = Path.GetFileNameWithoutExtension(Name);
// Get abbreviation from above ThreeLetterMapping variable
string abbr = null;
try
{
// Country exists and mapping is possible
abbr = ThreeLetterMapping[CountryName];
}
catch
{
// Custom mapping to 3 letter using first 3 letters in country name
abbr = CountryName.Substring(0, 3);
}
// Add country with data to list
getNames.Add(new Country { Name = CountryName, Abbr = abbr, Flag = image });
//getNames.Add(new Country { Name = CountryName, Abbr = abbr, Flag = GetFlag(CountryName) });
}
}
});
return getNames;
}
public static BitmapImage GetFlag(string CountryName)
{
try
{
BitmapImage getFlag = new BitmapImage(new Uri(AppDomain.CurrentDomain.BaseDirectory @"Resources" CountryName ".png"));
//BitmapImage getFlag = new BitmapImage(new Uri("pack://application:,,,/Resources/" CountryName ".png"));
return getFlag;
}
catch (Exception e)
{
MessageBox.Show("Error: " e.Message, "Retrieve Flag", MessageBoxButton.OK, MessageBoxImage.Error);
return null;
}
}
Как вы можете видеть из приведенного выше кода, я могу успешно извлечь a List<CountryModel>
из вышеупомянутого класса.
Теперь мое главное окно выглядит так:
Текстовые поля определяются следующим образом:
<TextBox x:Name="TextBoxId" Grid.Column="1" Grid.Row="0" Text="{Binding SelectedItem.Id, ElementName=DataGridPlayers}" IsEnabled="False" Height="26" VerticalContentAlignment="Center" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="0,0,12,0" IsTabStop="False"/>
<TextBox x:Name="TextBoxFullName" Grid.Column="1" Grid.Row="1" Text="{Binding SelectedItem.FullName, ElementName=DataGridPlayers}" IsEnabled="False" Height="26" VerticalContentAlignment="Center" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="0,0,12,0" IsTabStop="False"/>
<TextBox x:Name="TextBoxLastName" Grid.Column="1" Grid.Row="2" Text="{Binding SelectedItem.LastName, ElementName=DataGridPlayers}" Height="26" VerticalContentAlignment="Center" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="0,0,12,0" TabIndex="1"/>
<TextBox x:Name="TextBoxFirstName" Grid.Column="1" Grid.Row="3" Text="{Binding SelectedItem.FirstName, ElementName=DataGridPlayers}" Height="26" VerticalContentAlignment="Center" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="0,0,12,0" TabIndex="2"/>
<TextBox x:Name="TextBoxMiddleName" Grid.Column="1" Grid.Row="4" Text="{Binding SelectedItem.MiddleName, ElementName=DataGridPlayers}" Height="26" VerticalContentAlignment="Center" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="0,0,12,0" TabIndex="3"/>
<ComboBox x:Name="ComboBoxGender" Grid.Column="1" Grid.Row="5" SelectedIndex="{Binding SelectedItem.Male, ElementName=DataGridPlayers}" Height="26" HorizontalAlignment="Left" Width="100" TabIndex="4">
<ComboBoxItem Content="Female"/>
<ComboBoxItem Content="Male"/>
</ComboBox>
<ComboBox x:Name="ComboBoxCountry" Grid.Column="1" Grid.Row="6" SelectedValuePath="Name" SelectedValue="{Binding SelectedItem.Country, ElementName=DataGridPlayers}" IsTextSearchEnabled="True" TextSearch.TextPath="Name" Height="26" HorizontalAlignment="Stretch" Margin="0,0,12,0" TabIndex="5">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Width="30" Source="{Binding Flag}"/>
<TextBlock Text="{Binding Name}" Margin="4,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
И DataGrid определяется следующим образом:
<DataGrid Grid.Column="1" x:Name="DataGridPlayers" AutoGenerateColumns="False" IsReadOnly="True" CanUserSortColumns="True" CanUserAddRows="False" AlternatingRowBackground="LightGray" SelectionChanged="DataGridPlayers_SelectionChanged" TabIndex="12">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Id}" Visibility="Hidden"/>
<DataGridTextColumn Binding="{Binding LastName}" Visibility="Hidden"/>
<DataGridTextColumn Binding="{Binding FirstName}" Visibility="Hidden"/>
<DataGridTextColumn Binding="{Binding MiddleName}" Visibility="Hidden"/>
<DataGridTextColumn Header="Player full name" Binding="{Binding FullName}" Width="1.5*"/>
<DataGridTextColumn Binding="{Binding Male}" Visibility="Hidden"/>
<DataGridTextColumn Header="Country" Binding="{Binding Country}" Width="*"/>
<DataGridTemplateColumn Header="Flag" Width="40" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Flag}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Flag}" Value="{x:Null}">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
Вот мой код, лежащий в основе:
public partial class MainWindow : Window
{
private ObservableCollection<CountryModel.Country> ListCountries;
private ObservableCollection<PlayerModel> ListPlayers;
public MainWindow()
{
InitializeComponent();
GetCountriesAsync();
ListPlayers = new ObservableCollection<PlayerModel>();
DataGridPlayers.ItemsSource = ListPlayers;
}
private async void GetCountriesAsync()
{
ListCountries = await Task.Run(() => CountryModel.GetCountriesAsync());
ComboBoxCountry.ItemsSource = ListCountries;
}
private void ButtonAddPlayer_Click(object sender, RoutedEventArgs e)
{
PlayerModel newplayer = new PlayerModel { LastName = "Lastname", FirstName = "Firstname", MiddleName = "Middlename", Male = true, Country = "" }; //, Flag = null
ListPlayers.Add(newplayer);
DataGridPlayers.SelectedItem = ListPlayers.Last();
}
}
Теперь у меня есть несколько проблем с этим:
Как мне привязать столбец флага к изображению флага страны?
Почему, когда я обновляю поля слева (например, для фамилии или страны), сетка данных не обновляется соответствующим образом?
Спасибо, что нашли время помочь! Мастер-джедай
Комментарии:
1. У вас есть Fody. PropertyChanged установлен? Если нет, вам следует взглянуть на это. Если вы не хотите устанавливать какой-либо пакет, то в ваших моделях необходимо реализовать INotifyPropertyChanged . В противном случае WPF не знает, что свойства в DataGrid изменились через поля слева. Вам нужно уведомить об этом WPF.
2. Отлично! После некоторого тестирования я, наконец, выбрал Fody! Жизнь намного проще!
3. Рад, что это помогло ^.^
Ответ №1:
Вторая проблема: ваш класс должен выглядеть так, если у вас не установлен Fody
Я рекомендую посмотреть на это. Довольно сильно очищает код, как вы можете видеть в примере. https://github.com/Fody/PropertyChanged
В противном случае WPF не знает, что свойства в DataGrid изменились через поля слева. Вам нужно уведомить об этом WPF.
public class Country : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
private string abbr;
public string Abbr
{
get { return abbr; }
set
{
abbr = value;
OnPropertyChanged(nameof(Abbr));
}
}
private string countryName;
public string CountryName
{
get { return countryName; }
set
{
countryName = value;
OnPropertyChanged(nameof(CountryName));
OnPropertyChanged(nameof(Flag));
}
}
public BitmapImage Flag => GetFlag(countryName);
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Простой пример:
public class Country : INotifyPropertyChanged
{
public string Name { get; set; }
public string Abbr { get; set; }
public string CountryName { get; set; }
public BitmapImage Flag => GetFlag(CountryName);
public event PropertyChangedEventHandler PropertyChanged;
}
Первая проблема: когда у вас есть настройка класса в качестве примера, вы можете привязать ImageSource к флагу.
Комментарии:
1. Я начинаю понимать цель INotifyPropertyChanged . Но, как вы можете видеть в моем сообщении, я загружаю изображения флагов в память, чтобы не делать частого доступа к файлам. Разве это не так в определении класса, которое вы написали выше, где вы добавили флаг как «get», который постоянно будет считываться с файлом?
2. Кроме того, поскольку я использую
ObservableCollection
при привязке к DataGrid, разве это неINotifyPropertyChanged
должно быть реализовано?3. @JediMaster ObservableCollection уведомляет о добавлении или удалении объектов в коллекцию. Он не уведомляет об изменении свойств внутри этих объектов. Я тоже был смущен этим, когда начал. И о флаге. Да, он будет считывать файл каждый раз
CountryName
при внесении изменений, но я не вижу в этом проблемы. Но поскольку вам нужно реагировать на изменения свойства, это все равно будет сделано. Одна вещь, которую вы можете сделать, это создать метод в модели like.SetNewFlag()
, который вызывался бы в setter ofCountryName
и который устанавливал бы свойство flag и сохранял его в памяти.4. Таким образом, он будет устанавливать его только один раз при каждом
CountryName
изменении. (Извините за разделение сообщений, но закончились символы ☺)