Search code examples
c#wpfimagemvvm

Why does an image I added through my application, does not appear in its control, but the other images do?


The project in question is a WPF application intended to help users learn Japanese Kanji characters. The app has multiple pages including a main page where users can interact with Kanji characters and view their associated details. One of the key features of this app is the ability to add new Kanji characters and their related information, such as Hiragana, pronunciation, translation, and stroke count. When a new element is added, it is supposed to be displayed on the main page, but there is a problem here.

The project's base images have their Build Action set to "Content", but if I don't put it on "Content", the images are not shown (see the images at the end of the post). However, the new images are dynamically added to the application when it is running, after the compilation phase. These dynamically added images do not display correctly on the main page, although all information associated with Kanji characters displays correctly. Intriguingly, no errors are reported by the debugger, which makes solving the problem more complex.

Here is my code:

-----KanjiPageViewModel.cs-----

using Japonais.Model;
using Japonais.Utilities;
using Japonais.View;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Japonais.ViewModel
{
    class KanjiPageViewModel : ViewModelBase
    {
        /* -------------------- Variable -------------------- */
        private Stack<string> Next_Stack = new();
        private Stack<string> Previous_Stack = new();
        private int cpt;
        private int TotalKanji;
        private Dictionary<string, KanjiData> imageToKanjiData = new();
        private string imageName = string.Empty;


        /* -------------------- Command -------------------- */
        public RelayCommand HomePageCommand => new(execute => HomePageAction());
        public RelayCommand AddKanjiCommand => new(execute => AddKanjiAction());
        public RelayCommand PreviousCommand => new(execute => PreviousAction());
        public RelayCommand NextCommand => new(execute => NextAction());
        public RelayCommand UpdateNumberOfKanjiCommand => new(execute => UpdateNumberOfKanji());


        /* -------------------- Properties -------------------- */
        // Kanji
        private bool _isKanjiChecked;
        public bool IsKanjiChecked
        {
            get { return _isKanjiChecked; }
            set
            {
                _isKanjiChecked = value;
                OnPropertyChanged();
            }
        }

        // Hiragana
        private bool _isHiraganaChecked;

        public bool IsHiraganaChecked
        {
            get { return _isHiraganaChecked; }
            set
            {
                _isHiraganaChecked = value;
                OnPropertyChanged();
            }
        }

        // Pronunciation
        private bool _isPronunciationChecked;

        public bool IsPronunciationChecked
        {
            get { return _isPronunciationChecked; }
            set
            {
                _isPronunciationChecked = value;
                OnPropertyChanged();
            }
        }

        // Translation
        private bool _isTranslationChecked = true;

        public bool IsTranslationChecked
        {
            get { return _isTranslationChecked; }
            set
            {
                _isTranslationChecked = value;
                OnPropertyChanged();
            }
        }

        // Number Of Strokes
        private bool _isNbOfStrokesChecked;

        public bool IsNbOfStrokesChecked
        {
            get { return _isNbOfStrokesChecked; }
            set
            {
                _isNbOfStrokesChecked = value;
                OnPropertyChanged();
            }
        }

        // All Check/Uncheck
        private bool _toggleAllChecked;

        public bool ToggleAllChecked
        {
            get { return _toggleAllChecked; }
            set
            {
                _toggleAllChecked = value;
                IsKanjiChecked = !IsKanjiChecked;
                IsHiraganaChecked = !IsHiraganaChecked;
                IsPronunciationChecked = !IsPronunciationChecked;
                IsTranslationChecked = !IsTranslationChecked;
                IsNbOfStrokesChecked = !IsNbOfStrokesChecked;
                OnPropertyChanged();
            }
        }

        // Current Image
        private ImageSource _currentImage = new BitmapImage(new Uri("/Images/Graphismes/No Image.png", UriKind.Relative));

        public ImageSource CurrentImage
        {
            get { return _currentImage; }
            set
            {
                _currentImage = value;
                OnPropertyChanged();
            }
        }

        // TextBlock Hiragana
        private string _hiraganaText = string.Empty;

        public string HiraganaText
        {
            get { return _hiraganaText; }
            set
            {
                _hiraganaText = value;
                OnPropertyChanged();
            }
        }

        // TextBlock Pronunciation
        private string _pronunciationText = string.Empty;

        public string PronunciationText
        {
            get { return _pronunciationText; }
            set
            {
                _pronunciationText = value;
                OnPropertyChanged();
            }
        }

        // TextBlock Translation
        private string _translationText = string.Empty;

        public string TranslationText
        {
            get { return _translationText; }
            set
            {
                _translationText = value;
                OnPropertyChanged();
            }
        }

        // TextBlock NumberOfStrokes
        private string _nbOfStrokesText = string.Empty;

        public string NbOfStrokesText
        {
            get { return _nbOfStrokesText; }
            set
            {
                _nbOfStrokesText = value;
                OnPropertyChanged();
            }
        }

        // Number of kanji done
        private string _kanjiText = string.Empty;

        public string KanjiText
        {
            get { return _kanjiText; }
            set
            {
                _kanjiText = value;
                OnPropertyChanged();
            }
        }


        /* -------------------- Method -------------------- */
        private void HomePageAction()
        {
            FrameManager.MainFrame?.Navigate(new Uri("/View/HomePage.xaml", UriKind.Relative));
        }

        public void AddKanjiAction()
        {
            FrameManager.MainFrame?.Navigate(new Uri("/View/AddKanjiPage.xaml", UriKind.Relative));
        }

        private void PreviousAction()
        {
            if (Previous_Stack.Count > 1)
            {
                cpt--;
                string previousImage = Previous_Stack.Pop();
                Next_Stack.Push(imageName);
                LoadImage(previousImage);
                imageName = previousImage;

                if (imageToKanjiData.TryGetValue(previousImage, out var data))
                {
                    HiraganaText = data.Hiragana;
                    PronunciationText = data.Pronunciation;
                    TranslationText = data.Translation;
                    NbOfStrokesText = data.NumberOfStrokes.ToString();
                }

                UpdateNumberOfKanjiCommand.Execute(null);
            }
            else
            {
                MessageBox.Show("You can't go back anymore. If you want to continue, click the button 'Next'.");
            }
        }

        private void NextAction()
        {
            if (Next_Stack.Count > 0)
            {
                cpt++;
                string nextImage = Next_Stack.Pop();
                Previous_Stack.Push(imageName);
                LoadImage(nextImage);
                imageName = nextImage;

                if (imageToKanjiData.TryGetValue(nextImage, out var data))
                {
                    HiraganaText = data.Hiragana;
                    PronunciationText = data.Pronunciation;
                    TranslationText = data.Translation;
                    NbOfStrokesText = data.NumberOfStrokes.ToString();
                }

                UpdateNumberOfKanjiCommand.Execute(null);
            }
            else
            {
                MessageBoxResult result = MessageBox.Show("You have reached the end. Do you want to restart ?","Restart ?", MessageBoxButton.YesNo);

                if (result == MessageBoxResult.Yes)
                {
                    Next_Stack.Clear();
                    Previous_Stack.Clear();

                    InitializeNextStack();
                }
            }
        }

        public void InitializeNextStack()
        {
            List<string> randomImageList = Images.GetRandomImageList(@"./Images/Kanji/");

            foreach (string image in randomImageList)
            {
                KanjiData kanjiData = JsonData.LoadKanjiDataFromJson(image);
                imageToKanjiData[image] = kanjiData;

                Next_Stack.Push(image);
            }
            string nextImage = Next_Stack.Pop();
            Previous_Stack.Push(nextImage);
            LoadImage(nextImage);
            imageName = nextImage;

            if (imageToKanjiData.TryGetValue(nextImage, out var data))
            {
                HiraganaText = data.Hiragana;
                PronunciationText = data.Pronunciation;
                TranslationText = data.Translation;
                NbOfStrokesText = data.NumberOfStrokes.ToString();
            }

            cpt = 1;
            TotalKanji = Next_Stack.Count;
            UpdateNumberOfKanjiCommand.Execute(null);
        }

        public void UpdateNumberOfKanji()
        {
            KanjiText = $"{cpt}/{TotalKanji+1}";
        }

        private void LoadImage(string imageRelativePath)
        {
            CurrentImage = new BitmapImage(new Uri($"/Images/Kanji/{imageRelativePath}", UriKind.Relative));
        }
    }
}

-----AddKanjiPageViewModel.cs-----

using Japonais.Model;
using Japonais.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using System.Windows;
using System.Drawing;
using Microsoft.Win32;
using System.IO;

namespace Japonais.ViewModel
{
    class AddKanjiPageViewModel : ViewModelBase
    {
        /* -------------------- Variable -------------------- */
        private string selectedFilePath = string.Empty;


        /* -------------------- Command -------------------- */
        public RelayCommand GoBackCommand => new(execute => GoBackAction());
        public RelayCommand SelectImageCommand => new(execute => SelectImageAction());
        public RelayCommand SaveCommand => new(execute => SaveAction());

        /* -------------------- Properties -------------------- */
        // Current Image
        private ImageSource _currentImage = new BitmapImage(new Uri("/Images/Graphismes/No Image.png", UriKind.Relative));

        public ImageSource CurrentImage
        {
            get { return _currentImage; }
            set
            {
                _currentImage = value;
                OnPropertyChanged();
            }
        }

        // TextBox Hiragana
        private string _hiraganaTextBox = string.Empty;

        public string HiraganaTextBox
        {
            get { return _hiraganaTextBox; }
            set
            {
                _hiraganaTextBox = value;
                OnPropertyChanged();
            }
        }

        // TextBox Pronunciation
        private string _pronunciationTextBox = string.Empty;

        public string PronunciationTextBox
        {
            get { return _pronunciationTextBox; }
            set
            {
                _pronunciationTextBox = value;
                OnPropertyChanged();
            }
        }

        // TextBox Translation
        private string _translationTextBox = string.Empty;

        public string TranslationTextBox
        {
            get { return _translationTextBox; }
            set
            {
                _translationTextBox = value;
                OnPropertyChanged();
            }
        }

        // TextBox NbOfStrokes
        private string _nbOfStrokesTextBox = string.Empty;

        public string NbOfStrokesTextBox
        {
            get { return _nbOfStrokesTextBox; }
            set
            {
                _nbOfStrokesTextBox = value;
                OnPropertyChanged();
            }
        }


        /* -------------------- Method -------------------- */
        private void GoBackAction()
        {
            FrameManager.MainFrame?.Navigate(new Uri("/View/KanjiPage.xaml", UriKind.Relative));
        }

        private void SelectImageAction()
        {
            OpenFileDialog openFileDialog = new()
            {
                Filter = "Images (*.jpg, *.jpeg, *.png, *.gif, *.bmp)|*.jpg;*.jpeg;*.png;*.gif;*.bmp|All Files (*.*)|*.*",
                FilterIndex = 0,
                Multiselect = false
            };

            bool? result = openFileDialog.ShowDialog();

            if (result == true)
            {
                selectedFilePath = openFileDialog.FileName;
                CurrentImage = new BitmapImage(new Uri(selectedFilePath));
            }
        }

        private void SaveAction()
        {
            if (IsFormValid())
            {
                KanjiData kanjiData = new()
                {
                    ImageName = Path.GetFileName(selectedFilePath),
                    Hiragana = HiraganaTextBox,
                    Pronunciation = PronunciationTextBox,
                    Translation = TranslationTextBox,
                    NumberOfStrokes = int.Parse(NbOfStrokesTextBox)
                };

                Images.CopyImageToKanjiFolder(selectedFilePath);
                JsonData.AddKanjiToKanjiJson(kanjiData);

                CurrentImage = new BitmapImage(new Uri("/Images/Graphismes/No Image.png", UriKind.Relative));
                selectedFilePath = string.Empty;

                HiraganaTextBox = string.Empty;
                PronunciationTextBox = string.Empty;
                TranslationTextBox = string.Empty;
                NbOfStrokesTextBox = string.Empty;
            }
            else
            {
                MessageBox.Show("Please complete all fields, and select an image.");
            }
        }

        private bool IsFormValid()
        {
            if (string.IsNullOrEmpty(HiraganaTextBox) || string.IsNullOrEmpty(PronunciationTextBox) ||
                string.IsNullOrEmpty(TranslationTextBox) || string.IsNullOrEmpty(NbOfStrokesTextBox))
            {
                return false;
            }

            if (string.IsNullOrEmpty(selectedFilePath))
            {
                return false;
            }

            return true;
        }
    }
}

-----Images.cs-----

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Resources;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace Japonais.Model
{
    class Images
    {
        /* -------------------- Method -------------------- */
        public static List<string> GetRandomImageList(string relativePath)
        {
            var imageExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif" };
            List<string> randomImageList = Directory.GetFiles(relativePath)
                                                    .Where(file => imageExtensions.Contains(Path.GetExtension(file).ToLower()))
                                                    .Select(file => Path.GetFileName(file))
                                                    .ToList();

            Random random = new();
            int n = randomImageList.Count;
            while (n > 1)
            {
                n--;
                int k = random.Next(n + 1);
                (randomImageList[n], randomImageList[k]) = (randomImageList[k], randomImageList[n]);
            }
            return randomImageList;
        }

        public static void CopyImageToKanjiFolder(string sourcePath)
        {
            try
            {
                if (File.Exists(sourcePath))
                {
                    string destinationFolder = @"./Images/Kanji/";
                    string fileName = Path.GetFileName(sourcePath);
                    string destinationPath = Path.Combine(destinationFolder, fileName);

                    File.Copy(sourcePath, destinationPath, true);
                }
                else
                {
                    MessageBox.Show("The source file does not exist.");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("An error occurred while copying the image : \n" + ex.Message);
            }
        }
    }
}

I checked using the debugger the path of the image, if the image is added in the corresponding folder, and if the information is added to the json file. And from what I could see, everything is normal (or as it should be).

I tried changing the Build Action from "Content" to "Ressource", or even "Embedded Resource", but I couldn't solve my problem (I did'nt know at the time that it was not possible).

Problem :

  • How do I make the image added (when my program is running), visible during the test ?
  • If I don't set the Build Action to "Content" for all images before compiling, the images won't display. How to make them display even without "Content" (maybe it will solve my problem when an image is added dynamically) ?

When the Kanji is correctly shown

When the Kanji added manually is not shown


Solution

  • After a few weeks on this problem (scattered over 2 months), I finally found the solution! So for anyone interested, here's what solved my problem:

    First of all, to see where my problem was I had to look for things that I didn't know (and still not enough) about the debugger. I placed a breakpoint on my "LoadImage" method to check the path to my image which was not showing. By passing the mouse over it, you can scroll down a list to get more information. And in this information, there were errors! (see image below)

    Error for the image not showing

    To see the exact error message, I added this in my method, because there is an error with "Height".

    -----AddKanjiPageViewModel.cs-----

    private void LoadImage(string imageRelativePath)
    {
        try
        {
            CurrentImage = new BitmapImage(new Uri(absoluteImagePath, UriKind.Absolute));
    
            double Height = CurrentImage.Height;
        }
        catch (IOException ex)
        {
            MessageBox.Show("Error loading image: " + ex.Message);
        }
    }
    

    With this, the error will appear. The problem came from the path which changed between an image added before compilation, and added dynamically.


    The solution to this problem was as follows:

    -----AddKanjiPageViewModel.cs-----

    private void LoadImage(string imageRelativePath)
    {
        string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
        string relativeImagePath = $"Images/Kanji/{imageRelativePath}";
        string absoluteImagePath = Path.Combine(baseDirectory, relativeImagePath);
    
        try
        {
            CurrentImage = new BitmapImage(new Uri(absoluteImagePath, UriKind.Absolute));
        }
        catch (IOException ex)
        {
            MessageBox.Show("Error loading image: " + ex.Message);
        }
    }
    

    Thanks to this, we get an absolute path of the place where the program is launched (AppDomain.CurrentDomain.BaseDirectory;), and to that we combine the place where the images are located from the place where the program is launched (Path .Combine(baseDirectory, relativeImagePath);). And we pass in the URI absoluteImagePath.

    And thanks to that, I have the image displayed without any worries! (see image below)

    Everything now works! (the image displayed is a cross, it's just to make it very conspicuous so as not to miss it)