Inside my application I have grid that is using ObservableCollection as ImageSource
My Model looks like this:
public class Student:INotifyPropertyChanged
{
public Student()
{
}
public Student(int id, string firstName, string lastName, int points, bool active = true)
{
_id = id;
_firstName = firstName;
_lastName = lastName;
_points = points;
_active = active;
}
private int _id;
private string _firstName;
private string _lastName;
private int _points;
private bool _active;
public int Id
{
get { return _id; }
set
{
if (value == _id) return;
_id = value;
OnPropertyChanged();
}
}
public string FirstName
{
get { return _firstName; }
set
{
if (value == _firstName) return;
_firstName = value;
OnPropertyChanged();
}
}
public string LastName
{
get { return _lastName; }
set
{
if (value == _lastName) return;
_lastName = value;
OnPropertyChanged();
}
}
public string Name
{
get { return string.Format("{0} {1}", _firstName, _lastName); }
}
public int Points
{
get { return _points; }
set
{
if (value == _points) return;
_points = value;
OnPropertyChanged();
}
}
public string Avatar
{
get
{
string appPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (appPath == null) return null;
string imagesPath = Path.Combine(appPath, "Images","Students");
if (!Directory.Exists(imagesPath)) return null;
var imagePath = Path.Combine(imagesPath, string.Format("{0}.png", _id));
if (File.Exists(imagePath)) return imagePath;
imagePath = Path.Combine(imagesPath, string.Format("{0}.jpg", _id));
if (File.Exists(imagePath)) return imagePath;
imagePath = Path.Combine(imagesPath, string.Format("{0}.jpeg", _id));
if (File.Exists(imagePath)) return imagePath;
return Path.Combine(imagesPath, "Default.png");
}
}
public bool Active
{
get { return _active; }
set
{
if (value == _active) return;
_active = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I'm also storing selected student from grid in variable.
My XAML looks like this:
<DataGrid
Grid.Column="0"
Name="StudentsGrid"
ItemsSource="{Binding Students}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
CanUserAddRows="True"
AutoGenerateColumns="False"
RowEditEnding="StudentsGrid_OnRowEditEnding"
SelectedItem="{Binding SelectedStudent, Converter={StaticResource SelectedStudentConverter}}">
<DataGrid.Columns>
<DataGridTextColumn Width="*" Binding="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}" ClipboardContentBinding="{x:Null}" Header="Imię"/>
<DataGridTextColumn Width="*" Binding="{Binding LastName, UpdateSourceTrigger=PropertyChanged}" ClipboardContentBinding="{x:Null}" Header="Nazwisko"/>
<DataGridCheckBoxColumn Width="60" Binding="{Binding Active, UpdateSourceTrigger=PropertyChanged}" ClipboardContentBinding="{x:Null}" Header="Aktywna"/>
</DataGrid.Columns>
</DataGrid>
<Border HorizontalAlignment="Left" Height="120" Margin="10,78,0,0"
VerticalAlignment="Top" Width="97" BorderBrush="Black" BorderThickness="1">
<Image Name="Avatar" Source="{Binding SelectedStudent.Avatar, Converter={StaticResource AvatarConverter}}" />
</Border>
<Button Click="ButtonBase_OnClick" Content="Change avatar" Grid.Column="1" HorizontalAlignment="Right" Margin="0,80,10,0" VerticalAlignment="Top" Width="107"/>
I've used converter for selected student because I got red border around grid when trying to add new row.
and code responsible for button click:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog openfile = new Microsoft.Win32.OpenFileDialog
{
DefaultExt = "*.png",
Filter = "JPG (*.jpg,*.jpeg)|*.jpg;*.jpeg|PNG (*.png)|*.png"
};
bool? result = openfile.ShowDialog();
if (result == true)
{
var source = openfile.FileName;
var ext = Path.GetExtension(source);
string appPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (appPath == null) return;
string imagesPath = Path.Combine(appPath, "Images","Students");
if (!Directory.Exists(imagesPath)) Directory.CreateDirectory(imagesPath);
var destination = Path.Combine(imagesPath, string.Format("{0}{1}", _selectedStudent.Id, ext));
Student old = _selectedStudent;
SelectedStudent = null;
File.Copy(source, destination,true);
SelectedStudent = old;
var bindingExpression = Avatar.GetBindingExpression(Image.SourceProperty);
if (bindingExpression != null) bindingExpression.UpdateTarget();
}
}
And AvatarConverter:
public class AvatarConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
BitmapImage image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriSource = new Uri((string)value);
image.EndInit();
return image;
//I've used below code previously, but file was locked
return new BitmapImage(new Uri((string)value));
}
catch
{
return new BitmapImage();
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
My idea was to display current avatar that is stored inside Images\Student
. If file exists then Avatar
property is set to that image path, else I'm displaying Default.png
.
Next to currently selected avatar image I've added button that will allow me to update avatar.
I was't able to replace image file, because it was in use, but thanks to this answer I was able to fix this quickly.
But I'm unable to update image inside my application to display new source. I've tried null'ing SelectedStudent
and restoring it, but without luck.
- How can I refresh image source for current student after I replace that image on disk.
- Should I move Avatar path resolving to converter or can I leave it inside model?