I am a bit new to how wpf datagrids work, and I have a project which, given an airport IATA code will display the current arrival and departure flights for that airport in a wpf datagrid.
The flight data is taken from the website www.avionio.com, and as this website is multi-lingual, the column headers will change dependent on the language selected. For example, below is the English version of the arrival flights into London Heathrow:
and this is the German version:
Because the column headers are dynamic based on the language selected, they are populated by the 'DataTable.Columns.Add' method and the rows being added to the datatable via the DataRow method, before finally setting the datagrid source to the datable.
This all works fine and the datagrid is updated correctly depending on which language is selected.
What I now want to do is instead of the 'Airline' column displaying the airline name as text, I would like it to show the airline logo, which can be taken from the following site https://pics.avs.io/71/25. This site uses the first two characters of the flight number to reference the correct airline logo, so for the first flight in the datagrid above (Lufthansa) the full URL would be https://pics.avs.io/71/25/LH.png.
My question is how do I replace the Airline column on the datagrid with the relevant airline logo depending on the first two characters in the corresponding 'Flight' column.
The language I am using is C# and below is the existing code I have that populates the datagrid:
Using the webpage https://www.avionio.com/widget/en/LHR/arrivals (this displays the current London Heathrow arrivals in English. I firstly get the HTML code behind the website via this code:
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 "";
}
I then pick out the column headings from this code (as they vary by language chosen) via:
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);
}
}
}
And add to a DataTable via:
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
I then extract each row of data from the HTML code into separate variables in a similar way to the above and populate a new row of the datatable via
DataRow drArrivalFlight = dtArrivals.NewRow();
drArrivalFlight[0] = scheduled + " " + flightDate;
drArrivalFlight[1] = airline;
drArrivalFlight[2] = flightNo;
drArrivalFlight[3] = origin;
drArrivalFlight[4] = status;
dtArrivals.Rows.Add(drArrivalFlight);
I finally populate the datagrid via:
dgCurrentArrivals.ItemsSource = dtArrivals.DefaultView;
I haven't tried anything yet as I am not familiar with wpf datagrids, and have only just successfully populated the datagrid with text values, so wouldn't know where to start with an image column.
Thanks for any help in advance.
You can modify autogenerated columns by handling the DataGrid.AutoGeneratingColumns
event. Listening to this event allows you to reorder or to filter/replace columns.
The following example allows to have a dynamic DataGrid
by only defining the logo column explicitly.
The Image
control which is used to display the logos will automatically download the images from an URL which is provided by the cell value.
It's important to note that the WebRequest
class and all its associated classes are deprecated. Microsoft states that we should no longer use them. Instead, we must use the HttpClient
class (WebClient
is also deprecated).
HttpClient
allows to use async APIs, which will improve the performance: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;
}
AirlineLogo
to your DataTable
that contains the path to the icon: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));
"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);
Image
control):<Window>
<DataGrid x:Name="dgCurrentArrivals"
AutoGenerateColumns="True"
AutoGeneratingColumn="DataGrid_AutoGeneratingColumn">
<DataGrid.Resources>
<DataGridTemplateColumn x:Key="AirlineLogoColumn">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<!-- Bind the Image to the URL value of the 'AirlineLogo' column of the DataTable -->
<Image Source="{Binding [AirlineLogo]}"
Height="24" />
<!-- Bind to airline name column -->
<TextBlock Text="{Binding [1]}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Resources>
</DataGrid>
</Window>
Airline
column using the DataGrid.AutoGeneratingColumn
event:private const int ReplacedColumnIndex = 1;
private string ReplacedColumnName { get; set; }
private int CurrentColumnIndex { 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 DataGridColumn;
// 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++;
}