向 WPF 数据网格添加图像列并根据另一列中的前两个字符填充

问题描述 投票:0回答:2

我对 wpf 数据网格的工作方式有点陌生,我有一个项目,给定机场 IATA 代码将在 wpf 数据网格中显示该机场的当前到达和离开航班。

航班数据取自网站www.avionio.com,由于该网站是多语言的,因此列标题将根据所选语言而变化。例如,以下是伦敦希思罗机场到达航班的英文版本:

这是德文版:

因为列标题根据所选语言是动态的,所以它们由“DataTable.Columns.Add”方法填充,行通过 DataRow 方法添加到数据表,最后将数据网格源设置为数据表。

一切正常,数据网格根据选择的语言正确更新。

我现在想要做的不是“航空公司”列以文本形式显示航空公司名称,我希望它显示航空公司徽标,可以从以下网站获取 https://pics.avs.io /71/25。该站点使用航班号的前两个字符来引用正确的航空公司徽标,因此对于上面数据网格(汉莎航空)中的第一个航班,完整的 URL 将是 https://pics.avs.io/71/25/ LH.png.

我的问题是如何根据相应“航班”列中的前两个字符用相关航空公司徽标替换数据网格上的航空公司列。

我使用的语言是 C#,下面是我现有的填充数据网格的代码:

使用网页https://www.avionio.com/widget/en/LHR/arrivals(这显示了当前伦敦希思罗机场到达的英语。我首先通过以下代码获取网站背后的HTML代码:

public static string MakeRequest(string URL)
        {
            ServicePointManager.Expect100Continue = true;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(URL);
            try
            {
                using (HttpWebResponse resp = (HttpWebResponse)req.GetResponse())
                {
                    try
                    {
                        Stream response = resp.GetResponseStream();
                        StreamReader readStream = new StreamReader(response, Encoding.UTF8);
                        return readStream.ReadToEnd();
                    }
                    finally
                    {
                        resp.Close();
                    }
                }
            }
            catch (WebException ex)
            {
                if (ex.Status == WebExceptionStatus.ProtocolError && ex.Response != null)
                {
                    var resp = (HttpWebResponse)ex.Response;
                    if (resp.StatusCode == HttpStatusCode.NotFound)
                    {
                        MessageBox.Show("Details unavailable or URL airport name incorrect", "Cannot Retrieve Details", MessageBoxButton.OK, MessageBoxImage.Error);
                        return "N/A";
                    }
                }
            }
            return "";
        }

然后我从这个代码中挑选出列标题(因为它们因所选语言而异)通过:

int startPos = 0;
int endPos = 0;

List<string> colHeaderText = new List<string>();
startPos = arrivalsDoc.IndexOf("<table class=\"timetable\">");
string colHeaders = arrivalsDoc.Substring(startPos, arrivalsDoc.Length - startPos);
startPos = colHeaders.IndexOf("<th>");
colHeaders = colHeaders.Substring(startPos, colHeaders.Length - startPos);
endPos = colHeaders.IndexOf("</tr>");
colHeaders = colHeaders.Substring(0, endPos);
string delimiter = "</th>";
string[] colHeadersSplit = colHeaders.Split(new[] { delimiter }, StringSplitOptions.None);
colHeaderText.Clear();
foreach (var item in colHeadersSplit)
{
    if (item != "")
    {
         string headerText = item.Replace("<th>", "").Replace("</th>", "").Replace("\r","").Replace("\n","").Trim();
         if (headerText != "")
         {
             colHeaderText.Add(headerText);
         }
     }
 }

并通过以下方式添加到数据表:

DataTable dtArrivals = new DataTable();
dtArrivals.Columns.Add(colHeaderText[0], typeof(string));                                  //Scheduled
dtArrivals.Columns.Add(colHeaderText[4].ToString(), typeof(string));            //Airline
dtArrivals.Columns.Add(colHeaderText[5].ToString(), typeof(string));            //Flight No
dtArrivals.Columns.Add(colHeaderText[3].ToString(), typeof(string));            //Origin
dtArrivals.Columns.Add(colHeaderText[2].ToString(), typeof(string));            //Status

然后我以与上述类似的方式将 HTML 代码中的每一行数据提取到单独的变量中,并通过

填充数据表的新行
DataRow drArrivalFlight = dtArrivals.NewRow();
drArrivalFlight[0] = scheduled + " " + flightDate;
drArrivalFlight[1] = airline;
drArrivalFlight[2] = flightNo;
drArrivalFlight[3] = origin;
drArrivalFlight[4] = status;
dtArrivals.Rows.Add(drArrivalFlight);

我最终通过以下方式填充数据网格:

dgCurrentArrivals.ItemsSource = dtArrivals.DefaultView;

我还没有尝试过任何东西,因为我不熟悉 wpf 数据网格,并且刚刚成功地用文本值填充了数据网格,所以不知道从哪里开始图像列。

提前感谢您的帮助。

wpf image datagrid
2个回答
0
投票

您可以通过处理

DataGrid.AutoGeneratingColumns
事件来修改自动生成的列。侦听此事件允许您重新排序或过滤/替换列。

以下示例允许通过仅显式定义徽标列来拥有动态

DataGrid

用于显示徽标的
Image
控件将自动从单元格值提供的 URL 下载图像。

重要的是要注意

WebRequest
类及其所有关联类已被弃用。 Microsoft 声明我们不应再使用它们。相反,我们必须使用
HttpClient
类(
WebClient
也被弃用)。

  1. 修复HTTP请求相关代码。使用推荐的
    HttpClient
    允许使用异步 API,这将提高性能:
private async Task RunCodeAsync()
{
  string arrivalsDoc = await MakeRequestAsync("https://www.avionio.com/widget/en/LHR/arrivals");

  // TODO::Parse result and create DataTable
}

public async Task<string> MakeRequestAsync(string url)
{
  using var httpClient = new HttpClient();
  using HttpResponseMessage response = await httpClient.GetAsync(url);
  try
  {
    // Will throw if the status code is not in the range 200-299.
    // In case the exception is handled locally, 
    // to further improve performance you should avoid the exception
    // and instead replace the following line with a conditional check 
    // of the 'HttpResponseMessage.IsSuccessStatusCode' property.
    _ = response.EnsureSuccessStatusCode();
  }
  catch (HttpRequestException)
  {
    if (response.StatusCode == HttpStatusCode.NotFound)
    {
      _ = MessageBox.Show("Details unavailable or URL airport name incorrect", "Cannot Retrieve Details", MessageBoxButton.OK, MessageBoxImage.Error);
      return "N/A";
    }

    return "";
  }

  string responseBody = await response.Content.ReadAsStringAsync();
  return responseBody;
}
  1. 您应该在
    AirlineLogo
    中添加一个额外的列
    DataTable
    ,其中包含图标的路径:
DataTable dtArrivals = new DataTable();
dtArrivals.Columns.Add(colHeaderText[0], typeof(string));     
dtArrivals.Columns.Add(colHeaderText[4], typeof(string));     
dtArrivals.Columns.Add(colHeaderText[5], typeof(string));     
dtArrivals.Columns.Add(colHeaderText[3], typeof(string));     
dtArrivals.Columns.Add(colHeaderText[2], typeof(string));     

// Append the logo column. 
// The column name will be replaced with the "Airline" column name later.
dtArrivals.Columns.Add("AirlineLogo", typeof(string)); 
  1. 然后将每一行映射到相应的航空公司徽标资源。此路径可以是指向网络资源的原始 URL,例如
    "https://pics.avs.io/71/25/LH.png"
    :
DataRow drArrivalFlight = dtArrivals.NewRow();
drArrivalFlight[0] = scheduled + " " + flightDate;
drArrivalFlight[1] = airline;
drArrivalFlight[2] = flightNo;
drArrivalFlight[3] = origin;
drArrivalFlight[4] = status;

// Compose the URL to the logo using the first two letters of the flight ID
drArrivalFlight["AirlineLogo"] = $"https://pics.avs.io/71/25/{flightNo[0..2]}.png";

dtArrivals.Rows.Add(drArrivalFlight);
  1. 为替换的列定义模板,以便它从 URL 中获取图像以显示它(通过使用
    Image
    控件):
<Window>
  <DataGrid x:Name="dgCurrentArrivals" 
            AutoGenerateColumns="True"
            AutoGeneratingColumn="DataGrid_AutoGeneratingColumn"
            AutoGeneratedColumns="DataGrid_AutoGeneratedColumns">
    <DataGrid.Resources>
      <DataGridTemplateColumn x:Key="AirlineLogoColumn">
        <DataGridTemplateColumn.CellTemplate>
          <DataTemplate>

            <!-- Bind the Image to the URL value of the 'AirlineLogo' column of the DataTable -->
            <Image Source="{Binding [AirlineLogo]}"
                   Height="24" />
          </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
      </DataGridTemplateColumn>
    </DataGrid.Resources>
  </DataGrid>
</Window>
  1. 使用
    Airline
    事件隐藏/替换
    DataGrid.AutoGeneratingColumn
    列:
private const int ReplacedColumnIndex = 1;
private int CurrentColumnIndex { get; set; }
private int ReplacedColumnName { get; set; }

private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
  if (this.CurrentColumnIndex == ReplacedColumnIndex)
  {
    // Store the column name of the actual language
    this.ReplacedColumnName = e.PropertyName;

    // Hide the column
    e.Cancel = true;
  }
  else if (e.PropertyName == "AirlineLogo")
  {
    var dataGrid = sender as DataGrid;
    var dataGridImageColumn = dataGrid.Resources["AirlineLogoColumn"] as DataGridTemplateColumn;
    
    // Reorder replaced column
    dataGridImageColumn.DisplayIndex = ReplacedColumnIndex;

    // Rename replaced column with the name of the current table language
    dataGridImageColumn.Header = this.ReplacedColumnName;

    // Replace the auto generated text column with our custom image column
    e.Column = dataGridImageColumn;
  }

  this.CurrentColumnIndex++;
}

-1
投票

所以这是一个简化程序的某些部分并展示如何将图像绑定到数据网格的解决方案。通过编程做到这一点并不容易,xaml 的使用简化了任务。所以我建议你检查你的程序,因为它真的不可能使用它,因为它真的很复杂......

  1. 添加 nuget 包 HtmlAgility Pack(简化 html 文件中标签的搜索)

  2. 在 Xaml 视图中声明您的 DataGrid。

MainWindow.xaml(主视图)

<Window x:Class="TestDatagrid.MainWindow" Name="Root"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestDatagrid"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <DataGrid HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10,10,0,0" AutoGenerateColumns="False"
                  Loaded="DataGrid_Loaded">
            <DataGrid.Resources>
                <local:BindingProxy x:Key="proxy" Data="{Binding ElementName=Root}" />
            </DataGrid.Resources>
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="{Binding Data.Headers[0], Source={StaticResource proxy}}" Width="SizeToCells" IsReadOnly="True">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal" >
                                <TextBlock Text="{Binding Scheduled}" Margin="0,0,5,0"/>
                                <TextBlock Text="{Binding Date}"/>
                            </StackPanel>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTemplateColumn Header="{Binding Data.Headers[4], Source={StaticResource proxy}}" Width="SizeToCells" IsReadOnly="True">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Image Source="{Binding Logo}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTextColumn Header="{Binding Data.Headers[5], Source={StaticResource proxy}}"
                                    Binding="{Binding Flight}"/>
                <DataGridTextColumn Header="{Binding Data.Headers[3], Source={StaticResource proxy}}"
                                    Binding="{Binding From}"/>
                <DataGridTextColumn Header="{Binding Data.Headers[2], Source={StaticResource proxy}}"
                                    Binding="{Binding Status}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

MainWindow.xaml.cs

using HtmlAgilityPack;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Windows;
using System.Windows.Controls;

namespace TestDatagrid
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public List<Arrival> Arrivals = new List<Arrival>();
        public event PropertyChangedEventHandler? PropertyChanged;
        public List<string> Headers { get; set; }
        public MainWindow()
        {
            var html = MakeRequest("https://www.avionio.com/widget/en/lhr/arrivals");
            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(html);

            //get headers from html file 
            Headers = doc.DocumentNode.SelectNodes("//table/thead/tr/th").Select(x => x.InnerText).ToList();
            //get datas from TimeTables (get tag tr with 2 classes, one class is timetable-record)
            var TimeTables = doc.DocumentNode.SelectNodes("//table/tbody/tr[contains(@class,'timetable-record')]")
                                             .Where(x => x.GetAttributeValue("class", "").Split(' ').Count() == 2)
                                             .Select(x => 
                                             {
                                                 var dt = x.SelectNodes(".//td").Select(a =>
                                                 {
                                                     if (a.GetAttributeValue("class", "").Equals("timetable-flight"))
                                                     {
                                                         var y = a.SelectSingleNode(".//a");
                                                         return y.InnerText;
                                                     }
                                                     return a.InnerText;
                                                 }).ToArray();

                                                 return dt;
                                             }).ToArray();

            #region Load new logo from internet and save in folder for future use
            var files = Directory.GetFiles(@"D:\files").Select(x => x.Split('\\').Last()).ToList();
            var flags = TimeTables.Select(x => $"{x.Last()[0..2]}.png");
            var newFlags = flags.Except(files).ToList();        

            ServicePointManager.Expect100Continue = true;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

            //download new flags and add to files list
            var folderTosaveLogos = @"D:\files\";
            using (var client = new WebClient())
            {
                foreach (var flag in newFlags)
                {
                    client.DownloadFile(new Uri($"https://pics.avs.io/71/25/{flag}"), $"{folderTosaveLogos}{flag}");
                    files.Add(flag);
                }
            }
            #endregion
            #region Create the global list Arrivals)
            foreach (var t in TimeTables)
            {
                var f = new Arrival();
                f.Scheduled = t[0];
                f.Date = t[1];
                f.Status = t[2];
                f.From = t[3];
                f.Airline = t[4];
                f.Flight = t[5];
                f.Logo = @$"D:\files\{f.Flight[0..2]}.png";
                Arrivals.Add(f);
            }
            #endregion
        }

        private void DataGrid_Loaded(object sender, RoutedEventArgs e)
        {
            // ... Assign ItemsSource to Arrivals List.
            var grid = sender as DataGrid;
            grid.ItemsSource = Arrivals;
        }
        public static string MakeRequest(string URL)
        {
            ServicePointManager.Expect100Continue = true;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(URL);
            try
            {
                using (HttpWebResponse resp = (HttpWebResponse)req.GetResponse())
                {
                    try
                    {
                        Stream response = resp.GetResponseStream();
                        StreamReader readStream = new StreamReader(response, Encoding.UTF8);
                        return readStream.ReadToEnd();
                    }
                    finally
                    {
                        resp.Close();
                    }
                }
            }
            catch (WebException ex)
            {
                if (ex.Status == WebExceptionStatus.ProtocolError && ex.Response != null)
                {
                    var resp = (HttpWebResponse)ex.Response;
                    if (resp.StatusCode == HttpStatusCode.NotFound)
                    {
                        MessageBox.Show("Details unavailable or URL airport name incorrect", "Cannot Retrieve Details", MessageBoxButton.OK, MessageBoxImage.Error);
                        return "N/A";
                    }
                }
            }
            return "";
        }
    }
}

班级Arrival.cs

namespace TestDatagrid
{
    public class Arrival
    {
        public string Scheduled { get; set; }
        public string Date { get; set; }
        public string Status { get; set; }
        public string From { get; set; }
        public string Airline { get; set; }
        public string Flight { get; set; }
        public string Logo { get; set; }
    }
}

您必须为 Datagrid 创建绑定代理(请参阅DataGrid 的 DataContext

BindingProxy.cs

using System.Windows;

namespace TestDatagrid
{
    public class BindingProxy : Freezable
    {
        #region Overrides of Freezable
        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }
        #endregion

        public object Data
        {
            get { return (object)GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
    }
}

结果:


所以研究我的程序以使用 xaml 和绑定的强大功能

© www.soinside.com 2019 - 2024. All rights reserved.