ETS - Membuat Aplikasi Prakiraan Cuaca
4 Maret 2023
Pada ETS kali ini, saya membuat Aplikasi Prakiraan Cuaca berbasis dekstop menggunakan teknologi Windows Presentation Form (WPF) dengan .NET Framework. Untuk data cuaca, saya menggunakan API https://openweathermap.org/.
Fitur-fitur yang ada pada aplikasi ini antara lain.
- Cari Kota (Default = Surabaya)
- Menampilkan Hari dan Jam
- Menampilkan Data Cuaca
- Temperature
- Weather
- Cloudiness
- Wind status
- Sunrise & sunset
- Geo Location
- Humidity
- Visibility, dan
- Pressure
Berikut beberapa referensi yang saya gunakan
- Rancangan Tema dan Layout Aplikasi dengan WPF - via Youtube C# WPF Academy
- API Call menggunakan WPF - via Youtube Programming Concepts
Repository lengkap dapat diakses melalui Github
Video Demonstrasi
Dokumentasi
Source Code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Window x:Class="WeatherApp.MainWindow" | |
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:WeatherApp" | |
mc:Ignorable="d" | |
Title="Weather App" Height="685" Width="995" Background="White" | |
WindowStyle="None" AllowsTransparency="True" WindowStartupLocation="CenterScreen" FontFamily="Cambria"> | |
<Grid> | |
<Grid.ColumnDefinitions> | |
<ColumnDefinition Width="260"/> | |
<ColumnDefinition Width="*"/> | |
</Grid.ColumnDefinitions> | |
<!--Left Side--> | |
<Border CornerRadius="30 0 0 30" Background="#FFFFFF"> | |
<StackPanel> | |
<!--TextBox Search--> | |
<Border BorderBrush="#d9d9d9" BorderThickness="1" CornerRadius="15" HorizontalAlignment="Center" Width="200" Margin="0 35 0 0"> | |
<Grid Margin="7 7"> | |
<Grid.ColumnDefinitions> | |
<ColumnDefinition Width="auto" /> | |
<ColumnDefinition Width="*" /> | |
</Grid.ColumnDefinitions> | |
<Image Source="/Resources/explore.png" Height="18" Margin="5 0 0 0"/> | |
<TextBlock x:Name="textSearch" MouseDown="textSearch_MouseDown" Text="Search ..." Style="{StaticResource textHint}"/> | |
<TextBox Name="txtSearch" TextChanged="txtSearch_TextChanged" PreviewKeyDown="txtSearch_PreviewKeyDown" Style="{StaticResource textBox}"/> | |
</Grid> | |
</Border> | |
<StackPanel Margin="50 60 0 0"> | |
<Image Source="/Resources/weather-app.png" Width="140" HorizontalAlignment="Left" /> | |
<TextBlock x:Name="label_temp" Text="12°c" FontSize="46" Margin="0 20 0 0"/> | |
<TextBlock x:Name="label_datetime" Text="Monday, 16:00" FontSize="18" FontWeight="SemiBold" Margin="0 15 0 0"/> | |
<Separator Background="#dadada" Margin="3 30 40 30" Height="0.8"/> | |
<StackPanel Orientation="Horizontal"> | |
<Image x:Name="image_weather" Source="/Resources/cloud.png" Width="20" Height="20"></Image> | |
<TextBlock x:Name="label_weather" Text="Mostly Cloudy" FontSize="14" FontWeight="SemiBold" Margin="10 0 0 0" VerticalAlignment="Center"/> | |
</StackPanel> | |
<StackPanel Orientation="Horizontal" Margin="0 15 0 77"> | |
<Image Source="/Resources/cloud.png" Width="20" Height="20"/> | |
<TextBlock x:Name="label_cloudiness" Text="Cloudiness - 30%" FontSize="14" FontWeight="SemiBold" Margin="10 0 0 0" VerticalAlignment="Center"/> | |
</StackPanel> | |
</StackPanel> | |
<Border CornerRadius="15" Background="Black" Margin="30 0"> | |
<Border CornerRadius="15" Padding="0 30"> | |
<Border.Background> | |
<ImageBrush ImageSource="/Resources/q1.jpg" Stretch="Fill" Opacity="0.65"/> | |
</Border.Background> | |
<TextBlock x:Name="label_city" Text="Surabaya" Foreground="#FFFFFF" VerticalAlignment="Center" TextAlignment="Center" FontWeight="Bold" FontSize="14"/> | |
</Border> | |
</Border> | |
</StackPanel> | |
</Border> | |
<!--Right Side--> | |
<Border Grid.Column="1" CornerRadius="0 30 30 0" MouseDown="Border_MouseDown" Background="#F6F6F6" > | |
<StackPanel> | |
<!--Title--> | |
<Grid Margin="40 30 40 10"> | |
<TextBlock Text="Today's Highlights" FontSize="16" FontWeight="SemiBold" Margin="40 10 0 10"/> | |
<Button HorizontalAlignment="Right" BorderThickness="0" Width="30" Height="30" Click="btnExit_Click"> | |
<Button.Background> | |
<ImageBrush ImageSource="/Resources/close.png"/> | |
</Button.Background> | |
</Button> | |
</Grid> | |
<!--Widget Section--> | |
<WrapPanel Margin="40 0 0 0"> | |
<!--Widget 1--> | |
<Border Style="{StaticResource widgetBorder}"> | |
<Grid> | |
<TextBlock Text="Wind Status" Style="{StaticResource titleText}"/> | |
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"> | |
<TextBlock x:Name="label_windSpeed" Text="7.70" FontSize="34" FontWeight="SemiBold"/> | |
<TextBlock Text="km/h" FontSize="14" VerticalAlignment="Bottom" Margin="5 0 0 3"/> | |
</StackPanel> | |
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom"> | |
<Image Source="/Resources/dir.png" Width="25" Height="25" Margin="0 0 10 0"/> | |
<TextBlock x:Name="label_windDirection" Text="WSW" FontWeight="SemiBold" VerticalAlignment="Center"/> | |
</StackPanel> | |
</Grid> | |
</Border> | |
<!--Widget 2--> | |
<Border Style="{StaticResource widgetBorder}" BorderBrush="#03A9F4"> | |
<StackPanel> | |
<TextBlock Text="Sunrise Sunset" Style="{StaticResource titleText}"/> | |
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="0 25 0 0"> | |
<Image Source="/Resources/sunrise.png" Width="40" Height="40" Margin="0 0 10 0"/> | |
<StackPanel VerticalAlignment="Bottom"> | |
<TextBlock x:Name="label_sunrise" Text="6:35 AM" FontSize="16" FontWeight="SemiBold"/> | |
</StackPanel> | |
</StackPanel> | |
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="0 10 0 0"> | |
<Image Source="/Resources/sunset.png" Width="40" Height="40" Margin="0 0 10 0"/> | |
<StackPanel VerticalAlignment="Bottom"> | |
<TextBlock x:Name="label_sunset" Text="5:12 AM" FontSize="16" FontWeight="SemiBold"/> | |
</StackPanel> | |
</StackPanel> | |
</StackPanel> | |
</Border> | |
<!--Widget 3--> | |
<Border Style="{StaticResource widgetBorder}"> | |
<StackPanel> | |
<TextBlock Text="Geo Location" Style="{StaticResource titleText}"/> | |
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="0 25 0 0"> | |
<Image Source="/Resources/latitude.png" Width="40" Height="40" Margin="0 0 10 0"/> | |
<StackPanel> | |
<TextBlock x:Name="label_latitude" Text="6:35 AM" FontSize="16" FontWeight="SemiBold"/> | |
<TextBlock Text="latitude" FontSize="12" Margin="0 2 0 0" Foreground="#a0a0a0"/> | |
</StackPanel> | |
</StackPanel> | |
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="0 10 0 0"> | |
<Image Source="/Resources/longitude.png" Width="40" Height="40" Margin="0 0 10 0"/> | |
<StackPanel> | |
<TextBlock x:Name="label_longitude" Text="5:12 AM" FontSize="16" FontWeight="SemiBold"/> | |
<TextBlock Text="longitude" FontSize="12" Margin="0 2 0 0" Foreground="#a0a0a0"/> | |
</StackPanel> | |
</StackPanel> | |
</StackPanel> | |
</Border> | |
<!--Widget 4--> | |
<Border Style="{StaticResource widgetBorder}"> | |
<Grid> | |
<TextBlock Text="Humidity" Style="{StaticResource titleText}"/> | |
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"> | |
<TextBlock x:Name="label_humidity" Text="56" FontSize="34" FontWeight="SemiBold"/> | |
<TextBlock Text="%" FontSize="14" VerticalAlignment="Bottom" Margin="5 0 0 3"/> | |
</StackPanel> | |
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom"> | |
<TextBlock x:Name="label_humidity_rate" Text="Normal" FontWeight="SemiBold"/> | |
<Image Source="/Resources/humidity.png" Width="18" Height="18" Margin="10 0 0 0"/> | |
</StackPanel> | |
<Slider x:Name="slider_humidity" Style="{DynamicResource SliderStyle1}" Value="5.5" Maximum="10" Orientation="Vertical" HorizontalAlignment="Right" VerticalAlignment="Bottom" Height="90" IsEnabled="false"/> | |
</Grid> | |
</Border> | |
<!--Widget 5--> | |
<Border Style="{StaticResource widgetBorder}"> | |
<Grid> | |
<TextBlock Text="Visibility" Style="{StaticResource titleText}"/> | |
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"> | |
<TextBlock x:Name="label_visibility" Text="5.2" FontSize="34" FontWeight="SemiBold"/> | |
<TextBlock Text="km" FontSize="14" VerticalAlignment="Bottom" Margin="5 0 0 3"/> | |
</StackPanel> | |
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom"> | |
<TextBlock x:Name="label_visibility_rate" Text="Average" FontWeight="SemiBold"/> | |
<Image Source="/Resources/smog.png" Width="18" Height="18" Margin="10 0 0 0"/> | |
</StackPanel> | |
<Slider x:Name="slider_visibility" IsEnabled="false" Style="{DynamicResource SliderStyle1}" Value="4" Maximum="10" Orientation="Vertical" HorizontalAlignment="Right" VerticalAlignment="Bottom" Height="90"/> | |
</Grid> | |
</Border> | |
<!--Widget 6--> | |
<Border Style="{StaticResource widgetBorder}"> | |
<Grid> | |
<TextBlock Text="Atmospheric Pressure" Style="{StaticResource titleText}"/> | |
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"> | |
<TextBlock x:Name="label_pressure" Text="105" FontSize="34" FontWeight="SemiBold"/> | |
<TextBlock Text="hPa" FontSize="14" VerticalAlignment="Bottom" Margin="5 0 0 3"/> | |
</StackPanel> | |
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom"> | |
<TextBlock x:Name="label_pressure_rate" Text="Unhealthy" FontWeight="SemiBold"/> | |
<Image Source="/Resources/atmospheric.png" Width="18" Height="18" Margin="10 0 0 0"/> | |
</StackPanel> | |
<Slider x:Name="slider_pressure" IsEnabled="false" Style="{DynamicResource SliderStyle1}" Value="7" Maximum="10" Orientation="Vertical" HorizontalAlignment="Right" VerticalAlignment="Bottom" Height="90"/> | |
</Grid> | |
</Border> | |
</WrapPanel> | |
</StackPanel> | |
</Border> | |
</Grid> | |
</Window> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Windows; | |
using System.Windows.Input; | |
using Newtonsoft.Json; | |
using System.Net; | |
using System.Data.SqlTypes; | |
using System.Windows.Media.Imaging; | |
using System.Windows.Threading; | |
namespace WeatherApp | |
{ | |
/// <summary> | |
/// Interaction logic for MainWindow.xaml | |
/// </summary> | |
public partial class MainWindow : Window | |
{ | |
string city = "Surabaya"; | |
string api_key = "0a2e2179a81ac8b8d5037759ebaa6562"; | |
private DispatcherTimer timer; | |
public MainWindow() | |
{ | |
InitializeComponent(); | |
// Create and start the timer | |
timer = new DispatcherTimer(); | |
timer.Interval = TimeSpan.FromSeconds(1); | |
timer.Tick += Timer_Tick; | |
timer.Start(); | |
getData_fromAPI(); | |
} | |
private void Timer_Tick(object sender, EventArgs e) | |
{ | |
// Get the current time | |
DateTime currentTime = DateTime.Now; | |
// Format the time as a string in the format "09:00 PM" | |
string timeString = currentTime.ToString("dddd,\nhh:mm:ss tt"); | |
// Update the label with the time string | |
label_datetime.Text = timeString; | |
} | |
private void Border_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) | |
{ | |
if (e.ChangedButton == MouseButton.Left) | |
this.DragMove(); | |
} | |
private void textSearch_MouseDown(object sender, MouseButtonEventArgs e) | |
{ | |
txtSearch.Focus(); | |
} | |
private void txtSearch_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e) | |
{ | |
if (!string.IsNullOrEmpty(txtSearch.Text) && txtSearch.Text.Length > 0) | |
textSearch.Visibility = Visibility.Collapsed; | |
else | |
textSearch.Visibility = Visibility.Visible; | |
} | |
private void txtSearch_PreviewKeyDown(object sender, KeyEventArgs e) | |
{ | |
if (e.Key == Key.Enter) | |
{ | |
this.city = txtSearch.Text.ToString(); | |
getData_fromAPI(); | |
} | |
} | |
private async void getData_fromAPI() | |
{ | |
try | |
{ | |
using (WebClient webClient = new WebClient()) | |
{ | |
string url = string.Format("https://api.openweathermap.org/data/2.5/weather?q={0}&appid={1}", this.city, this.api_key); | |
string json = webClient.DownloadString(url); | |
WeatherData.root weatherData = new WeatherData.root(); | |
weatherData = JsonConvert.DeserializeObject<WeatherData.root>(json); | |
// update data | |
label_temp.Text = Math.Floor(weatherData.main.temp - 273.15).ToString() + "°c"; | |
label_weather.Text = toCamelCase((weatherData.weather[0].description).ToString()); | |
image_weather.Source = new BitmapImage(new Uri("https://openweathermap.org/img/w/" + weatherData.weather[0].icon + ".png")); | |
label_cloudiness.Text = String.Format("Cloudiness - {0}%", weatherData.clouds.all.ToString()); | |
label_city.Text = toCamelCase(this.city); | |
label_windSpeed.Text = (weatherData.wind.speed * 3.6).ToString("F2"); | |
label_windDirection.Text = ConvertWindDirection(weatherData.wind.deg); | |
label_sunrise.Text = UnixTimestampToLocalDateTime(weatherData.sys.sunrise).ToString("h:mm tt"); | |
label_sunset.Text = UnixTimestampToLocalDateTime(weatherData.sys.sunset).ToString("h:mm tt"); | |
label_humidity.Text = weatherData.main.humidity.ToString(); | |
label_humidity_rate.Text = GetHumidityCategory(weatherData.main.humidity); | |
slider_humidity.Value = weatherData.main.humidity / 10; | |
label_visibility.Text = (weatherData.visibility / 1000).ToString(); | |
label_visibility_rate.Text = GetVisibilityCategory(weatherData.visibility / 1000); | |
slider_visibility.Value = weatherData.visibility / 1000; | |
label_pressure.Text = weatherData.main.pressure.ToString(); | |
label_pressure_rate.Text = GetPressureCategory(weatherData.main.pressure); | |
slider_pressure.Value = 1 + (weatherData.main.pressure - 980) * (10 - 1) / (1030 - 980); | |
label_latitude.Text = weatherData.coord.lat.ToString(); | |
label_longitude.Text = weatherData.coord.lat.ToString(); | |
} | |
} | |
catch (Exception ex) | |
{ | |
// handle the exception | |
MessageBox.Show($"An error occurred: {ex.Message}"); | |
} | |
} | |
private string toCamelCase(string str) | |
{ | |
string[] words = str.Split(' '); | |
for (int i = 0; i < words.Length; i++) | |
{ | |
words[i] = char.ToUpper(words[i][0]) + words[i].Substring(1); | |
} | |
string camelCaseString = string.Join(" ", words); | |
return camelCaseString; | |
} | |
public static string ConvertWindDirection(double windDirectionInDegrees) | |
{ | |
string windDirectionCategory; | |
switch ((int)((windDirectionInDegrees / 22.5) + 0.5) % 16) | |
{ | |
case 0: | |
windDirectionCategory = "North"; | |
break; | |
case 1: | |
windDirectionCategory = "North-northeast"; | |
break; | |
case 2: | |
windDirectionCategory = "Northeast"; | |
break; | |
case 3: | |
windDirectionCategory = "East-northeast"; | |
break; | |
case 4: | |
windDirectionCategory = "East"; | |
break; | |
case 5: | |
windDirectionCategory = "East-southeast"; | |
break; | |
case 6: | |
windDirectionCategory = "Southeast"; | |
break; | |
case 7: | |
windDirectionCategory = "South-southeast"; | |
break; | |
case 8: | |
windDirectionCategory = "South"; | |
break; | |
case 9: | |
windDirectionCategory = "South-southwest"; | |
break; | |
case 10: | |
windDirectionCategory = "Southwest"; | |
break; | |
case 11: | |
windDirectionCategory = "West-southwest"; | |
break; | |
case 12: | |
windDirectionCategory = "West"; | |
break; | |
case 13: | |
windDirectionCategory = "West-northwest"; | |
break; | |
case 14: | |
windDirectionCategory = "Northwest"; | |
break; | |
case 15: | |
windDirectionCategory = "North-northwest"; | |
break; | |
default: | |
windDirectionCategory = "Unknown"; | |
break; | |
} | |
return windDirectionCategory; | |
} | |
public static DateTime UnixTimestampToLocalDateTime(double unixTimestamp) | |
{ | |
DateTimeOffset utcDateTimeOffset = DateTimeOffset.FromUnixTimeSeconds((long)unixTimestamp); | |
return utcDateTimeOffset.ToLocalTime().DateTime; | |
} | |
public static string GetHumidityCategory(double humidityPercentage) | |
{ | |
string category; | |
switch (humidityPercentage) | |
{ | |
case var h when h < 0 || h > 100: | |
category = "Invalid humidity percentage"; | |
break; | |
case var h when h <= 30: | |
category = "Dry"; | |
break; | |
case var h when h <= 60: | |
category = "Comfortable/Normal"; | |
break; | |
case var h when h <= 90: | |
category = "Humid"; | |
break; | |
default: | |
category = "Very humid"; | |
break; | |
} | |
return category; | |
} | |
public static string GetVisibilityCategory(double distanceInKilometers) | |
{ | |
string category; | |
switch (distanceInKilometers) | |
{ | |
case var d when d >= 0 && d < 0.1: | |
category = "Poor"; | |
break; | |
case var d when d >= 0.1 && d < 1: | |
category = "Moderate"; | |
break; | |
case var d when d >= 1 && d < 2: | |
category = "Fairly good"; | |
break; | |
case var d when d >= 2 && d < 5: | |
category = "Good"; | |
break; | |
case var d when d >= 5 && d < 10: | |
category = "Very good"; | |
break; | |
case var d when d >= 10: | |
category = "Excellent"; | |
break; | |
default: | |
category = "Invalid distance"; | |
break; | |
} | |
return category; | |
} | |
public static string GetPressureCategory(double pressureInHpa) | |
{ | |
string category; | |
switch (pressureInHpa) | |
{ | |
case var p when p < 980: | |
category = "Low"; | |
break; | |
case var p when p >= 980 && p < 1013: | |
category = "Normal"; | |
break; | |
case var p when p >= 1013 && p < 1030: | |
category = "High"; | |
break; | |
case var p when p >= 1030: | |
category = "Very high"; | |
break; | |
default: | |
category = "Invalid pressure"; | |
break; | |
} | |
return category; | |
} | |
private void btnExit_Click(object sender, RoutedEventArgs e) | |
{ | |
Application.Current.Shutdown(); | |
} | |
} | |
} |
Nama : Arief Badrus Sholeh
NRP : 5025201228
Kelas : PBKK B
Tahun : 2022/2023
Komentar
Posting Komentar