Loading Images in WPF Using File Dialogs with Prism MVVM Pattern
Loading Images with File Dialogs in WPF
WPF applications frequently need to load and display images from the file system. The Microsoft.Win32 namespace provides the OpenFileDialog class, which integrates seamlessly with the Windows file browsing experience.
OpenFileDialog Implementation
The OpenFileDialog class resides in the Microsoft.Win32 namespace, distinct from System.Windows.Forms used in Windows Forms applications. Here's how to configure and use it:
using Microsoft.Win32;
var dialog = new OpenFileDialog();
dialog.Title = "Select Image";
dialog.Filter = "JPEG Images (*.jpg;*.jpeg)|*.jpg;*.jpeg|PNG Images (*.png)|*.png|All Image Files|*.jpg;*.jpeg;*.png;*.bmp";
dialog.CheckFileExists = true;
dialog.CheckPathExists = true;
Key configuration properties:
- Title: Sets the dialog window caption
- Filter: Defines file type restrictions displayed in the dropdown
- InitialDirectory: Sets the starting folder path
- Multiselect: Enables selecting multiple files simultaneously
After configuration, display the dialog and retrieve the selected path:
bool? result = dialog.ShowDialog();
if (result == true)
{
string imagePath = dialog.FileName;
// Process the selected file
}
Release resources after use:
dialog.Dispose();
BitmapImage versus Bitmap in WPF
WPF uses BitmapImage for display purposes, which offers significant advantages over the traditional Bitmap class:
- BitmapImage operates asynchronously and manages its own memory
- Supports loading from URIs without blocking the UI thread
- Automatically caches loaded images
- Read-only by design; for manipulation, use Bitmap and convert as needed
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.UriSource = new Uri(imagePath, UriKind.Absolute);
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
bitmap.Freeze(); // Enable cross-thread usage
Prism MVVM Implementation
XAML View
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<UserControl.Resources>
<Style x:Key="actionButton" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="#7a7490"/>
<Setter Property="Height" Value="44"/>
<Setter Property="Margin" Value="4 8"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="18"
Padding="24 10">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#8372c4"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid Width="450">
<Grid.RowDefinitions>
<RowDefinition Height="90"/>
<RowDefinition Height="520"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal"
HorizontalAlignment="Center">
<Button Content="Load Image"
Background="#8372c4"
Foreground="White"
Style="{StaticResource actionButton}"
Command="{Binding LoadCommand}"/>
</StackPanel>
<Border Grid.Row="1" Background="#1e1e2e" CornerRadius="8"
Margin="8" Padding="12">
<Image Source="{Binding DisplayImage}"
Stretch="Uniform"
RenderOptions.BitmapScalingMode="HighQuality"/>
</Border>
</Grid>
</UserControl>
ViewModel Implementation
using ImageDemo.Core.Mvvm;
using ImageDemo.Services.Interfaces;
using Microsoft.Win32;
using Prism.Commands;
using Prism.Regions;
using System;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
namespace ImageDemo.Modules.ImageViewer.ViewModels
{
public class ImageViewModel : RegionViewModelBase
{
private readonly IMessageService _messageService;
private BitmapImage _displayImage;
public DelegateCommand LoadCommand { get; }
public BitmapImage DisplayImage
{
get => _displayImage;
set => SetProperty(ref _displayImage, value);
}
public ImageViewModel(IRegionManager regionManager, IMessageService messageService)
: base(regionManager)
{
_messageService = messageService;
LoadCommand = new DelegateCommand(LoadImageFromDisk);
}
public override void OnNavigatedTo(NavigationContext navigationContext)
{
// Handle navigation parameters if neeeded
}
private void LoadImageFromDisk()
{
var fileDialog = new OpenFileDialog
{
Title = "Choose an Image",
Filter = "Supported Images|*.jpg;*.jpeg;*.png;*.bmp|JPEG Files|*.jpg|PNG Files|*.png|All Files|*.*",
CheckFileExists = true
};
if (fileDialog.ShowDialog() == true)
{
try
{
var selectedPath = fileDialog.FileName;
var image = CreateBitmapFromPath(selectedPath);
DisplayImage = image;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Image load error: {ex.Message}");
}
}
fileDialog.Dispose();
}
private BitmapImage CreateBitmapFromPath(string fullPath)
{
var image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri(fullPath, UriKind.Absolute);
image.CacheOption = BitmapCacheOption.OnLoad;
image.EndInit();
image.Freeze();
return image;
}
}
}
The ViewModel leverages Prism's DelegateCommand for binding UI actions, uses RaisePropertyChanged implicitly through the SetProperty method, and follows the RegionViewModelBase pattern for navigation support. The image loading process handles exceptions gracefully and properly disposes of dialog resources to prevent memory leaks.
When loading images from disk, setting CacheOption to OnLoad ensures the image data is fully loaded into memory immediately, preventing issues when the source file becomes inaccessible. The Freeze() method makes the BitmapImage thread-safe and usable across different UI contexts.