Для проекта, над которым мы работаем, нам нужен простой (и бесплатный) пользовательский контроль WP7-графа. Данные, которые визуализирует пользовательский контроль, должны быть привязаны к данным, и каждый раз, когда поступают новые данные, график должен обновляться на лету. В следующем уроке мы покажем, как создать такой элемент управления с нуля (обратите внимание, что обычно это также должен работать в проектах Silverlight или WPF).
Демо-решение этого урока можно скачать здесь .
График Пользовательский контроль: Xaml-код
Сначала мы создаем новый UserControl. Часть xaml довольно пуста, мы добавим две вещи:
- Стиль-ресурс, который мы будем использовать при рисовании графика. В этом примере мы хотим, чтобы графические линии имели тот же цвет, что и цвет акцента телефона.
- Пустой gridcontrol, в котором будет нарисован график
<UserControl x:Class="GraphControlDemo.GraphControl"> <UserControl.Resources> <Style TargetType="Polyline" x:Key="graphLine"> <Setter Property="Stroke" Value= "{StaticResource PhoneAccentColor}" /> <Setter Property="StrokeThickness" Value="2" /> </Style> </UserControl.Resources> <Grid x:Name="GraphGrid" /> </UserControl>
Если вы хотите более необычный пользовательский контроль, вы можете добавить красивую рамку или любой другой материал, который вам нравится. Пока где-то есть gridcontrol с именем «GraphGrid», все будет хорошо.
График Пользовательский контроль: код позади, обзор
Кодовая часть нашего usercontrol состоит из двух важных частей:
- Метод DrawGraph (), который будет … ждать его … рисовать график внутри gridcontrol
- Свойство зависимости (DP), содержащее коллекцию значений (целых), из которых мы хотим построить график.
public partial class GraphControl : UserControl { public GraphControl() { InitializeComponent(); Loaded += (s,e) => DrawGraph(); } public void DrawGraph() { //... } // GraphValue DependencyProperty }
Также обратите внимание, что мы явно вызываем метод DrawGraph () после загрузки usercontrol (строка 6). Если мы этого не сделаем, наш график будет отрисован с самого начала, даже если свойство зависимостей уже имеет привязанную коллекцию.
График Пользовательский контроль: код позади, рисование графика
Чтобы нарисовать реальный график, мы скопируем и отредактируем очень простое решение с помощью «stoneTip», который ответит на этот вопрос StackOverflow . Обязательно ознакомьтесь с исходным кодом в StackOverflow, если вам нужна дополнительная информация о том, что на самом деле происходит.
Мы разделяем метод DrawGraph () на две части: одна, которая будет рисовать график, другая часть используется, если к элементу управления не привязаны никакие данные, которые затем просто покажут «Нет данных» внутри элемента управления:
public void DrawGraph() { if (GraphValues != null && GraphValues.Count(i => true) > 0) { //Draw graph lines } else { GraphGrid.Children.Clear(); GraphGrid.Children.Add(new TextBlock() { Text = "No data", VerticalAlignment = VerticalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center }); } }
Фактическая часть чертежа (внутри if) выглядит следующим образом:
int maxGraphVal = GraphValues.Max(p => p); int minGraphVal = GraphValues.Min(p => p); int gridUnit = (int)GraphGrid.ActualWidth / GraphValues.Count(i => true); int graphHeight = (int)GraphGrid.ActualHeight; var sparkLine = new Polyline() { Style = (Style)Resources["graphLine"] }; //Process each value and compute place in graph int currentX = 0; decimal graphValRange = maxGraphVal - minGraphVal; foreach (int graphVal in GraphValues) { decimal graphY2Val = (graphVal - minGraphVal) / graphValRange; double graphY2ValDouble = Convert.ToDouble(graphHeight - (graphY2Val * graphHeight)); sparkLine.Points.Add(new Point(currentX, graphY2ValDouble)); currentX += gridUnit; } // Add the spark line to the graph GraphGrid.Children.Clear(); GraphGrid.Children.Add(sparkLine);
Пользовательский элемент управления графиком: выделение кода, добавление свойства зависимости данных источника
Наконец, нам нужно определить свойство зависимости, с которым пользователи нашего элемента управления могут связывать точки графа. Для этого мы будем использовать ObservableCollection (хотя я совершенно уверен, что это не самое легкое решение):
public ObservableCollection<int> GraphValues { get { return (ObservableCollection<int>)GetValue(GraphDataProperty); } set { SetValue(GraphDataProperty, value); DrawGraph(); } } public static readonly DependencyProperty GraphDataProperty = DependencyProperty.Register("GraphValues", typeof(ObservableCollection<int>), typeof(GraphControl), new PropertyMetadata(null, OnGraphValuesChanged));
Чего сейчас не хватает, так это части, которая будет автоматически обновлять наш график каждый раз, когда новые значения добавляются в коллекцию или изменения привязки. Эта часть вдохновлена решением «Джош Дж», который отвечает на этот вопрос .
private static void OnGraphValuesChanged (DependencyObject d, DependencyPropertyChangedEventArgs e) { GraphControl me = d as GraphControl; if (me != null) { var old = e.OldValue as ObservableCollection<int>; if (old != null) old.CollectionChanged -= me.OnGraphValueChanged; var n = e.NewValue as ObservableCollection<int>; if (n != null) n.CollectionChanged += me.OnGraphValueChanged ; } } private void OnGraphValueChanged (object sender, NotifyCollectionChangedEventArgs e) { DrawGraph(); }
Использование элемента управления
С UserControl готов и ждет. Теперь мы можем легко добавить наш новый «GraphControl» где угодно.
Сначала добавьте пространство имен на страницу, на которую мы хотим добавить элемент управления:
xmlns:myGraph="clr-namespace:GraphControlDemo"
Затем добавьте элемент управления:
<myGraph:GraphControl GraphValues="{Binding SampleData}"/>
В выделенном фрагменте кода теперь нам нужно только определить некоторую ObservableCollectioncalled SampleData, которая содержит точки, которые будут нарисованы на графике. Например:
public ObservableCollection<int> SampleData { get; set; } public MainPage() { InitializeComponent(); SampleData = new ObservableCollection<int>() { 1, 2, 4 }; LayoutRoot.DataContext = this; }
Проверить, работает ли автообновление. Вы можете добавить кнопку, которая добавляет случайную выборку в коллекцию при каждом нажатии:
private void AddRandSampleBtnClick(object sender, RoutedEventArgs e) { Random r= new Random(); SampleData.Add(r.Next(-10, 50)); }
И это упаковка!