/// <summary> Propagate the current value of this control forward from this point across the current set of filtered images </summary> /// <param name="key"></param> public void Forward(string key, bool checkForZero) { string valueToCopy = this.dbData.RowGetValueFromDataLabel(key); valueToCopy = valueToCopy.Trim(); int number_images_affected = this.dbData.dataTable.Rows.Count - this.dbData.CurrentRow - 1; if (number_images_affected == 0) { // Nothing to propagate. Note that we shouldn't really see this, as the menu shouldn't be highlit if we are on the last image // But just in case... DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.IconType = MessageBoxImage.Exclamation; dlgMB.ButtonType = MessageBoxButton.OK; dlgMB.MessageTitle = "Nothing to copy forward."; dlgMB.MessageReason = "As you are on the last image, there are no other images after this."; dlgMB.ShowDialog(); return; } if (this.CopyForward(valueToCopy, number_images_affected.ToString(), checkForZero) != true) return; // Update. Note that we start on the next row, as we are copying from the current row. this.dbData.RowsUpdateFromRowToRowFilteredView(key, valueToCopy, this.dbData.CurrentRow + 1, this.dbData.ImageCount - 1); }
/// <summary> /// Export all the database data associated with the filtered view to the CSV file indicated in the file path so that spreadsheet applications (like Excel) can display it. /// </summary> /// <param name="db"></param> /// <param name="filepath"></param> public static void ExportDataAsCSV(DBData db, string filepath) { TextWriter tw = new StreamWriter(filepath, false); try { // Write the header as defined by the data labels in the template file // If the data label is an empty string, we use the label instead. string header = ""; string label; string datalabel; List<string> datalabels = new List<string>(); for (int i = 0; i < db.templateTable.Rows.Count; i++) { label = (string)db.templateTable.Rows[i][Constants.LABEL]; datalabel = (string)db.templateTable.Rows[i][Constants.DATALABEL]; header += addColumn(getLabel(label, datalabel)); // get a list of datalabels so we can add columns in the order that matches the current template table order if (Constants.ID != datalabel) datalabels.Add(datalabel); } tw.WriteLine(header); // For each row in the data table, write out the columns in the same order as the // data labels in the template file for (int i = 0; i < db.dataTable.Rows.Count; i++) { string row = ""; foreach (string dataLabel in datalabels) { row += addColumn((string)db.dataTable.Rows[i][dataLabel]); } tw.WriteLine(row); } } catch { // Can't write the spreadsheet file DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.IconType = MessageBoxImage.Error; dlgMB.ButtonType = MessageBoxButton.OK; dlgMB.MessageTitle = "Can't write the spreadsheet file."; dlgMB.MessageProblem = "The following file can't be written: " + filepath + "."; dlgMB.MessageReason = "You may already have it open in Excel or another application."; dlgMB.MessageSolution = "If the file is open in another application, close it and try again."; dlgMB.ShowDialog(); } tw.Close(); }
// Ask the user to confirm value propagation from the last value private bool? PropagateFromLastValue(String text, string number_images_affected) { text = text.Trim(); DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.IconType = MessageBoxImage.Question; dlgMB.ButtonType = MessageBoxButton.YesNo; dlgMB.MessageTitle = "Please confirm 'Propagate to Here' for this field."; dlgMB.MessageProblem = "The 'Propagate to Here' operation is not undoable, and can overwrite existing values."; dlgMB.MessageReason = "\u2022 The last non-empty value \u00AB" + text + "\u00BB was seen " + number_images_affected + " images back." + Environment.NewLine; dlgMB.MessageReason += "\u2022 That field's value will be copied across all images between that image and this one in this filtered image set"; dlgMB.MessageResult = "If you select yes: " + Environment.NewLine; dlgMB.MessageResult = "\u2022 " + number_images_affected + " images will be affected."; return dlgMB.ShowDialog(); }
// Ask the user to confirm value propagation from the last value private bool? CopyForward(String text, string number, bool checkForZero) { text = text.Trim(); DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.IconType = MessageBoxImage.Question; dlgMB.ButtonType = MessageBoxButton.YesNo; dlgMB.MessageTitle = "Please confirm 'Copy Forward' for this field."; dlgMB.MessageProblem = "The Copy Forward operation is not undoable, and can overwrite existing values."; dlgMB.MessageResult = "If you select yes, this operation will:" + Environment.NewLine; if (!checkForZero && text.Equals("")) { dlgMB.MessageResult += "\u2022 copy the (empty) value \u00AB" + text + "\u00BB in this field from here to the last image of your filtered images."; } else { dlgMB.MessageResult += "\u2022 copy the value \u00AB" + text + "\u00BB in this field from here to the last image of your filtered images."; } dlgMB.MessageResult += Environment.NewLine + "\u2022 over-write any existing data values in those fields"; dlgMB.MessageResult += Environment.NewLine + "\u2022 will affect " + number + " images."; return (dlgMB.ShowDialog()); }
// Ask the user to confirm value propagation private bool? CopyCurrentValueToAll(String text, string number, bool checkForZero) { text = text.Trim(); DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.IconType = MessageBoxImage.Question; dlgMB.ButtonType = MessageBoxButton.YesNo; dlgMB.MessageTitle = "Please confirm 'Copy to All' for this field."; dlgMB.MessageProblem = "The Copy to All operation is not undoable, and can overwrite existing values."; dlgMB.MessageResult = "If you select yes, this operation will:" + Environment.NewLine; if (!checkForZero && text.Equals("")) { dlgMB.MessageResult += "\u2022 clear this field across all " + number + " of your filtered images."; } else { dlgMB.MessageResult += "\u2022 set this field to \u00AB" + text + "\u00BB across all " + number + " of your filtered images."; } dlgMB.MessageResult += Environment.NewLine + "\u2022 over-write any existing data values in those fields"; return (dlgMB.ShowDialog()); }
// Checks for updates by comparing the current version number with a version stored on the Timelapse website in an xml file. public static void GetAndParseVersion(Window win, bool showNoUpdatesMessage) { Version newVersion = null; // if a newVersion variable, we will store the version info from xml file string url = ""; // THE URL where the new version is located string changes = ""; // A list of changes held in the xml file XmlTextReader reader = null; try { // provide the XmlTextReader with the URL of our xml document reader = new XmlTextReader(Constants.URL_CONTAINING_LATEST_VERSION_INFO); reader.MoveToContent(); // simply (and easily) skip the junk at the beginning // internal - as the XmlTextReader moves only forward, we save current xml element name in elementName variable. // When we parse a text node, we refer to elementName to check what was the node name string elementName = ""; // we check if the xml starts with a proper "ourfancyapp" element node if ((reader.NodeType == XmlNodeType.Element) && (reader.Name == Constants.APPLICATION_NAME)) { while (reader.Read()) { // when we find an element node, we remember its name if (reader.NodeType == XmlNodeType.Element) elementName = reader.Name; else { // for text nodes... if ((reader.NodeType == XmlNodeType.Text) && (reader.HasValue)) { // we check what the name of the node was switch (elementName) { case "version": // we keep the version info in xxx.xxx.xxx.xxx format as the Version class does the parsing for us newVersion = new Version(reader.Value); break; case "url": url = reader.Value; break; case "changes": changes = reader.Value; break; } } } } } } catch (Exception) { return; } finally { if (reader != null) reader.Close(); } // get the running version Version curVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; // compare the versions if (curVersion.CompareTo(newVersion) < 0) { // ask the user if he would like to download the new version DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.MessageTitle = "A new version of Timelapse available."; dlgMB.MessageProblem = "You a running an old version of Timelapse: version " + curVersion.ToString(); dlgMB.MessageReason = "A new version of Timelapse is available: version " + newVersion.ToString(); dlgMB.MessageSolution = "Select 'Yes' to go to the Timelapse website and download it."; dlgMB.MessageResult = "The new version will contain these changes and more:" ; dlgMB.MessageResult += changes; dlgMB.MessageHint = "\u2022 We recommend downloading the latest version." + Environment.NewLine; dlgMB.MessageHint += "\u2022 To see all changes, go to http://saul.cpsc.ucalgary.ca/timelapse. Select 'Version history' from the side bar."; dlgMB.IconType = MessageBoxImage.Exclamation; dlgMB.ButtonType = MessageBoxButton.YesNo; bool? msg_result = dlgMB.ShowDialog(); // Set the filter to show all images and a valid image if (msg_result == true) { // navigate the default web browser to our app homepage (the url comes from the xml content) System.Diagnostics.Process.Start(url); } } else if (showNoUpdatesMessage) { DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.MessageTitle = "No updates to Timelapse are available."; dlgMB.MessageReason = "You a running the latest version of Timelapse, version: " + curVersion.ToString(); dlgMB.IconType = MessageBoxImage.Information; dlgMB.ButtonType = MessageBoxButton.OK; bool? msg_result = dlgMB.ShowDialog(); } }
private void Window_Loaded(object sender, RoutedEventArgs e) { string executable_folder = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); if (!File.Exists(System.IO.Path.Combine(executable_folder, "System.Data.SQLite.dll"))) { DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.MessageTitle = "Timelapse needs to be in its original downloaded folder"; dlgMB.MessageProblem = "The Timelapse Programs won't run properly as it was not correctly installed."; dlgMB.MessageReason = "When you downloaded Timelapse, it was in a folder with several other files and folders it needs. You probably dragged Timelapse out of that folder."; dlgMB.MessageSolution = "Put the Timelapse programs back in its original folder, or download it again."; dlgMB.MessageHint = "If you want to access these programs from elsewhere, create a shortcut to it." + Environment.NewLine; dlgMB.MessageHint += "1. From its original folder, right-click the Timelapse program icon and select 'Create Shortcut' from the menu." + Environment.NewLine; dlgMB.MessageHint += "2. Drag the shortcut icon to the location of your choice."; dlgMB.IconType = MessageBoxImage.Error; dlgMB.ShowDialog(); Application.Current.Shutdown(); } else { CheckForUpdate.GetAndParseVersion(this, false); } // FOR MY DEBUGGING ONLY: THIS STARTS THE SYSTEM WITH THE LOAD MENU ITEM SELECTED loadImagesFromSources(); //OPENS THE MENU AUTOMATICALLY }
private bool SetImageFilterAndIndex(int index, int filter) { // Change the filter to reflect what the user selected. Update the menu state accordingly // Set the checked status of the radio button menu items to the filter. if (filter == (int)Constants.ImageQualityFilters.All) // All images { this.dbData.GetImagesAll(); StatusBarUpdate.View(this.statusBar, "all images."); MenuItemViewSetSelected((int)Constants.ImageQualityFilters.All); if (null != this.dlgDataView) { this.dlgDataView.RefreshDataTable(); // If its displayed, update the window that shows the filtered view data base } } else if (filter == (int)Constants.ImageQualityFilters.Ok) // Light images { if (this.dbData.GetImagesAllButDarkAndCorrupted()) { StatusBarUpdate.View(this.statusBar, "light images."); MenuItemViewSetSelected((int)Constants.ImageQualityFilters.Ok); if (null != this.dlgDataView) { this.dlgDataView.RefreshDataTable(); // If its displayed, update the window that shows the filtered view data base } } else { // It really should never get here, as the menu option for filtering by light images will be disabled if there aren't any. // Still,... StatusBarUpdate.Message(this.statusBar, "no light images to display."); DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.MessageTitle = "Light filter selected, but no images are marked as light."; dlgMB.MessageProblem = "None of the images in this image set are light images, so nothing can be shown."; dlgMB.MessageReason = "None of the images have their 'ImageQuality' field set to OK."; dlgMB.MessageResult = "The filter will not be applied."; dlgMB.MessageHint = "If you have images that you think should be marked as 'light', set its ImageQUality field to OK."; dlgMB.IconType = MessageBoxImage.Information; dlgMB.ButtonType = MessageBoxButton.OK; dlgMB.ShowDialog(); if (this.state.imageFilter == (int)Constants.ImageQualityFilters.Ok) return SetImageFilterAndIndex(0, (int)Constants.ImageQualityFilters.All); MenuItemViewSetSelected(this.state.imageFilter); return false; } } else if (filter == (int)Constants.ImageQualityFilters.Corrupted) // Corrupted images { if (this.dbData.GetImagesCorrupted()) { StatusBarUpdate.View(this.statusBar, "corrupted images."); MenuItemViewSetSelected((int)Constants.ImageQualityFilters.Corrupted); if (null != this.dlgDataView) { this.dlgDataView.RefreshDataTable(); // If its displaye, update the window that shows the filtered view data base } } else { // It really should never get here, as the menu option for filtering by corrupted images will be disabled if there aren't any. // Still,... StatusBarUpdate.Message(this.statusBar, "no corrupted images to display."); DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.MessageTitle = "Corrupted filter selected, but no images are marked as corrupted."; dlgMB.MessageProblem = "None of the images in this image set are corrupted images, so nothing can be shown."; dlgMB.MessageReason = "None of the images have their 'ImageQuality' field set to Corrupted."; dlgMB.MessageResult = "The filter will not be applied."; dlgMB.MessageHint = "If you have images that you think should be marked as 'Corrupted', set its ImageQUality field to Corrupted."; dlgMB.IconType = MessageBoxImage.Information; dlgMB.ButtonType = MessageBoxButton.OK; dlgMB.ShowDialog(); if (this.state.imageFilter == (int)Constants.ImageQualityFilters.Corrupted) return SetImageFilterAndIndex(0, (int)Constants.ImageQualityFilters.All); MenuItemViewSetSelected(this.state.imageFilter); return false; } } else if (filter == (int)Constants.ImageQualityFilters.Dark) // Dark images { if (this.dbData.GetImagesDark()) { StatusBarUpdate.View(this.statusBar, "dark images."); MenuItemViewSetSelected((int)Constants.ImageQualityFilters.Dark); if (null != this.dlgDataView) { this.dlgDataView.RefreshDataTable(); // If its displayed, update the window that shows the filtered view data base } } else { // It really should never get here, as the menu option for filtering by dark images will be disabled if there aren't any. // Still,... StatusBarUpdate.Message(this.statusBar, "no dark images to display."); DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.MessageTitle = "Dark filter selected, but no images are marked as dark."; dlgMB.MessageProblem = "None of the images in this image set are dark images, so nothing can be shown."; dlgMB.MessageReason = "None of the images have their 'ImageQuality' field set to Dark."; dlgMB.MessageResult = "The filter will not be applied."; dlgMB.MessageHint = "If you have images that you think should be marked as 'Dark', set its ImageQUality field to Dark."; dlgMB.IconType = MessageBoxImage.Information; dlgMB.ButtonType = MessageBoxButton.OK; dlgMB.ShowDialog(); if (this.state.imageFilter == (int)Constants.ImageQualityFilters.Dark) return SetImageFilterAndIndex(0, (int)Constants.ImageQualityFilters.All); MenuItemViewSetSelected(this.state.imageFilter); return false; } } else if (filter == (int)Constants.ImageQualityFilters.Missing) // Missing images { if (this.dbData.GetImagesMissing()) { StatusBarUpdate.View(this.statusBar, "missing images."); MenuItemViewSetSelected((int)Constants.ImageQualityFilters.Missing); if (null != this.dlgDataView) { this.dlgDataView.RefreshDataTable(); // If its displayed, update the window that shows the filtered view data base } } else { // It really should never get here, as the menu option for filtering by missing images will be disabled if there aren't any. // Still,... StatusBarUpdate.Message(this.statusBar, "no missing images to display."); DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.MessageTitle = "Missing filter selected, but no images are marked as missing."; dlgMB.MessageProblem = "None of the images in this image set are missing images, so nothing can be shown."; dlgMB.MessageReason = "None of the images have their 'ImageQuality' field set to Missing."; dlgMB.MessageResult = "The filter will not be applied."; dlgMB.MessageHint = "If you have images that you think should be marked as 'Missing', set its ImageQUality field to Missing."; dlgMB.IconType = MessageBoxImage.Information; dlgMB.ButtonType = MessageBoxButton.OK; dlgMB.ShowDialog(); if (this.state.imageFilter == (int)Constants.ImageQualityFilters.Missing) return SetImageFilterAndIndex(0, (int)Constants.ImageQualityFilters.All); MenuItemViewSetSelected(this.state.imageFilter); return false; } } else if (filter == (int)Constants.ImageQualityFilters.MarkedForDeletion) // Images marked for deletion { if (this.dbData.GetImagesMarkedForDeletion()) { StatusBarUpdate.View(this.statusBar, "images marked for deletion."); MenuItemViewSetSelected((int)Constants.ImageQualityFilters.MarkedForDeletion); if (null != this.dlgDataView) { dlgDataView.RefreshDataTable(); this.MenuItemViewFilteredDatabaseContents_Click(null, null); //Regenerate the DataView if needed } } else { // It really should never get here, as the menu option for filtering by images marked for deletion will be disabled if there aren't any. // Still,... StatusBarUpdate.Message(this.statusBar, "No images marked for deletion to display."); DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.MessageTitle = "Delete filter selected, but no images are marked for deletion"; dlgMB.MessageProblem = "None of the images in this image set are marked for deletion, so nothing can be shown."; dlgMB.MessageReason = "None of the images have their 'Delete?' field checkmarked."; dlgMB.MessageResult = "The filter will not be applied."; dlgMB.MessageHint = "If you have images that you think should be marked for deletion, checkmark its Delete? field."; dlgMB.IconType = MessageBoxImage.Information; dlgMB.ButtonType = MessageBoxButton.OK; dlgMB.ShowDialog(); if (this.state.imageFilter == (int)Constants.ImageQualityFilters.MarkedForDeletion) return SetImageFilterAndIndex(0, (int)Constants.ImageQualityFilters.All); MenuItemViewSetSelected(this.state.imageFilter); return false; } } else if (filter == (int)Constants.ImageQualityFilters.Custom) // Custom Filter { if (this.customfilter.QueryResultCount != 0) { StatusBarUpdate.View(this.statusBar, "images matching your custom filter."); MenuItemViewSetSelected((int)Constants.ImageQualityFilters.Custom); if (null != this.dlgDataView) { this.dlgDataView.RefreshDataTable(); // If its displayed, update the window that shows the filtered view data base } } else { // It really should never get here, as the dialog for filtering images shouldn't allow it, but... // Still,... StatusBarUpdate.Message(this.statusBar, "no images to display."); DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.MessageTitle = "Custom filter selected, but no images match the specified search."; dlgMB.MessageProblem = "None of the images in this image set match the specified search, so nothing can be shown."; dlgMB.MessageResult = "The filter will not be applied."; dlgMB.MessageHint = "Try to create another custom filter."; dlgMB.IconType = MessageBoxImage.Information; dlgMB.ButtonType = MessageBoxButton.OK; dlgMB.ShowDialog(); if (this.state.imageFilter == (int)Constants.ImageQualityFilters.Missing) return SetImageFilterAndIndex(0, (int)Constants.ImageQualityFilters.All); MenuItemViewSetSelected(this.state.imageFilter); return false; } } // Go to the first row. // We may want to change this to try to go to last saved image, if its in this filtered view. this.dbData.ToDataRowIndex (index); // After a filter change, set the slider to represent the index and the count of the current filter this.sldrImageNavigatorEnableCallback(false); this.sldrImageNavigator.Maximum = this.dbData.ImageCount - 1; // Reset the slider to the size of images in this set this.sldrImageNavigator.Value = this.dbData.CurrentRow; // Update the status bar accordingly StatusBarUpdate.CurrentImageNumber(statusBar, this.dbData.CurrentRow + 1); // We add 1 because its a 0-based list StatusBarUpdate.TotalCount(statusBar, this.dbData.ImageCount); showImage(this.dbData.CurrentRow); this.sldrImageNavigatorEnableCallback(true); this.state.imageFilter = filter; // Remember the current filter return true; }
// Load all the jpg images found in the folder Boolean LoadDByScanningImageFolder() { DateTimeHandler dateTimeHandler = new DateTimeHandler(); FileInfo fileInfo; ProgressState progressState = new ProgressState(); ImageProperties imgprop; // Collect the image properties for for the 2nd pass... List<ImageProperties> imgprop_list = new List<ImageProperties>(); Dictionary<String, String> dataline = new Dictionary<String, String>(); // Populate the data for the image Dictionary<String, String> markerline = new Dictionary<String, String>(); // Populate the markers database, where each key column corresponds to each key counter in the datatable int index = 0; string datalabel = ""; bool ambiguous_daymonth_order = false; this.imageFilePaths = new DirectoryInfo(this.dbData.FolderPath).GetFiles("*.jpg"); int count = imageFilePaths.Length; if (count == 0) return false; // Create the database and its table before we can load any data into it // Open a connection to the template DB bool result = this.dbData.CreateDB(template); // We generate the data user interface controls from the template description after the database has been created from the template myControls.GenerateControls(dbData); MenuItemControlsInSeparateWindow_Click(this.MenuItemControlsInSeparateWindow, null); //this.ControlsInMainWindow(); this.dbData.CreateTables(); this.dbData.CreateLookupTables(); // We want to show previews of the frames to the user as they are individually loaded // Because WPF uses a scene graph, we have to do this by a background worker, as this forces the update var bgw = new BackgroundWorker() { WorkerReportsProgress = true }; bgw.DoWork += (ow, ea) => { // this runs on the background thread; its written as an anonymous delegate //We need to invoke this to allow updates on the UI this.Dispatcher.Invoke(new Action(() => {; // First, change the UI this.helpControl.Visibility = System.Windows.Visibility.Collapsed; Feedback(null, 0, "Examining images..."); })); // First pass: Examine images to extract its basic properties BitmapSource bmap; BitmapSource corruptedbmp = BitmapFrame.Create(new Uri("pack://application:,,/Resources/corrupted.jpg"));; for (int i = 0; i < count; i++) { fileInfo = imageFilePaths[i]; bmap = null; imgprop = new ImageProperties(); imgprop.Name = fileInfo.Name; imgprop.Folder = Utilities.GetFolderNameFromFolderPath(this.FolderPath); try { // Create the bitmap and determine its ImageQuality bmap = BitmapFrame.Create(new Uri(fileInfo.FullName), BitmapCreateOptions.None, BitmapCacheOption.None); bool dark = PixelBitmap.IsDark(bmap, this.darkPixelThreshold, this.darkPixelRatioThreshold); // imgprop.ImageQuality = (dark) ? (int)Constants.ImageQualityFilters.Dark : (int)Constants.ImageQualityFilters.Ok; } catch { bmap = corruptedbmp; imgprop.ImageQuality = (int) Constants.ImageQualityFilters.Corrupted; } // Get the data from the metadata BitmapMetadata meta = (BitmapMetadata) bmap.Metadata; imgprop.DateMetadata = meta.DateTaken; // For some reason, different versions of Windows treat creation time and modification time differently, // giving inconsisten values. So I just check both and take the lesser of the two. DateTime time1 = File.GetCreationTime(fileInfo.FullName); DateTime time2 = File.GetLastWriteTime(fileInfo.FullName); imgprop.DateFileCreation = (DateTime.Compare (time1, time2) < 0) ? time1 : time2; //string time3 = (meta.DateTaken == null) ? "null" : meta.DateTaken.ToString(); //Debug.Print(fileInfo.Name + " " + time1.ToString() + " " + time2.ToString() + " " + time3); imgprop.ID = index + 1; // its plus 1 as the Database IDs start at 1 rather than 0 imgprop_list.Add (imgprop); index++; int progress = Convert.ToInt32(Convert.ToDouble(index) / Convert.ToDouble(count) * 100); if (index == 1 || (index % 1 == 0) ) { progressState.Message = String.Format ("{0}/{1}: Examining {2}", i, count, imgprop.Name); progressState.Bmap = bmap; bgw.ReportProgress(progress, progressState); } else { progressState.Bmap = null; } } // Second pass: Determine dates ... This can be pretty quick, so we don't really need to give any feedback on it. progressState.Message = "Second pass"; progressState.Bmap = null; bgw.ReportProgress(0, progressState); ambiguous_daymonth_order = DateTimeHandler.VerifyAndUpdateDates(imgprop_list); // Third pass: Update database // TODO This is pretty slow... a good place to make it more efficient by adding multiple values in one shot // We need to get a list of which columns are counters vs notes or fixed coices, // as we will shortly have to initialize them to some defaults List<string> CounterList = new List<string>(); List<string> Notes_and_FixedChoicesList = new List<string>(); List<string> FlagsList = new List<string>(); for (int i = 0; i < this.dbData.dataTable.Columns.Count; i++) { datalabel = this.dbData.dataTable.Columns[i].ColumnName; string type = (string)this.dbData.TypeFromKey[datalabel]; if (null == type) continue; // Column must be the ID, which we skip over as its not a key. if (type.Equals(Constants.COUNTER)) CounterList.Add(datalabel); else if (type.Equals(Constants.NOTE) || type.Equals(Constants.FIXEDCHOICE)) Notes_and_FixedChoicesList.Add(datalabel); else if (type.Equals(Constants.FLAG)) FlagsList.Add(datalabel); } // Create a dataline from the image properties, add it to a list of data lines, // then do a multiple insert of the list of datalines to the database List <Dictionary<string, string>> dataline_list ; //= new List <Dictionary<string, string>> (); List<Dictionary<string, string>> markerline_list ; //= new List<Dictionary<string, string>>(); //for (int i = 0; i < imgprop_list.Count; i++) const int interval = 100; for (int j = 0; j < imgprop_list.Count; j++) { // Create a dataline from the image properties, add it to a list of data lines, // then do a multiple insert of the list of datalines to the database dataline_list = new List<Dictionary<string, string>>(); markerline_list = new List<Dictionary<string, string>>(); for (int i = j; ( (i < (j + interval)) && (i < imgprop_list.Count) ); i++) { // THE PROBLEM IS THAT WE ARE NOT ADDING THESE VALUES IN THE SAME ORDER AS THE TABLE // THEY MUST BE IN THE SAME ORDER IE, AS IN THE COLUMNS. This case statement just fills up // the dataline in the same order as the template table. // It assumes that the key is always the first column dataline = new Dictionary<string, string>(); markerline = new Dictionary<string, string>(); //dataline.Add(Constants.ID, "NULL"); // Add the ID. Its Null to force autoincrement // markerline.Add(Constants.ID, (i+1).ToString()); for (int col = 0; col < dbData.dataTable.Columns.Count; col++) // Fill up each column in order { string col_datalabel = dbData.dataTable.Columns[col].ColumnName; string type = (string) dbData.TypeFromKey [col_datalabel]; if (null == type) continue; // a null will be returned from the ID, as we don't add it to the typefromkey hash. switch (type) { case Constants.FILE: // Add The File name datalabel = (string)this.dbData.DataLabelFromType[Constants.FILE]; dataline.Add(datalabel, imgprop_list[i].Name); break; case Constants.FOLDER: // Add The Folder name datalabel = (string)this.dbData.DataLabelFromType[Constants.FOLDER]; dataline.Add(datalabel, imgprop_list[i].Folder); break; case Constants.DATE: // Add the date datalabel = (string)this.dbData.DataLabelFromType[Constants.DATE]; dataline.Add(datalabel, imgprop_list[i].FinalDate); break; case Constants.TIME: // Add the time datalabel = (string)this.dbData.DataLabelFromType[Constants.TIME]; dataline.Add(datalabel, imgprop_list[i].FinalTime); break; case Constants.IMAGEQUALITY: // Add the Image Quality datalabel = (string)this.dbData.DataLabelFromType[Constants.IMAGEQUALITY]; string str = Constants.IMAGEQUALITY_OK; if (imgprop_list[i].ImageQuality == (int)Constants.ImageQualityFilters.Dark) str = Constants.IMAGEQUALITY_DARK; else if (imgprop_list[i].ImageQuality == (int)Constants.ImageQualityFilters.Corrupted) str = Constants.IMAGEQUALITY_CORRUPTED; dataline.Add(datalabel, str); break; case Constants.DELETEFLAG: // Add the Delete flag datalabel = (string)this.dbData.DataLabelFromType[Constants.DELETEFLAG]; dataline.Add(datalabel, this.dbData.TemplateGetDefault(datalabel)); // Default as specified in the template file, which should be "false" break; case Constants.NOTE: // Find and then Add the Note or Fixed Choice case Constants.FIXEDCHOICE: // Now initialize notes, counters, and fixed choices to the defaults foreach (string tkey in Notes_and_FixedChoicesList) { if (col_datalabel.Equals (tkey)) dataline.Add(tkey, this.dbData.TemplateGetDefault(tkey) ); // Default as specified in the template file } break; case Constants.FLAG: // Now initialize flags to the defaults foreach (string tkey in FlagsList) { if (col_datalabel.Equals(tkey)) dataline.Add(tkey, this.dbData.TemplateGetDefault(tkey)); // Default as specified in the template file } break; case Constants.COUNTER: foreach (string tkey in CounterList) { if (col_datalabel.Equals(tkey)) { dataline.Add(tkey, this.dbData.TemplateGetDefault(tkey)); // Default as specified in the template file markerline.Add(tkey, ""); // TODO ASSUMES THAT MARKER LIST IS IN SAME ORDER AS COUNTERS. THIS MAY NOT BE CORRECT ONCE WE SWITCH ROWS, SO SHOULD DO THIS SEPARATELY } } break; default: Debug.Print("Shouldn't ever reach here!"); break; } } dataline_list.Add(dataline); if (markerline.Count > 0) markerline_list.Add(markerline); index = i; } this.dbData.InsertMultipleRows(Constants.TABLEDATA, dataline_list); this.dbData.InsertMultipleRows(Constants.TABLEMARKERS, markerline_list); j = j + interval - 1; // Get the bitmap again to show it if (imgprop_list[index].ImageQuality == (int)Constants.ImageQualityFilters.Corrupted) bmap = corruptedbmp; else bmap = BitmapFrame.Create(new Uri(System.IO.Path.Combine(this.dbData.FolderPath, imgprop_list[index].Name)), BitmapCreateOptions.None, BitmapCacheOption.None); // Show progress. Since its slow, we may as well do it every update int progress2 = Convert.ToInt32(Convert.ToDouble(index) / Convert.ToDouble(count) * 100); progressState.Message = String.Format("{0}/{1}: Adding {2}", index, count, imgprop_list[index].Name); progressState.Bmap = bmap; bgw.ReportProgress(progress2, progressState); } // this.dbData.AddNewRow(dataline); // this.dbData.AddNewRow(markerline, Constants.TABLEMARKERS); }; bgw.ProgressChanged += (o, ea) => { // this gets called on the UI thread ProgressState progstate = (ProgressState)ea.UserState; Feedback (progressState.Bmap, ea.ProgressPercentage, progressState.Message); this.feedbackCtl.Visibility = System.Windows.Visibility.Visible; }; bgw.RunWorkerCompleted += (o, ea) => { // this.dbData.GetImagesAll(); // Now load up the data table // Get rid of the feedback panel, and show the main interface this.feedbackCtl.Visibility = Visibility.Collapsed; this.feedbackCtl.ShowImage = null; this.markableCanvas.Visibility = Visibility.Visible; // Finally warn the user if there are any ambiguous dates in terms of day/month or month/day order if (ambiguous_daymonth_order) { DlgMessageBox dlgMB = new DlgMessageBox (); dlgMB.MessageTitle = "Timelapse was unsure about the month / day order of your image's dates"; dlgMB.MessageProblem = "Timelapse is extracting the dates from your images. However, it cannot tell if the dates are in day/month order, or month/day order."; dlgMB.MessageReason = "Image date formats can be ambiguous. For example, is 2015/03/05 March 5 or May 3?"; dlgMB.MessageSolution = "If Timelapse gets it wrong, you can correct the dates by choosing" + Environment.NewLine; dlgMB.MessageSolution += "\u2022 Edit Menu -> Dates -> Swap Day and Month."; dlgMB.MessageHint = "If you are unsure about the correct date, try the following." + Environment.NewLine; dlgMB.MessageHint += "\u2022 If your camera prints the date on the image, check that." + Environment.NewLine; dlgMB.MessageHint += "\u2022 Look at the images to see what season it is (e.g., winter vs. summer)." + Environment.NewLine; dlgMB.MessageHint += "\u2022 Examine the creation date of the image file." + Environment.NewLine; dlgMB.MessageHint += "\u2022 Check your own records."; dlgMB.ButtonType = MessageBoxButton.OK; dlgMB.IconType = MessageBoxImage.Information; dlgMB.ShowDialog(); } LoadComplete(true); // If we want to import old data from the ImageData.xml file, we can do it here... // Check to see if there is an ImageData.xml file in here. If there is, ask the user // if we want to load the data from that... if (File.Exists(System.IO.Path.Combine(this.FolderPath, Constants.XMLDATAFILENAME))) { DlgImportImageDataXMLFile dlg = new DlgImportImageDataXMLFile(); dlg.Owner = this; bool? result3 = dlg.ShowDialog(); if (result3 == true) { ImageDataXML.Read(System.IO.Path.Combine(this.FolderPath, Constants.XMLDATAFILENAME), dbData.templateTable, dbData); SetImageFilterAndIndex(this.dbData.State_Row, this.dbData.State_Filter); // to regenerate the controls and markers for this image } } }; bgw.RunWorkerAsync(); return true; }
private void MenuItemRereadDatesfromImages_Click(object sender, RoutedEventArgs e) { // If we are not in the filter all view, or if its a corrupt image, tell the person. Selecting ok will shift the views.. if (this.state.imageFilter != (int)Constants.ImageQualityFilters.All) { DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.MessageTitle = "Re-read the dates from the images..."; dlgMB.MessageProblem = "To re-read dates from the images, Timelapse must first be filtered to view All Images (normally set in the Filter menu)"; dlgMB.MessageSolution = "Select 'Ok' for Timelapse to set the filter to 'All Images'."; dlgMB.IconType = MessageBoxImage.Exclamation; dlgMB.ButtonType = MessageBoxButton.OKCancel; bool? msg_result = dlgMB.ShowDialog(); // Set the filter to show all images and a valid image if (msg_result == true) { SetImageFilterAndIndex(0, (int)Constants.ImageQualityFilters.All); // Set it to all images int row = dbData.RowFindNextDisplayableImage(1); // Start at Row 1, as they are numbered from 1 onwards... if (row >= 0) showImage(row); } else return; } DlgDateRereadDatesFromImages dlg = new DlgDateRereadDatesFromImages(this.dbData); dlg.Owner = this; bool? result = dlg.ShowDialog(); if (result == true) { this.showImage(dbData.CurrentRow); } }
// Populate a data field from metadata (example metadata displayed from the currently selected image) private void MenuItemPopulateFieldFromMetaData_Click(object sender, RoutedEventArgs e) { // If we are not in the filter all view, or if its a corrupt image or deleted image, tell the person. Selecting ok will shift the filter.. if (dbData.RowIsImageDisplayable() == false || this.state.imageFilter != (int)Constants.ImageQualityFilters.All) { DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.MessageTitle = "Populate a Data Field with Image Metadata of your Choosing..."; dlgMB.MessageProblem = "To populate a data field with image metadata of your choosing, Timelapse must first" + Environment.NewLine; dlgMB.MessageProblem += "\u2022 be filtered to view All Images (normally set in the Filter menu)" + Environment.NewLine; dlgMB.MessageProblem += "\u2022 be displaying a valid image"; dlgMB.MessageSolution = "Select 'Ok' for Timelapse to do the above actions for you."; dlgMB.IconType = MessageBoxImage.Exclamation; dlgMB.ButtonType = MessageBoxButton.OKCancel; bool? msg_result = dlgMB.ShowDialog(); // Set the filter to show all images and a valid image if (msg_result == true) { SetImageFilterAndIndex(0, (int)Constants.ImageQualityFilters.All); // Set it to all images int row = dbData.RowFindNextDisplayableImage(1); // Start at Row 1, as they are numbered from 1 onwards... if (row >= 0) showImage(row); } else return; } DlgPopulateFieldWithMetadata dlg = new DlgPopulateFieldWithMetadata(this.dbData, this.dbData.RowGetValueFromType(Constants.FILE), this.FolderPath); dlg.Owner = this; bool? result = dlg.ShowDialog(); if (result == true) { this.showImage(dbData.CurrentRow); this.state.isContentChanged = true; } }
private void MenuItemOptionsDarkImagesThreshold_Click(object sender, RoutedEventArgs e) { // If we are not in the filter all view, or if its a corrupt image, tell the person. Selecting ok will shift the views.. if (this.state.imageFilter != (int)Constants.ImageQualityFilters.All) { DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.MessageTitle = "Customize the threshold for determining dark images..."; dlgMB.MessageProblem = "To customize the threshold for determining dark images, Timelapse must first be filtered to view All Images (normally set in the Filter menu)."; dlgMB.MessageSolution = "Select 'Ok' for Timelapse to set the filter to 'All Images'."; dlgMB.IconType = MessageBoxImage.Exclamation; dlgMB.ButtonType = MessageBoxButton.OKCancel; bool? msg_result = dlgMB.ShowDialog(); // Set the filter to show all images and a valid image if (msg_result == true) { SetImageFilterAndIndex(0, (int)Constants.ImageQualityFilters.All); // Set it to all images int row = dbData.RowFindNextDisplayableImage(1); // Start at Row 1, as they are numbered from 1 onwards... if (row >= 0) showImage(row); } else return; } DlgOptionsDarkImagesThreshold dlg = new DlgOptionsDarkImagesThreshold(this.dbData, this.darkPixelThreshold, this.darkPixelRatioThreshold) ; dlg.Owner = this; bool? result = dlg.ShowDialog(); if (result == true) { this.state.isContentChanged = true; } }
/// <summary> /// Export the current image to the folder selected by the user via a folder browser dialog. /// and provide feedback in the status bar if done. /// </summary> private void MenuItemExportThisImage_Click(object sender, RoutedEventArgs e) { if (!this.dbData.RowIsImageDisplayable()) { DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.IconType = MessageBoxImage.Error; dlgMB.ButtonType = MessageBoxButton.OK; dlgMB.MessageTitle = "Can't export this image!"; dlgMB.MessageProblem = "We can't export the currently displayed image."; dlgMB.MessageReason = "It is likely a corrupted or missing image."; dlgMB.MessageSolution = "Make sure you have navigated to, and are displaying, a valid image before you try to export it."; dlgMB.ShowDialog(); return; } //Get the file name of the current image string sourceFile = this.dbData.RowGetValueFromType(Constants.FILE); // Set up a Folder Browser with some instructions var dialog = new System.Windows.Forms.SaveFileDialog(); dialog.Title = "Export a copy of the currently displayed image"; dialog.Filter = "JPeg Image|*.jpg"; dialog.FileName = sourceFile; dialog.OverwritePrompt = true; // Display the Folder Browser dialog System.Windows.Forms.DialogResult result = dialog.ShowDialog(); if (result == System.Windows.Forms.DialogResult.OK) { // Set the source and destination file names, including the complete path string sourceFileName = System.IO.Path.Combine(this.FolderPath, sourceFile); string destFileName = dialog.FileName; // Try to copy the source file to the destination, overwriting the destination file if it already exists. // And giving some feedback about its success (or failure) try { System.IO.File.Copy(sourceFileName, destFileName, true); StatusBarUpdate.Message(this.statusBar, sourceFile + " copied to " + destFileName); } catch { StatusBarUpdate.Message(this.statusBar, "Copy failed for some reason!"); } } }
/// <summary> Delete all images marked for deletion, and optionally the data associated with those images. /// Deleted images are actually moved to a backup folder.</summary> private void MenuItemDeleteImages_Click(object sender, RoutedEventArgs e) { int filter = this.state.imageFilter; int currentrow = this.dbData.CurrentRow; MenuItem mi = sender as MenuItem; DataTable deletedTable = this.dbData.GetDataTableOfImagesMarkedForDeletion(); if (null==deletedTable) { // It really should never get here, as this menu will be disabled if there aren't any images to delete. // Still,... DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.MessageTitle = "No images are marked for deletion"; dlgMB.MessageProblem = "You are trying to delete images marked for deletion, but none of the images have their 'Delete?' field checkmarked."; dlgMB.MessageHint = "If you have images that you think should be deleted, checkmark its Delete? field."; dlgMB.IconType = MessageBoxImage.Information; dlgMB.ButtonType = MessageBoxButton.OK; dlgMB.ShowDialog(); return; } DlgDeleteImages dlg; if (mi.Name.Equals ("MenuItemDeleteImages")) dlg = new DlgDeleteImages(this.dbData, deletedTable, this.FolderPath, false); // don't delete data else dlg = new DlgDeleteImages(this.dbData, deletedTable, this.FolderPath, true); // delete data dlg.Owner = this; bool? result = dlg.ShowDialog(); if (result == true) { this.SetImageFilterAndIndex(currentrow, filter); this.showImage(this.dbData.CurrentRow, false); } }
/// <summary> Correct for daylight savings time</summary> private void MenuItemCorrectDaylightSavings_Click(object sender, RoutedEventArgs e) { // If we are not in the filter all view, or if its a corrupt image, tell the person. Selecting ok will shift the views.. if (dbData.RowIsImageDisplayable() == false || this.state.imageFilter != (int)Constants.ImageQualityFilters.All) { if (this.state.imageFilter != (int)Constants.ImageQualityFilters.All) { DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.MessageTitle = "Can't correct for daylight savings time..."; dlgMB.MessageProblem = "To correct for daylight savings time:" + Environment.NewLine; dlgMB.MessageProblem += "\u2022 Timelapse must first be filtered to view All Images (normally set in the Filter menu)" + Environment.NewLine; dlgMB.MessageProblem += "\u2022 The displayed image should also be the one at the daylight savings time threshold."; dlgMB.MessageSolution = "Select 'Ok' for Timelapse to set the filter to 'All Images', and try again."; dlgMB.MessageHint = "For this correction to work properly, you should navigate and display the image that is at the daylight savings time threshold."; dlgMB.IconType = MessageBoxImage.Exclamation; dlgMB.ButtonType = MessageBoxButton.OKCancel; bool? msg_result = dlgMB.ShowDialog(); // Set the filter to show all images and then go to the first image if (msg_result == true) { SetImageFilterAndIndex(0, (int)Constants.ImageQualityFilters.All); // Set it to all images showImage(0); } } else // Just a corrupted image { DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.MessageTitle = "Can't correct for daylight savings time..."; dlgMB.MessageProblem = "This is a corrupted image. "; dlgMB.MessageSolution = "To correct for daylight savings time, you need to:" + Environment.NewLine; dlgMB.MessageSolution += "\u2022 be displaying an image with a valid date "; dlgMB.MessageSolution += "\u2022 where that image should be the one at the daylight savings time threshold."; dlgMB.IconType = MessageBoxImage.Exclamation; dlgMB.ButtonType = MessageBoxButton.OK; bool? msg_result = dlgMB.ShowDialog(); } return; } DlgDateTimeChangeCorrection dlg = new DlgDateTimeChangeCorrection(this.dbData); dlg.Owner = this; bool? result = dlg.ShowDialog(); if (result == true) { this.showImage(dbData.CurrentRow); } }
// Load the code template and then the images from either the database (if it exists) or the actual images (if it doesn't exist) private void loadImagesFromSources() { // First, select a template file which should reside with the image set, otherwise abort loading (which means the user can try again later) // Also pass it the last image folder / template file viewed, which will be shown in the file dialog string tpath = persist.ReadLastImageFolderPath(); // the last path opened by the user is stored in the registry string filename = persist.ReadLastImageTemplateName(); // the template filname opened by the user is stored in the registry tpath = Utilities.GetTemplateFileFromUser(tpath, filename); // Returns the path and the file name if (tpath == null) return; // Parse the returned file path to get just the filename and the path to the folder. filename = System.IO.Path.GetFileName(tpath); if (filename.Equals ("")) filename = Constants.DBTEMPLATEFILENAME; persist.WriteLastImageTemplateName(filename); // We should really do this on exit as well, but I didn't feel like storing it tpath = System.IO.Path.GetDirectoryName(tpath); if ("" == tpath || null == tpath) return; this.FolderPath = tpath; // We keep the path in two places for convenience of referencing them this.dbData.FolderPath = tpath; // Create the template to the Timelapse Template database this.template = new Template(); if (!template.Open(this.FolderPath, filename)) return; // We now have the template file. Load the TemplateTable from that file, which makes it data accessible through its table template.LoadTemplateTable(); // Find the .ddb file in the image set folder. If a single .ddb file is found, use that one // If there are multiple .ddb files, ask the use to choose one and use that // However, if the user cancels that choice, just abort. // If there are no .ddb files, then just create the standard one. switch (this.dbData.FindFile()) { case 0: // An existing .ddb file is available if (this.LoadImagesFromDB(template) == true) { if (state.immediateExit) return; LoadComplete(false); } break; case 1: // User cancelled the process of choosing between .ddb files if (state.immediateExit) return; break; case 2: // There are no existing .ddb files default: if (LoadDByScanningImageFolder() == false) { DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.MessageTitle = "No Images Found in the Image Set Folder"; dlgMB.MessageProblem = "There doesn't seem to be any JPG images in your chosen image folder:"; dlgMB.MessageProblem += Environment.NewLine + "\u2022 " + this.FolderPath + Environment.NewLine; dlgMB.MessageReason = "\u2022 The folder has no JPG files in it (image files ending in '.jpg'), or" + Environment.NewLine; dlgMB.MessageReason += "\u2022 You may has selected the wrong folder, i.e., a folder other than the one containing the images."; dlgMB.MessageSolution = "\u2022 Check that the chosen folder actually contains JPG images (i.e., a 'jpg' suffix), or" + Environment.NewLine; dlgMB.MessageSolution += "\u2022 Choose another folder."; dlgMB.IconType = MessageBoxImage.Error; dlgMB.ButtonType = MessageBoxButton.OK; dlgMB.ShowDialog(); return; } break; } state.isContentChanged = false; // We've altered some content // For persistance: set a flag if We've opened the same image folder we worked with in the last session. // If its different, saved the new folder path this.ImageFolderReopened = (tpath == this.FolderPath) ? true : false; }
/// <summary> /// Copy the last non-empty value in this control preceding this image up to the current image /// /// </summary> /// <param name="mainWindow"></param> /// <param name="control"></param> public string FromLastValue(string key, bool checkForZero, bool isflag) { string valueToCopy = (checkForZero) ? "0" : ""; int row = -1; for (int i = dbData.CurrentRow - 1; i >=0; i--) // Search for the row with some value in it, starting from the previous row { valueToCopy = (string) dbData.dataTable.Rows[i][key]; valueToCopy = valueToCopy.Trim(); if ( valueToCopy.Length > 0 ) { if ( (checkForZero && !valueToCopy.Equals("0")) // Skip over non-zero values for counters || (isflag && !valueToCopy.ToLower().Equals("false")) // Skip over false values for flags || (!checkForZero && !isflag)) { row = i; //We found a non-empty value break; } } } if (row < 0) { // Nothing to propagate. Note that we shouldn't see this, as the menu item should be deactivated if this is the case. // But just in case. DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.IconType = MessageBoxImage.Exclamation; dlgMB.ButtonType = MessageBoxButton.OK; dlgMB.MessageTitle = "Nothing to Propagate to Here."; dlgMB.MessageReason = "None of the earlier images have anything in this field, so there are no values to propagate."; dlgMB.ShowDialog(); return (string) dbData.dataTable.Rows[dbData.CurrentRow][key]; // No change, so return the current value } int number_images_affected = this.dbData.CurrentRow - row; if (this.PropagateFromLastValue(valueToCopy, (number_images_affected).ToString()) != true) { return (string)dbData.dataTable.Rows[dbData.CurrentRow][key]; // No change, so return the current value }; // Update. Note that we start on the next row, as we are copying from the current row. this.dbData.RowsUpdateFromRowToRowFilteredView(key, valueToCopy, row+1, this.dbData.CurrentRow); return (valueToCopy); }
/// <summary> Swap the day / month fields if possible </summary> private void MenuItemSwapDayMonth_Click(object sender, RoutedEventArgs e) { // If we are not in the filter all view, or if its a corrupt image, tell the person. Selecting ok will shift the views.. if (dbData.RowIsImageDisplayable() == false || this.state.imageFilter != (int)Constants.ImageQualityFilters.All) { DlgMessageBox dlgMB = new DlgMessageBox(); dlgMB.MessageTitle = "Swap the day / month..."; dlgMB.MessageProblem = "To swap the day / month, Timelapse must first:" + Environment.NewLine; dlgMB.MessageProblem += "\u2022 be filtered to view All Images (normally set in the Filter menu)" + Environment.NewLine; dlgMB.MessageProblem += "\u2022 preferably be displaying a valid image"; dlgMB.MessageSolution = "Select 'Ok' for Timelapse to set the filter to 'All Images'."; dlgMB.IconType = MessageBoxImage.Exclamation; dlgMB.ButtonType = MessageBoxButton.OKCancel; bool? msg_result = dlgMB.ShowDialog(); // Set the filter to show all images and a valid image if (msg_result == true) { SetImageFilterAndIndex(0, (int)Constants.ImageQualityFilters.All); // Set it to all images int row = dbData.RowFindNextDisplayableImage(1); // Start at Row 1, as they are numbered from 1 onwards... if (row >= 0) showImage(row); } else return; } DlgDateSwapDayMonth dlg = new DlgDateSwapDayMonth(this.dbData); dlg.Owner = this; bool? result = dlg.ShowDialog(); if (result == true) { this.showImage(dbData.CurrentRow); } }