DaveR
DaveR

Reputation: 173

Binding doesnt change properly in MAUI when i change it inside my ViewModel

I have a function in my viewmodel in which an asynchronous method is called. To execute a loading animation in the UI I have a boolean isLoading variable as binding. Immediately before calling the asynchronous method this variable is set to true and as soon as the method is finished it is set to false again. But it seems that the variable is not changed correctly. Anybody got an idea?

LoadFilenames snippet

public async Task LoadFileNames(string engineIdentifier, int displayMode) {
try {
    ImageList.Clear();
    IsLoading = true;
    await MergeFiles(engineIdentifier);

    IsLoading = false;
    ...

From my ViewModel

private bool _isLoading;
public bool IsLoading {
    get { return _isLoading; }
    set {
        _isLoading = value;
        OnPropertyChanged();
    }
}

public void OnPropertyChanged([CallerMemberName] string name = null) =>
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

From my View

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         Shell.BackgroundColor="#696969"
         xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
         xmlns:converters="clr-namespace:EngineCheck.Converters"
         x:Class="EngineCheck.Views.EngineView"
         xmlns:vm="clr-namespace:EngineCheck.ViewModels">

    <ContentPage.BindingContext>
        <vm:EngineViewModel x:Name="viewmodel"/>
    </ContentPage.BindingContext>

Calling the Loading animation

<ActivityIndicator IsRunning="{Binding IsLoading}" IsVisible="{Binding IsLoading}"/>

All other bindings in the xaml file work smoothly. So the implementation of the ViewModel seems to be correct

UPDATE

My MergeFiles method

private async Task MergeFiles(string engineIdentifier) {
    try {
        string folderPath = prefs.Get("images_save_location", "");
        string searchString = engineIdentifier;
        if(Directory.Exists(folderPath)) {
            string[] files = Directory.GetFiles(folderPath);
            foreach(string filePath in files) {
                string fileName = Path.GetFileName(filePath);
                if(fileName.EndsWith("bmp") && 
                    fileName.Contains(searchString)) {
                        Utilities.Utilities.MergeBmpAndSvg(folderPath, 
                            fileName[..^4]);
                    }
            }
        } else {
        //TODO
        }
    } catch(Exception ex) {
        logger.Error(ex.Message);
    }
}

MergeBmpAndSvg method

public static void MergeBmpAndSvg(string path, string filename) {
    try {
        Bitmap bmpImage = new Bitmap(path + "/" + filename + ".bmp");
        SvgDocument svgDocument = SvgDocument.Open(path + "/" + filename +         
            ".svg");
        Bitmap svgBitmap = svgDocument.Draw();
        Bitmap resultImage = new Bitmap(bmpImage.Width, bmpImage.Height);
        using(Graphics graphics = Graphics.FromImage(resultImage)) {
            graphics.DrawImage(bmpImage, 0, 0);
            graphics.DrawImage(svgBitmap, new System.Drawing.Point(0, 0));
        }
        resultImage.Save(path + "/" + filename + ".png");
        bmpImage.Dispose();
        svgBitmap.Dispose();
        resultImage.Dispose();
    } catch(Exception ex) {
        logger.Error(ex.Message);
    }
}

Upvotes: 0

Views: 388

Answers (1)

Julian
Julian

Reputation: 8856

You may be running into a deadlock by mixing asynchronous code and synchronous code.

The following code does not run asynchronously, there are only synchronous (blocking) calls and nothing is being awaited:

private async Task MergeFiles(string engineIdentifier) {
    try {
        string folderPath = prefs.Get("images_save_location", "");
        string searchString = engineIdentifier;
        if(Directory.Exists(folderPath)) {
            string[] files = Directory.GetFiles(folderPath);
            foreach(string filePath in files) {
                string fileName = Path.GetFileName(filePath);
                if(fileName.EndsWith("bmp") && 
                    fileName.Contains(searchString)) {
                        Utilities.Utilities.MergeBmpAndSvg(folderPath, 
                            fileName[..^4]);
                    }
            }
        } else {
        //TODO
        }
    } catch(Exception ex) {
        logger.Error(ex.Message);
    }
}

Note: Just marking a method as async and using the Task return type, will not make your code run asynchronously!

In order to make the above code, which may be a longer running CPU-bound operation, run in an asynchronous fashion, you should change its signature to void (without async!):

private void MergeFiles(string engineIdentifier) {
    try {
        string folderPath = prefs.Get("images_save_location", "");
        string searchString = engineIdentifier;
        if(Directory.Exists(folderPath)) {
            string[] files = Directory.GetFiles(folderPath);
            foreach(string filePath in files) {
                string fileName = Path.GetFileName(filePath);
                if(fileName.EndsWith("bmp") && 
                    fileName.Contains(searchString)) {
                        Utilities.Utilities.MergeBmpAndSvg(folderPath, 
                            fileName[..^4]);
                    }
            }
        } else {
        //TODO
        }
    } catch(Exception ex) {
        logger.Error(ex.Message);
    }
}

and then move the call to that method to a thread pool thread using Task.Run():

await Task.Run(() =>
{
    MergeFiles(engineIdentifier);
});

Upvotes: 1

Related Questions