/// <summary> /// Sets Source for the first available image control on each new photo that's been added. /// </summary> /// <param name="layoutGrid">Layout grid whose Photo collection just got a new photo.</param> /// <param name="photo">The photo being added.</param> private static void OnNewPhotoAdded(LayoutGrid layoutGrid, Photo photo) { System.Diagnostics.Debug.WriteLine($"PhotoCollectionChanged -> Added {photo}"); // Go through every image control and set the Source for the first one that doesn't host a photo/image, i.e. whose Source == null for (int i = 0; i < layoutGrid.images.Count; i++) { if (layoutGrid.images[i].Source == null) { // Resets image control's scale and translate transforms so it doesn't use the last photo's values layoutGrid.images[i].RenderTransform = new MatrixTransform(); if (layoutGrid.PhotoType == BitmapType.Thumbnail) { layoutGrid.images[i].Source = photo.Thumbnail; } else { // Display the Sand clock until the photo is loaded layoutGrid.images[i].Source = new BitmapImage(new Uri(Environment.CurrentDirectory + @"..\..\..\Images\Sand clock.png")); // This layout grid is the one the user will be using for manipulating individual photos ThreadPool.QueueUserWorkItem((obj) => { photo.RefreshBitmapSource(BitmapType.PreviewBitmap); Application.Current.Dispatcher.BeginInvoke((Action)(() => layoutGrid.images[i].Source = photo.PreviewBitmap), System.Windows.Threading.DispatcherPriority.ApplicationIdle); }); } // Each individual photo can only be displayed once in the grid, so no need for continuation of the for loop -> the photo has been added return; } } }
/// <summary> /// Adds a new row to the specified layout grid. If the new row is at first position /// adds a new column first and a sub grid, which will hold the new row. /// </summary> /// <param name="row">Index of the new row.</param> /// <param name="col">Column in which the row will reside.</param> /// <param name="layoutGrid">Layout grid that is getting a new row.</param> private static void AddRowFirst(int row, int col, LayoutGrid layoutGrid) { Grid subGrid; // Check if a new column should be added (row with index 0 is next) if (row == 0) { // Add new column to the layout grid layoutGrid.ColumnDefinitions.Add(new ColumnDefinition()); // Add sub grid to the layout grid and set its column to the current column subGrid = new Grid(); Grid.SetColumn(subGrid, col); layoutGrid.Children.Add(subGrid); } else { // Sub grid was already created and added to the layout grid's children subGrid = layoutGrid.Children[col] as Grid; } // Add new row to the sub grid subGrid.RowDefinitions.Add(new RowDefinition()); // Remove first manipulation border from the layotu grid and add it to the sub grid (the border cannot be a logical child of 2 panles) ManipulationBorder border = layoutGrid.borders[0]; layoutGrid.borders.RemoveAt(0); subGrid.Children.Add(border); // Set row for the manipulation border Grid.SetRow(border, row); }
/// <summary> /// Binds the count of photos to the private CellCount dependency property. /// </summary> /// <param name="d">LayoutGrid for which the binding is done.</param> /// <param name="collection">The collection of photos in Photos property.</param> private static void BindCellCountToPhotosCount(LayoutGrid layoutGrid, ObservableCollection <Photo> collection) { if (layoutGrid != null) { Binding cellCountBinding = new Binding("Count"); cellCountBinding.Source = collection; layoutGrid.SetBinding(CellCountProperty, cellCountBinding); } }
/// <summary> /// Rearanges images by moving every image source 1 image control to the left practically removing /// the photo on the starting image and making the last 2 image control Sources duplicates. /// </summary> /// <param name="layoutGrid">Layout grid whose images are being rearanged.</param> /// <param name="photoIndex">Starting index of the image whose Source is changing.</param> private static void RearangeImages(LayoutGrid layoutGrid, int photoIndex) { for (int i = photoIndex; i < layoutGrid.images.Count - 1; i++) { // Sets render transform of the next image to the current one, because its Source will hold the next image's Source as well layoutGrid.images[i].RenderTransform = layoutGrid.images[i + 1].RenderTransform; layoutGrid.images[i].Source = layoutGrid.images[i + 1].Source; } }
/// <summary> /// Resolves addition, removal and swapping/replacing of photos in the layout grid /// and binds cell count (private dependency property) to the count of photos in the new collection. /// </summary> private static void OnPhotosChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { LayoutGrid layoutGrid = d as LayoutGrid; if (layoutGrid == null) { return; } NotifyCollectionChangedEventHandler handler = (sender, collectionChangedEventArgs) => { var action = collectionChangedEventArgs.Action; Photo photo; switch (action) { case NotifyCollectionChangedAction.Add: photo = (Photo)collectionChangedEventArgs.NewItems[0]; OnNewPhotoAdded(layoutGrid, photo); break; case NotifyCollectionChangedAction.Remove: photo = (Photo)collectionChangedEventArgs.OldItems[0]; OnPhotoRemoved(layoutGrid, photo); break; case NotifyCollectionChangedAction.Move: // TODO Determine how to resolve Replace function, as there must always be 2 Move operations for swapping/replacing // Or if Remove, Add and another Remove, Add will be used to swap 2 Photos break; } }; var oldCollection = e.OldValue as ObservableCollection <Photo>; if (oldCollection != null) { // TODO Determine if this is necessary for releasing memory, i.e. if this can really cause memory leak oldCollection.Clear(); oldCollection.CollectionChanged -= handler; GC.Collect(); } var newCollection = e.NewValue as ObservableCollection <Photo>; if (newCollection != null) { newCollection.CollectionChanged += handler; } BindCellCountToPhotosCount(layoutGrid, newCollection); }
/// <summary> /// Checks if the photo type is Thumbnail and if it is disables manipulation of all image controls for the layout grid. /// </summary> private static void OnPhotoTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { LayoutGrid layoutGrid = d as LayoutGrid; BitmapType type = (BitmapType)e.NewValue; if (layoutGrid != null && type == BitmapType.Thumbnail) { // This layout grid is one of many displaying possible layouts that the user can choose from // and it must not have the option of manipulating individual photos foreach (Image img in layoutGrid.images) { img.IsHitTestVisible = false; } } }
/// <summary> /// Updates layout grid by specified action. Either by adding new rows/columns for the new photo or removing them. /// </summary> /// <param name="layoutGrid">LayoutGrid whose layout is updated.</param> /// <param name="action">Action with which to update the layout.</param> private static void UpdateLayout(LayoutGrid layoutGrid, LayoutAction action) { int cellCount = layoutGrid.CellCount; byte rowCount = layoutGrid.LayoutMatrix[0]; byte columnCount = layoutGrid.LayoutMatrix[1]; // Depending on the layout orientation, whether new columns are added first or new rows, different methods are called if (layoutGrid.LayoutOrientation == LayoutOrientation.ColumnFirst) { UpdateLayoutColumnFirst(rowCount, columnCount, cellCount, layoutGrid, action); } else { UpdateLayoutRowFirst(rowCount, columnCount, cellCount, layoutGrid, action); } }
/// <summary> /// Call update to layout depending on whether the cell count increased or decreased. /// </summary> private static void OnCellCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { LayoutGrid layoutGrid = d as LayoutGrid; if (layoutGrid != null) { if ((int)e.NewValue > (int)e.OldValue) { // Cell count increased, meaning some photo was added UpdateLayout(layoutGrid, LayoutAction.Addition); } else { // Cell count decreased, meaning some photo was removed UpdateLayout(layoutGrid, LayoutAction.Removal); } } }
/// <summary> /// Sets Source to null for the image that previously held the specified photo. /// </summary> /// <param name="layoutGrid">Layout grid from whose Photo collection the photo is removed.</param> /// <param name="photo">The photo being removed.</param> private static void OnPhotoRemoved(LayoutGrid layoutGrid, Photo photo) { System.Diagnostics.Debug.WriteLine($"PhotoCollectionChanged -> Removed {photo}"); // Go through every image control and see if it hosts the photo (either a Thumbnail or a PreviewBitmap version) and set its Source to null for (int i = 0; i < layoutGrid.images.Count; i++) { if (layoutGrid.images[i].Source != null && (layoutGrid.images[i].Source == photo.Thumbnail || layoutGrid.images[i].Source == photo.PreviewBitmap)) { RearangeImages(layoutGrid, i); layoutGrid.images[layoutGrid.images.Count - 1].Source = null; // After rearange the last image is redundant (in most cases it's a duplicate of the next to last) photo.PreviewBitmap = null; // Each individual photo can only be displayed once in the grid, so no need for continuation of the for loop -> the one photo has been removed return; } } }
/// <summary> /// Changes LayoutOrientation if certain combination of LayoutMatrix and LayoutOrientation values are met. /// </summary> private static void OnLayoutMatrixChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { LayoutGrid layoutGrid = d as LayoutGrid; if (layoutGrid == null) { return; } if ((byte[])e.NewValue == Constants.JustColumnsLayout && layoutGrid.LayoutOrientation == LayoutOrientation.RowFirst) { // LayoutMatrix is set to JustColumnsLayout and infers just a columns layout, so LayoutOrientation must be changed to ColumnFirst layoutGrid.LayoutOrientation = LayoutOrientation.ColumnFirst; } else if ((byte[])e.NewValue == Constants.JustRowsLayout && layoutGrid.LayoutOrientation == LayoutOrientation.ColumnFirst) { // LayoutMatrix is set to JustRowssLayout and infers just a rows layout, so LayoutOrientation must be changed to RowFirst layoutGrid.LayoutOrientation = LayoutOrientation.RowFirst; } }
/// <summary> /// Coerces the MinPhotoCount property to acceptable value. If the value is below 0 coerces it to 0, /// and if it's above <see cref="Constants.MaxSelectedPhotos"/> limit is set to that value. /// </summary> /// <param name="d">LayoutGrid whose property is being coerced.</param> /// <param name="baseValue">Specified value for the MinPhotoCount property.</param> /// <returns>Returns 0 if the value is negative, or <see cref="Constants.MaxSelectedPhotos"/> if it's above that value.</returns> private static object OnMinPhotoCountCoerceValue(DependencyObject d, object baseValue) { LayoutGrid layoutGrid = d as LayoutGrid; if (layoutGrid == null) { // TODO Decide if validation should be used instead and throw an exception return(0); } int count = (int)baseValue; if (count < 0) { count = 0; } else if (count > Constants.MaxSelectedPhotos) { count = Constants.MaxSelectedPhotos; } return(count); }
/// <summary> /// Removes the last row from the specified layout grid. If the row is at first position /// removes the whole column with its sub grid. /// </summary> /// <param name="rowCount">Max row count of the final layout.</param> /// <param name="cellCount">Number of cells - photos currently in the grid.</param> /// <param name="col">Column in which the row resides.</param> /// <param name="layoutGrid">Layout grid from which a row is removed.</param> private static void RemoveRowFirst(byte rowCount, int cellCount, int col, LayoutGrid layoutGrid) { Grid subGrid; ManipulationBorder border; // Check if with the last row addition a new column was also added (with a new sub grid) if (cellCount % rowCount == 0) { // Column that was added in the last row addition col = cellCount / rowCount; // Sub grid that was added to the above column in the last row addition subGrid = layoutGrid.Children[col] as Grid; // Remove sub grid's only child, which is a manipulation border, and give it back to the layout grid borders (insert at first position) border = (ManipulationBorder)(subGrid.Children[0]); subGrid.Children.RemoveAt(0); layoutGrid.borders.Insert(0, border); // Remove sub grid from layout grid (along with its row) layoutGrid.Children.Remove(subGrid); // Remove the last column layoutGrid.ColumnDefinitions.RemoveAt(col); } else // Entire column doesn't need to be removed, just its last row { // Sub grid of the layout grid which holds a row that needs to be removed subGrid = layoutGrid.Children[col] as Grid; // Remove sub grid's last child, which is a manipulation border, and give it back to the layout grid borderds (insert at first position) border = (ManipulationBorder)(subGrid.Children[subGrid.RowDefinitions.Count - 1]); subGrid.Children.RemoveAt(subGrid.RowDefinitions.Count - 1); layoutGrid.borders.Insert(0, border); // Remove the last row from the sub grid subGrid.RowDefinitions.RemoveAt(subGrid.RowDefinitions.Count - 1); } }
/// <summary> /// Coerces the orientation based on the LayoutMatrix of the layout grid. /// </summary> private static object CoerceLayoutOrientationValue(DependencyObject d, object baseValue) { LayoutGrid layoutGrid = d as LayoutGrid; if (layoutGrid == null) { return(null); } LayoutOrientation orientation = (LayoutOrientation)baseValue; if (layoutGrid.LayoutMatrix == Constants.JustColumnsLayout) { // LayoutMatrix is set to JustColumnsLayout so LayoutOrientation must be set to ColumnFirst orientation = LayoutOrientation.ColumnFirst; } else if (layoutGrid.LayoutMatrix == Constants.JustRowsLayout) { // LayoutMatrix is set to JustRowsLayout so LayoutOrientation must be set to RowFirst orientation = LayoutOrientation.RowFirst; } return(orientation); }
/// <summary> /// Updates layout grid's layout, by rows, based on the action - either with addition of a new row or with the removal of the last one. /// </summary> /// <param name="rowCount">Max rows count of the final layout.</param> /// <param name="columnCount">Max column count of the final layout.</param> /// <param name="cellCount">Number of cells - photos currently in the grid.</param> /// <param name="layoutGrid">Layout grid whose layout is updating.</param> /// <param name="action">Action which is done upon a layout.</param> private static void UpdateLayoutRowFirst(byte rowCount, byte columnCount, int cellCount, LayoutGrid layoutGrid, LayoutAction action) { int col = (cellCount - 1) / rowCount; int row = (cellCount - 1) % rowCount; if (action == LayoutAction.Addition) { AddRowFirst(row, col, layoutGrid); } else { RemoveRowFirst(rowCount, cellCount, col, layoutGrid); } }
/// <summary> /// Updates layout grid's layout, by columns, based on the action - either with addition of a new column or with the removal of the last one. /// </summary> /// <param name="rowCount">Max row count of the final layout.</param> /// <param name="columnCount">Max column count of the final layout.</param> /// <param name="cellCount">Number of cells - photos currently in the grid.</param> /// <param name="layoutGrid">Layout grid whose layout is updating.</param> /// <param name="action">Action which is done upon a layout.</param> private static void UpdateLayoutColumnFirst(byte rowCount, byte columnCount, int cellCount, LayoutGrid layoutGrid, LayoutAction action) { int row = (cellCount - 1) / columnCount; // Find which row needs a column added/removed int col = (cellCount - 1) % columnCount; // Find index of the next column in the above row if (action == LayoutAction.Addition) { AddColumnFirst(row, col, layoutGrid); } else { RemoveColumnFirst(columnCount, cellCount, row, layoutGrid); } }