вторник, 25 июня 2013 г.

ScreenWeather 2.0 WPF

Немногим более года назад, я писал погодный информер в стиле Windows 8. К несчастью, спустя некоторое время, программа перестала работать по причине того, что Google закрыла свой погодный сервис. Времени (да и желания) переделывать у меня не было - учёба забирает все силы. Но вот, учебный год прошел и желание переписать приложение снова появилось. На этот раз я решил отказаться от Windows Forms и перешел на WPF. Не зря же пол года его изучал в универе.

WPF даёт нам массу возможностей. Во-первых, можно без труда связывать (биндить) данные и их представление. Во-вторых, можно проделывать кучу интересных вещей с элементами графического интерфейса: это и анимация, и трансформация и различные триггеры, стили, темы и т.п.
Но вернёмся к нашей погоде. Итак, первый погодный информер Google без лишнего шума закрылся, поэтому пришлось поискать другие хорошие сервисы. На отечественных форумах мне попалась ссылка на weather.ua с их бесплатным API без ограничений. Скажу честно, я был приятно удивлён работой, которую проделали эти ребята. Поэтому считаю своим долгом посоветовать всем читателям этот сервис.
Следующим погодным информером стал Yahoo Weather. Как по мне, он не очень, но добавил я его для количества.
Итак, теперь в программе 4 сервиса погоды: WWO, WUnderground, Weather.UA и Yahoo. Расскажу как работать с информерами.
Алгоритм такой:

  1. Сформировать URL-адрес с необходимыми параметрами. Например: http://api.wunderground.com/api/{KEY}/conditions/forecast/q/Ukraine/Kyiv.xml
  2. Послать запрос и получить ответ в виде текстовых XML-данных.
  3. Распарсить XML.
  4. Вывести полученные данные на экран.
Первый пункт прост - соединяем строки.
Второй пункт тоже лёгкий, всего одна функция:
/// 
/// Получение информации по URL.
/// Информация сохраняется в unprocessedData.
/// 
/// Ссылка, по которой необходимо взять информацию.
protected void GetData(string url) {
    var req = (HttpWebRequest) WebRequest.Create(url);
    req.Headers.Add("Accept-Language", Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName);
    HttpWebResponse response = null;
    try {
        response = (HttpWebResponse) req.GetResponse();
        Stream stream = response.GetResponseStream();
        if (encoding == null)
            encoding = Encoding.Default;
        if (stream != null) {
            var streamReader = new StreamReader(stream, encoding);
            unprocessedData = streamReader.ReadToEnd();
            streamReader.Close();
        }
    } catch (Exception ex) {
#if DEBUG
        System.Diagnostics.Debug.WriteLine(ex.ToString());
#endif
        unprocessedData = "";
    } finally {
        if (response != null) response.Close();
    }
}
Третий пункт тоже несложный, но самый нудный. Вот пример парсинга данных WUnderground:
private void ParseContent() {
    XmlDocument doc = new XmlDocument();
    doc.LoadXml(base.unprocessedData);
    XmlNodeList loc = doc.SelectNodes("response/current_observation/display_location/city");
    if (loc != null && loc.Count != 0) visibleLocation = loc[0].InnerText;
    ParseCurrentCondition(doc.SelectNodes("response/current_observation"));
    ParseDayInfo(doc.SelectNodes("response/forecast/simpleforecast/forecastdays/forecastday"));
}

private void ParseCurrentCondition(XmlNodeList conditionList) {
    currentCondition = new CurrentCondition();
    foreach (XmlNode node in conditionList) {
        currentCondition.Temperature = node.SelectSingleNode("temp_c").InnerText;
        currentCondition.Humidity = node.SelectSingleNode("relative_humidity").InnerText;
        currentCondition.WindInfo.Speed = node.SelectSingleNode("wind_kph").InnerText + " км/ч ";
        currentCondition.WindInfo.Direction = node.SelectSingleNode("wind_dir").InnerText;
        currentCondition.Summary = node.SelectSingleNode("weather").InnerText;
        currentCondition.Icon = base.DownloadImage(node.SelectSingleNode("icon_url").InnerText);
    }
}

private void ParseDayInfo(XmlNodeList weatherList) {
    dayInfo = new List<DayInfo>();

    foreach (XmlNode node in weatherList) {
        DayInfo info = new DayInfo();

        info.TemperatureMax = node.SelectSingleNode("high/celsius").InnerText;
        info.Temperature = node.SelectSingleNode("low/celsius").InnerText;
        info.Summary = node.SelectSingleNode("conditions").InnerText;
        info.Icon = base.DownloadImage(node.SelectSingleNode("icon_url").InnerText);
        info.WindInfo.Speed = node.SelectSingleNode("avewind/kph").InnerText + " км/ч ";
        info.WindInfo.Direction = node.SelectSingleNode("avewind/dir").InnerText;

        dayInfo.Add(info);
    }
}
Четвёртый пункт самый интересный, потому что можно вывести информацию как душе угодно. Тут уж полёт фантазии ограничивается лишь умениями и знаниями XAML-разметки.
Лично я создал UserControl для информации на каждый из четырёх дней. XAML:
<UserControl x:Class="ScreenWeatherWPF.DayInfoControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d" 
             d:DesignHeight="400" d:DesignWidth="250"
             Background="#967789B0" Margin="5">
    <StackPanel x:Name="Root" DataContext="{Binding}">
        <Viewbox StretchDirection="Both">
            <Label Name="Temperature"
                   HorizontalAlignment="Center" Foreground="White" FontSize="55">
                <Label.Content>
                    <TextBlock>
                        <TextBlock.Text>
                            <MultiBinding StringFormat="{}{0} / {1}">
                                <Binding Path="Temperature" />
                                <Binding Path="TemperatureMax" />
                            </MultiBinding>
                        </TextBlock.Text>
                    </TextBlock>
                </Label.Content>
            </Label>
        </Viewbox>
        <Image Name="Icon"
               HorizontalAlignment="Center"
               Source="{Binding Icon}"/>
        <Viewbox StretchDirection="DownOnly">
            <Label Name="Summary"
               HorizontalAlignment="Center"
               Foreground="White" FontSize="25"
               Content="{Binding Summary}" />
        </Viewbox>

        <Separator Background="#64FFFFFF" Margin="10"/>
        <Label Name="Wind"
               HorizontalAlignment="Center"
               Foreground="White" FontSize="25"
               Content="{Binding WindInfo}" />
        
    </StackPanel>
</UserControl>
C#:
public partial class DayInfoControl : UserControl {

    public DayInfo DayInfo {
        get {
            return dayInfo;
        }
        set {
            dayInfo = value;
            Root.DataContext = value;
        }
    }
    private DayInfo dayInfo;

    public DayInfoControl() {
        InitializeComponent();

        Loaded += (s, e) => { Icon.Width = ActualWidth / 3; };

        Root.DataContext = DayInfo;
    }
}

Вот, собственно, и всё. Программу можно скачать отсюда.
Для запуска нужен .NET Framework 4.0 или выше.

Комментариев нет:

Отправить комментарий