weekly-planner-wp/wpweeklyplanner_WP7/WeeklyGrid.cs

366 строки
12 KiB
C#

/**
* Copyright (c) 2011-2014 Microsoft Mobile. All rights reserved.
*
* Nokia, Nokia Connecting People, Nokia Developer, and HERE are trademarks
* and/or registered trademarks of Nokia Corporation. Other product and company
* names mentioned herein may be trademarks or trade names of their respective
* owners.
*
* See the license text file delivered with this project for more information.
*/
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Phone.Controls;
namespace wpweeklyplanner
{
// WeeklyGrid is a custom control for showing the view of a single day, including
// "hour slots" and events. Custom control is used here because especially with events
// the rendering can get quite complex since events can overlap each other. With a custom
// control also the handling of operations possible for events is easier since we can
// declare separate events for each operation which can then be executed elsewhere.
public class WeeklyGrid : Control
{
public event RoutedEventHandler AddEvent;
public event RoutedEventHandler SelectEvent;
public event RoutedEventHandler EditEvent;
public event RoutedEventHandler RemoveEvent;
public WeeklyGrid()
{
DefaultStyleKey = typeof(WeeklyGrid);
Loaded += new RoutedEventHandler(WeeklyGrid_Loaded);
Unloaded += new RoutedEventHandler(WeeklyGrid_Unloaded);
}
private void WeeklyGrid_Loaded(object sender, RoutedEventArgs e)
{
// Check if we have a proper data context set
MainViewModel model = DataContext as MainViewModel;
if (model != null)
{
ViewModel = model;
}
// Call Update() to set up view based on events
Update();
}
private void WeeklyGrid_Unloaded(object sender, RoutedEventArgs e)
{
// Clear the data context so we don't receive view model updates while we are not visible
ViewModel = null;
}
private MainViewModel viewModel;
public MainViewModel ViewModel
{
get
{
return viewModel;
}
set
{
if (value != viewModel)
{
if( viewModel != null )
{
viewModel.Items.CollectionChanged -= new NotifyCollectionChangedEventHandler(Items_CollectionChanged);
}
viewModel = value;
if (viewModel != null)
{
viewModel.Items.CollectionChanged += new NotifyCollectionChangedEventHandler(Items_CollectionChanged);
}
}
}
}
public static readonly DependencyProperty DayOfWeekProperty =
DependencyProperty.Register("DayOfWeek", typeof(int), typeof(WeeklyGrid), null);
public int DayOfWeek
{
get
{
return (int)GetValue(DayOfWeekProperty);
}
set
{
SetValue(DayOfWeekProperty, value);
}
}
public static readonly DependencyProperty StartHourProperty =
DependencyProperty.Register("StartHour", typeof(int), typeof(WeeklyGrid), null);
public int StartHour
{
get
{
return (int)GetValue(StartHourProperty);
}
set
{
SetValue(StartHourProperty, value);
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
grid = (Grid)GetTemplateChild("LayoutRoot");
// Subscribe event handlers to all row items
foreach (UIElement child in grid.Children)
{
Button item = child as Button;
if (item != null)
{
item.Tap -= new EventHandler<System.Windows.Input.GestureEventArgs>(Item_Click);
item.Tap += new EventHandler<System.Windows.Input.GestureEventArgs>(Item_Click);
}
}
}
public void Update()
{
if (grid == null)
{
return;
}
// Remove all events from the grid
IList<UIElement> removables = grid.Children.OfType<WeeklyEvent>().Cast<UIElement>().ToList();
foreach (UIElement removable in removables)
{
grid.Children.Remove(removable);
}
// If view model is not set we don't have any events to show
if (ViewModel == null || ViewModel.Items == null)
{
return;
}
// Discover all events for this day
List<EventViewModel> events = new List<EventViewModel>();
foreach (EventViewModel model in ViewModel.Items)
{
if (model.DayOfWeek == DayOfWeek)
{
events.Add(model);
}
}
// Sort the events (required to draw overlapping events more easily)
events.Sort(EventsCompare);
// Generate matching UI controls for event (with recursion for overlapping events)
gfxNameCounter = 0;
while (events.Count > 0)
{
List<EventViewModel> temp = new List<EventViewModel>(events);
int maximumLevel = DiscoverMaxLevel(temp, 0);
GenerateEventControl(events, 0, maximumLevel);
}
}
private static int EventsCompare(EventViewModel x, EventViewModel y)
{
if (x.StartHour > y.StartHour)
{
return 1;
}
else if (x.StartHour < y.StartHour)
{
return -1;
}
else
{
if (x.Duration > y.Duration)
{
return -1;
}
else if (x.Duration < y.Duration)
{
return 1;
}
else
{
return 0;
}
}
}
private void GenerateEventControl(List<EventViewModel> events, int level, int maxLevel)
{
EventViewModel firstEvent = events[0];
events.RemoveAt(0);
bool hasOverlaps = false;
while (events.Count > 0 && EventsOverlap(firstEvent, events[0]))
{
hasOverlaps = true;
GenerateEventControl(events, level + 1, maxLevel);
}
WeeklyEvent control = new WeeklyEvent();
control.Style = Application.Current.Resources["custom:WeeklyEvent"] as Style;
control.Content = firstEvent.Text;
Grid.SetRow(control, firstEvent.StartHour - StartHour);
Grid.SetColumn(control, 1);
Grid.SetRowSpan(control, firstEvent.Duration);
double availableWidth = grid.ColumnDefinitions[1].ActualWidth;
double widthPerEvent = availableWidth / (maxLevel + 1);
control.Margin = new Thickness(level * widthPerEvent, 0, 0, 0);
control.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
control.ViewModel = firstEvent;
if (gfxNameCounter % 2 == 0)
{
control.GfxName = "green";
}
else
{
control.GfxName = "turquoise";
}
++gfxNameCounter;
ContextMenu menu = new ContextMenu();
menu.IsZoomEnabled = false;
MenuItem edit = new MenuItem();
edit.Header = wpweeklyplanner.AppResources.ButtonEdit;
edit.Click += new RoutedEventHandler(Event_Edit);
edit.DataContext = control;
MenuItem remove = new MenuItem();
remove.Header = wpweeklyplanner.AppResources.ButtonRemove;
remove.Click += new RoutedEventHandler(Event_Remove);
remove.DataContext = control;
menu.Items.Add(edit);
menu.Items.Add(remove);
ContextMenuService.SetContextMenu(control, menu);
if (hasOverlaps)
{
control.Width = widthPerEvent;
}
else
{
control.Width = availableWidth - level * widthPerEvent;
}
control.Click += new RoutedEventHandler(Event_Click);
grid.Children.Add(control);
}
private int DiscoverMaxLevel(List<EventViewModel> events, int level)
{
EventViewModel firstEvent = events[0];
events.RemoveAt(0);
int maximumLevel = level;
while (events.Count > 0 && EventsOverlap(firstEvent, events[0]))
{
maximumLevel = Math.Max(maximumLevel, DiscoverMaxLevel(events, level + 1));
}
return maximumLevel;
}
private bool EventsOverlap(EventViewModel firstEvent, EventViewModel secondEvent)
{
// Events are sorted, so this function presumes that starting hour of firstEvent <= secondEvent
return (firstEvent.StartHour + firstEvent.Duration > secondEvent.StartHour);
}
private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Update();
}
private void Event_Click(object sender, RoutedEventArgs e)
{
WeeklyEvent ev = sender as WeeklyEvent;
if (SelectEvent != null)
{
SelectEvent(this, new SelectEventArgs(ev.ViewModel));
}
}
private void Event_Edit(object sender, RoutedEventArgs e)
{
WeeklyEvent ev = (sender as MenuItem).DataContext as WeeklyEvent;
if (EditEvent != null)
{
EditEvent(this, new SelectEventArgs(ev.ViewModel));
}
}
private void Event_Remove(object sender, RoutedEventArgs e)
{
WeeklyEvent ev = (sender as MenuItem).DataContext as WeeklyEvent;
if (RemoveEvent != null)
{
RemoveEvent(this, new SelectEventArgs(ev.ViewModel));
}
}
private void Item_Click(object sender, System.Windows.Input.GestureEventArgs e)
{
Button control = sender as Button;
int row = Grid.GetRow(control);
int startHour = StartHour + row;
if (AddEvent != null)
{
AddEvent( this, new AddEventArgs( DayOfWeek, startHour ) );
}
}
private Grid grid;
private int gfxNameCounter;
}
public class AddEventArgs : RoutedEventArgs
{
public AddEventArgs(int dayOfWeek, int startHour)
{
this.dayOfWeek = dayOfWeek;
this.startHour = startHour;
}
private int dayOfWeek;
public int DayOfWeek { get { return dayOfWeek; } }
private int startHour;
public int StartHour { get { return startHour; } }
}
public class SelectEventArgs : RoutedEventArgs
{
public SelectEventArgs(EventViewModel viewModel)
{
this.viewModel = viewModel;
}
private EventViewModel viewModel;
public EventViewModel ViewModel { get { return viewModel; } }
}
}