I want to display images into a ListBox in WPF. What i want to achieve is following
Read from a device for images and get information like name etc. (DONE)
create items in listbox and display default thumb (some default pic) in place of actual thumb as thumb will be read later for the image. (DONE)
Now get their thumbnails and store them in a folder (DONE). Being done by a task.
NOW show the actual thumb of the image. ( NOT DONE).
STEP 4 is where i am stuck. Basically what i want to do is similar to windows which will first display images/videos icon then fill their thumbs.
Please help. I am new to WPF and hence facing problems.
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Image Source="{Binding Image}"/>
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Name}"/>
<TextBlock Grid.Row="1" Text="{Binding Desc}"/>
<TextBlock Grid.Row="2" Text="{Binding Notes}"/>
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
public partial class ImageListBox : INotifyPropertyChanged
{
private ObservableCollection<Item> _items;
public ObservableCollection<Item> Items
{
get { return _items; }
set { _items = value; OnPropertyChanged("Items"); }
}
public ImageListBox()
{
DataContext = this;
Items = new ObservableCollection<Item>(new List<Item>
{
new Item { Image = "Images/_(1).png" , Desc = "Desc1",Name = "Name1",Notes = "Notes1"},
new Item { Image = "Images/_(2).png" , Desc = "Desc2",Name = "Name2",Notes = "Notes2"},
new Item { Image = "Images/_(3).png" , Desc = "Desc3",Name = "Name3",Notes = "Notes3"},
new Item { Image = "Images/_(4).png" , Desc = "Desc4",Name = "Name4",Notes = "Notes4"},
new Item { Image = "Images/_(5).png" , Desc = "Desc5",Name = "Name5",Notes = "Notes5"},
});
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this,new PropertyChangedEventArgs(propertyName));
}
}
public class Item
{
public String Image { get; set; }
public String Name { get; set; }
public String Desc { get; set; }
public String Notes { get; set; }
}
I might not properly understand your question but if i got it right: you want to display an image in some WPF control.
1) Bind path to your image to the control (I used DataGrid):
<DataGrid ItemsSource="{Binding Path=ImageCollection}"
<DataGrid.Columns>
<DataGridTemplateColumn Header="Image">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Width="40" Height="20" Source="{Binding FilePath, Converter={StaticResource ResourceKey=ImageConverter}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid>
2) Create converter that converts FilePath to ImageSource (I found it somewhere in the Internet):
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var path = value as string;
if (path == null)
{
return DependencyProperty.UnsetValue;
}
//create new stream and create bitmap frame
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
try
{
bitmapImage.StreamSource = new FileStream(path, FileMode.Open, FileAccess.Read);
//load the image now so we can immediately dispose of the stream
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
//clean up the stream to avoid file access exceptions when attempting to delete images
bitmapImage.StreamSource.Dispose();
return bitmapImage;
}
catch (Exception)
{
//do smth
}
}
}
Hope, this helps.
There are various approachs to this, one would be using a PriorityBinding which first displays the placeholder icon, then the thumb. (You probably need to assign the property which holds the image only after it is fully loaded.)
Related
We used to develop application with WinForms and nowadays we are trying to migrate it to WPF, starting from zero. In our application we have 3 main parts on screen which are Header (all main menu items), body (based on MDI container, content can be changed) and the footer (where general status is displayed, logo etc.) Whenever a user clicks on different menuitem from header part, the body part would change it's children to that Panel/Form.
There are lot's of good examples/tutorials on the Internet but I am confused about how to achieve to create a navigation service that allows to switch the view of body part.
Any suggestions would be appriciated, thanks in advance.
There are indeed multiple ways to archive this result. I will try and explain the very basic/easiest way to get the result.
While this will not provide example in combination with Menu Control, I think it will help you to understand the concept
In your MainWindow you can split use Grid layout and split the space into 3 parts as you wanted. Your Main window Xaml should look something like this :
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<ContentControl x:Name="Header"/>
<ContentControl x:Name="Content" Grid.Row="1/>
<ContentControl x:Name="Footer" Grid.Row="2"/>
</Grid>
In your content control you can insert your "UserControls" for the Header,Content,Footer. Now to the navigation part:
As mentioned there are many ways to archive this and I will describe what I do consider the easiest way (not the most flexible way however, so keep that in mind).
First I will suggest to make a navigation Model:
public class NavigationModel
{
public NavigationModel(string title, string description, Brush color)
{
Title = title;
Description = description;
Color = color;
}
public string Title { get; set; }
public string Description { get; set; }
public Brush Color { get; set; }
public override bool Equals(object obj)
{
return obj is NavigationModel model &&
Title == model.Title &&
Description == model.Description &&
Color == model.Color;
}
public override int GetHashCode()
{
return HashCode.Combine(Title, Description, Color);
}
}
We create a new class that will handle the navigation collection, lets call it navigation service.
public class NavigationService
{
public List<NavigationModel> NavigationOptions { get=>NavigationNameToUserControl.Keys.ToList(); }
public UserControl NavigateToModel(NavigationModel _navigationModel)
{
if (_navigationModel is null)
//Or throw exception
return null;
if (NavigationNameToUserControl.ContainsKey(_navigationModel))
{
return NavigationNameToUserControl[_navigationModel].Invoke();
}
//Ideally you should throw here Custom Exception
return null;
}
//Usage of the Func, provides each call new initialization of the view
//If you need initialized views, just remove the Func
//-------------------------------------------------------------------
//Readonly is used only for performance reasons
//Of course there is option to add the elements to the collection, if dynamic navigation mutation is needed
private readonly Dictionary<NavigationModel, Func<UserControl>> NavigationNameToUserControl = new Dictionary<NavigationModel, Func<UserControl>>
{
{ new NavigationModel("Navigate To A","This will navigate to the A View",Brushes.Aqua), ()=>{ return new View.ViewA(); } },
{ new NavigationModel("Navigate To B","This will navigate to the B View",Brushes.GreenYellow), ()=>{ return new View.ViewB(); } }
};
#region SingletonThreadSafe
private static readonly object Instancelock = new object();
private static NavigationService instance = null;
public static NavigationService GetInstance
{
get
{
if (instance == null)
{
lock (Instancelock)
{
if (instance == null)
{
instance = new NavigationService();
}
}
}
return instance;
}
}
#endregion
}
This service will provide us with action to receive desired UserControll (note that I am using UserControl instead of pages, since they provide more flexibility).
Not we create additional Converter, which we will bind into the xaml:
public class NavigationConverter : MarkupExtension, IValueConverter
{
private static NavigationConverter _converter = null;
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (_converter is null)
{
_converter = new NavigationConverter();
}
return _converter;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
NavigationModel navigateTo = (NavigationModel)value;
NavigationService navigation = NavigationService.GetInstance;
if (navigateTo is null)
return null;
return navigation.NavigateToModel(navigateTo);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> null;
}
In our MainWindows.xaml add reference to the Converter namespace over xmlns, for example :
xmlns:Converter="clr-namespace:SimpleNavigation.Converter"
and create insance of converter :
<Window.Resources>
<Converter:NavigationConverter x:Key="NavigationConverter"/>
</Window.Resources>
Note that your project name will have different namespace
And set the Add datacontext to the instance of our Navigation Service:
You can do it over MainWindow.Xaml.CS or create a ViewModel if you are using MVVM
MainWindow.Xaml.CS:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = Service.NavigationService.GetInstance.NavigationOptions;
}
}
Now all is left to do is navigate. I do not know how about your UX, so I will just provide example from my github of the MainWindow.xaml. Hope you will manage to make the best of it :
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<ListView
x:Name="NavigationList"
ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<Border
Height="35"
BorderBrush="Gray"
Background="{Binding Color}"
ToolTip="{Binding Description}"
BorderThickness="2">
<TextBlock
VerticalAlignment="Center"
FontWeight="DemiBold"
Margin="10"
Text="{Binding Title}" />
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<ContentControl
Grid.Column="1"
Content="{Binding ElementName=NavigationList,Path=SelectedItem,Converter={StaticResource NavigationConverter}}"/>
</Grid>
Just in case I will leave you a link to github, so it will be easier for you
https://github.com/6demon89/Tutorials/blob/master/SimpleNavigation/MainWindow.xaml
Using same principle to use Menu Navigation
<Window.DataContext>
<VM:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<Converter:NavigationConverter x:Key="NavigationConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Menu>
<MenuItem Header="Navigaiton"
ItemsSource="{Binding NavigationOptions}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem
Command="{Binding DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Header="{Binding Title}"
Background="{Binding Color}"
ToolTip="{Binding Description}">
</MenuItem>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
<ContentControl
Grid.Row="1"
Background="Red"
BorderBrush="Gray"
BorderThickness="2"
Content="{Binding CurrentView,Converter={StaticResource NavigationConverter}}"/>
<Border Grid.Row="2" Background="{Binding CurrentView.Color}">
<TextBlock Text="{Binding CurrentView.Description}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</Grid>
And we have in VM List of the navigation Models, Current Model and the navigation command :
public class MainViewModel:INotifyPropertyChanged
{
public List<NavigationModel> NavigationOptions { get => NavigationService.GetInstance.NavigationOptions; }
private NavigationModel currentView;
public NavigationModel CurrentView
{
get { return currentView; }
set
{
currentView = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("CurrentView"));
}
}
RelayCommand _saveCommand;
public event PropertyChangedEventHandler PropertyChanged;
public ICommand NavigateCommand
{
get
{
if (_saveCommand == null)
{
_saveCommand = new RelayCommand(Navigate);
}
return _saveCommand;
}
}
private void Navigate(object param)
{
if(param is NavigationModel nav)
{
CurrentView = nav;
}
}
}
Sorry for long reply
I think that you not necessary have to start from scratch. You may have a look:
https://qube7.com/guides/navigation.html
Working on a ComboBox that displays a list of available tile backgrounds. This is just a simple ComboBox with an ItemSource set to a collection of MapTileBackground objects.
The MapTileBackground class is defined entirely with properties:
public partial class MapTileBackground
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public byte[] Content { get; set; }
public Nullable<int> Color { get; set; }
public int StrokeColor { get; set; }
public byte StrokeThickness { get; set; }
}
which is defined in a separate library and I would prefer to not change it.
I have defined a simple shape extension to draw the background::
public class MapTileBackgroundPreview : Shape
{
public static readonly DependencyProperty SizeProperty = DependencyProperty.Register("Size", typeof(Point), typeof(MapTileBackgroundPreview));
public static readonly DependencyProperty TileBackgroundProperty = DependencyProperty.Register("TileBackground", typeof(MapTileBackground), typeof(MapTileBackgroundPreview));
public MapTileBackgroundPreview()
{
layout = new Hex.Layout(Hex.Orientation.Flat, new Hex.Point(8, 8), new Hex.Point(4, 4));
Size = new Point(8, 8);
TileBackground = null;
}
private Hex.Layout layout;
protected override Geometry DefiningGeometry
{
get
{
var points = layout.HexCorners(0, 0).ToArray();
var path = new PathFigure();
path.StartPoint = points[5].ToWin();
for (var i = 0; i < 6; i++)
path.Segments.Add(new LineSegment(points[i].ToWin(), true));
var geo = new PathGeometry();
geo.Figures.Add(path);
return geo;
}
}
public Point Size
{
get
{
return (Point)GetValue(SizeProperty);
}
set
{
SetValue(SizeProperty, value);
layout.Size = value.ToHex();
layout.Origin = new Hex.Point(layout.Size.X / 2, layout.Size.Y / 2);
}
}
public MapTileBackground TileBackground
{
get
{
return (MapTileBackground)GetValue(TileBackgroundProperty);
}
set
{
SetValue(TileBackgroundProperty, value);
if (value == null)
{
Fill = Brushes.Transparent;
Stroke = Brushes.Black;
StrokeThickness = 1;
}
else
{
Stroke = value.Stroke();
StrokeThickness = value.StrokeThickness();
Fill = value.Fill(layout.Orientation);
}
}
}
}
The layout is just a conversion utility between screen pixel coordinates and a hexagonal system. DefiningGeometry just add 6 line segments of the hex. The TileBackground setter, when given a not null MapTileBackground, updates the Stroke and Fill as the background defines. I've tested this control successfully (outside the combo box data template).
And speaking of:
<DataTemplate x:Key="TileListItemRenderer">
<Grid Width="225">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="75"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<local:MapTileBackgroundPreview Grid.Row="0" Grid.Column="0" Size="12,12" VerticalAlignment="Center" HorizontalAlignment="Center" TileBackground="{Binding /}"/>
<Label Grid.Row="0" Grid.Column="1" Content="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center" FontWeight="Bold"/>
<TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding Description}" HorizontalAlignment="Stretch" VerticalAlignment="Top" TextWrapping="Wrap" />
</Grid>
</DataTemplate>
So I just create a shape, and two labels, bind the shape to the current MapTileBackground object (combo box ItemSource is a collection of MapTileBackground objects), and the labels to Name and Description.
My problem is the shape is always drawn empty (as in TileBackground is null) and the setter is never invoked. Both the Name Label and Description TextBlock behave as expected (display correct text). And during my debugging attempts, I created an id property on the preview object which in turn invokes the TileBackground Setter and bound it to the Id property (avoid current object bind), again, the TileBackgroundId setter is never invoked. I even added a new label bound to Id to see if that was working and it displays the id as expected. Here are those changes that again did not work. The TileBackgroundId or TileBackground properties are never set when opening the drop down.
<DataTemplate x:Key="TileListItemRenderer">
<Grid Width="225">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="75"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<local:MapTileBackgroundPreview Grid.Row="0" Grid.Column="0" Size="12,12" VerticalAlignment="Center" HorizontalAlignment="Center" TileBackgroundId="{Binding Id}"/>
<Label Grid.Row="0" Grid.Column="0" Content="{Binding Id}" HorizontalAlignment="Left" VerticalAlignment="Center" FontWeight="Bold"/>
<Label Grid.Row="0" Grid.Column="1" Content="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center" FontWeight="Bold"/>
<TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding Description}" HorizontalAlignment="Stretch" VerticalAlignment="Top" TextWrapping="Wrap" />
</Grid>
</DataTemplate>
public static readonly DependencyProperty TileBackgroundIdProperty = DependencyProperty.Register("TileBackgroundId", typeof(int), typeof(MapTileBackgroundPreview));
public int TileBackgroundId
{
get
{
return (int)GetValue(TileBackgroundIdProperty);
}
set
{
SetValue(TileBackgroundIdProperty, value);
TileBackground = TMapTileBackgroundTool.Get(value);
}
}
TMapTileBackgroundTool.Get() returns the correct object based on Id.
I have also tested instances of MapTileBackgroundPreview setting TileBackgroundId outside the data template.
Any thoughts as to what is going on?
The setter of the CLR wrapper for the dependency property is not supposed to be set as the WPF binding engine calls the GetValue and SetValue methods directly:
Setters not run on Dependency Properties?
Why are .NET property wrappers bypassed at runtime when setting dependency properties in XAML?
The getter and setter of the CLR wrapper property should only call the GetValue and SetValue method respectively.
If you want to do something when the dependency property is set, you should register a callback:
public static readonly DependencyProperty TileBackgroundIdProperty = DependencyProperty.Register("TileBackgroundId", typeof(int), typeof(MapTileBackgroundPreview),
new PropertyMetadata(0, new PropertyChangedCallback(TileBackgroundIdChanged)));
public int TileBackgroundId
{
get
{
return (int)GetValue(TileBackgroundIdProperty);
}
set
{
SetValue(TileBackgroundIdProperty, value);
}
}
private static void TileBackgroundIdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MapTileBackgroundPreview ctrl = (MapTileBackgroundPreview)d;
ctrl.TileBackground = TMapTileBackgroundTool.Get((int)e.NewValue);
}
I'm studying WPF at school but I ran into a problem with uploading a new image to my project.
The goal is to be able to add an image (at runtime) using a file browser. This image should be uploaded into the project and the filename should be saved in a database. Than it should be accessible as a resource in the project so I can show the image in a listbox for example.
This is what I've got so far:
View where the upload happens:
<Image Height="70px" Source="{Binding newImg}"/>
<Button Height="23" Name="btnLoad" VerticalAlignment="Bottom"
Width="75" Grid.Column="0" Grid.Row="1" Command="{Binding ImgUploadCommand}">_Browse</Button>
ViewModel UploadView
private string fullPath;
private BitmapImage image;
private Patient newPatient = new Patient();
private void KoppelenCommands()
{
FotoUploadCommand = new BaseCommand(FotoPatientUpload);
PatientOpslaanCommand = new BaseCommand(PatientOpslaan);
}
public ICommand FotoUploadCommand { get; set; }
public ICommand PatientOpslaanCommand { get; set; }
public void FotoPatientUpload()
{
OpenFileDialog op = new OpenFileDialog();
op.Title = "Select a picture";
op.Filter = "All supported graphics|*.jpg;*.jpeg;*.png|" +
"JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|" +
"Portable Network Graphic (*.png)|*.png";
if (op.ShowDialog() == true)
{
image= new BitmapImage(new Uri(op.FileName));
fullPath = op.FileName;
string[] partsFileName = fullPath.Split('\\');
System.Windows.MessageBox.Show(delenFileName[(partsFileName.Length - 1)]);
NewPatient.Image= partsFileName[(delenFileName.Length - 1)];
}
}
public void PatientOpslaan()
{
string destinationPath = GetDestinationPath(NewPatient.Afbeelding, "\\assets\\images");
File.Copy(fullPath, destinationPath, true);
//dataservice (My DS works fine, I can see the correct filename in the database but I save only the name not the Path)
PatientDataService patientDS =
new PatientDataService();
patientDS.InsertPatient(NewPatient);
}
else
{
MessageBox.Show("Niet alle velden zijn ingevuld! Een nieuwe patient moet tenminste een naam en een voornaam krijgen!", "Fout!", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
//opslaan foto in opgegeven map <<Code afkomstig van stackoverflow auteur: Yashpal Singla>>
private static String GetDestinationPath(string filename, string foldername)
{
String appStartPath = System.IO.Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
appStartPath = String.Format(appStartPath + "\\{0}\\" + filename, foldername);
return appStartPath;
}
The image is correctly saved to the bin/debug/assets/images folder but not as a resource. Because it isn't saved as a resource I can't use it in my MainWindow View which looks like this:
<ListBox HorizontalContentAlignment="Center" ItemsSource="{Binding Patienten}" SelectedItem="{Binding SelectedPatient}" Grid.Column="0" Grid.Row="0">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Name="ImageNameListBox" Visibility="Collapsed"
Text="{Binding Image, StringFormat=../assets/images/{0}}" />
<Border Style="{StaticResource imageBorderStyle}" Grid.Column="0" Grid.Row="0" Height="80px" Width="80px">
<Rectangle Margin="1,-2,-2,1" Height="80px" Width="80px">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding Text, ElementName=ImageNameListBox}"/>
</Rectangle.Fill>
</Rectangle>
</Border>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
MainWindow ViewModel:
class MainWindowViewModel : BaseViewModel
{
private DialogService dialogService;
private ObservableCollection<Patient> patienten;
public ObservableCollection<Patient> Patienten
{
get
{
return patienten;
}
set
{
patienten = value;
NotifyPropertyChanged();
}
}
private Patient selectedPatient;
public Patient SelectedPatient {get; set;}
public MainWindowViewModel()
{
LoadingPatients();
//instantiƫren DialogService als singleton
dialogService = new DialogService();
}
private void LoadingPatients()
{
//instantiƫren dataservice
PatientDataService patientDS =
new PatientDataService();
Patienten = new ObservableCollection<Patient>(patientDS.GetPatienten());
}
}
Note that I didn't include all of the code so my datacontext is set with a ViewModelLocator which you cannot see here.
Is there any way to save the image as a resource or do I have to convert all the images in the /bin/debug/assets/images folder to a resource at startup? If so how do I do that?
Apologies for my English, I'm not a native speaker
Thanks for those who had the courage to read all the way to this line and thanks for those who can and will help me!
You can load the Image as ImageSource from your file and bind it to an Image in your view.
public class MyViewModel
{
public void LoadImage()
{
ImageSource = new BitmapImage(new Uri("assets/images/yourImage.jpg", UriKind.Relative));
}
public ImageSource ImageSource { get; set; }
}
In the view:
<Image Source="{Binding Path=ImageSource}"></Image>
As answer to the comment, this works also inside a listbox.
The View:
<ListBox ItemsSource="{Binding Path=MyImages}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=ImageSource}"/>
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The ViewModel:
public class MainWindowViewModel
{
public void LoadImages()
{
var d = new DirectoryInfo("assets/images");
var images = d.GetFiles();
MyImages = images.Select(x => new MyImageModel(x.Name, new BitmapImage(new Uri(x.FullName))));
}
public IEnumerable<MyImageModel> MyImages { get; set; }
}
The MyImageModel
public class MyImageModel
{
public MyImageModel(string name, ImageSource imageSource)
{
Name = name;
ImageSource = imageSource;
}
public string Name { get; set; }
public ImageSource ImageSource { get; set; }
}
My UWP is required to have a Favorites page that allows the user to reorder and save the data on the page. Originally my data comes from a large JSON file which is deserialized using Newtonsoft's Json.net, and is stored for this page in a Dictionary which then fills the public ObservableCollection.
This is where I now get lost, setting the ObservableCollection as the DataContext and then using the data as a Binding in the XAML code to populate the GridView with all the Titles, Subtitles and Images that each Item requires.
In theory this should work, but in my trials and tests the page remains blank while all the C# code behind the scenes makes it seem like it should be populated.
I don't know why the page is not filling to I am turning to the collective help of all of you.
P.S: I don't really care about the neatness of this code, I just want to get it working.
XAML File
<Page
x:Name="pageRoot"
x:Class="Melbourne_Getaway.FavouritesPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Melbourne_Getaway"
xmlns:data="using:Melbourne_Getaway.Data"
xmlns:common="using:Melbourne_Getaway.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<x:String x:Key="AppName">Favourites</x:String>
</Page.Resources>
<!--
This grid acts as a root panel for the page that defines two rows:
* Row 0 contains the back button and page title
* Row 1 contains the rest of the page layout
-->
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition />
</TransitionCollection>
</Grid.ChildrenTransitions>
<Grid.RowDefinitions>
<RowDefinition Height="140" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemsGridView"
AutomationProperties.Name="Items"
TabIndex="1"
Grid.RowSpan="2"
Padding="60,136,116,46"
SelectionMode="None"
IsSwipeEnabled="false"
CanReorderItems="True"
CanDragItems="True"
AllowDrop="True"
ItemsSource="{Binding Items}">
<GridView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Left" Width="250" Height="107">
<Border Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}">
<Image Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}" />
</Border>
<StackPanel VerticalAlignment="Bottom" Background="{ThemeResource ListViewItemOverlayBackgroundThemeBrush}">
<TextBlock Text="{Binding Title}" Foreground="{ThemeResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource BaseTextBlockStyle}" Height="30" Margin="15,0,15,0" FontWeight="SemiBold" />
<TextBlock Text="{Binding Group}" Foreground="{ThemeResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource BaseTextBlockStyle}" TextWrapping="NoWrap" Margin="15,-15,15,10" FontSize="12" />
</StackPanel>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
<!-- Back button and page title -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button x:Name="backButton" Margin="39,59,39,0" Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"
Style="{StaticResource NavigationBackButtonNormalStyle}"
VerticalAlignment="Top"
AutomationProperties.Name="Back"
AutomationProperties.AutomationId="BackButton"
AutomationProperties.ItemType="Navigation Button" />
<TextBlock x:Name="pageTitle" Text="{StaticResource AppName}" Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1"
IsHitTestVisible="false" TextWrapping="NoWrap" VerticalAlignment="Bottom" Margin="0,0,30,40" />
</Grid>
</Grid>
CS File
using Melbourne_Getaway.Common;
using Melbourne_Getaway.Data;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Windows.Storage;
using Windows.UI.Popups;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace Melbourne_Getaway
{
public sealed partial class FavouritesPage : Page
{
public ObservableCollection<ItemData> Items { get; set; }
private ObservableDictionary defaultViewModel = new ObservableDictionary();
private NavigationHelper navigationHelper;
private RootObject jsonLines;
private StorageFile fileFavourites;
private Dictionary<string, ItemData> ItemData = new Dictionary<string, ItemData>();
public FavouritesPage()
{
loadJson();
getFavFile();
this.InitializeComponent();
this.navigationHelper = new NavigationHelper(this);
this.navigationHelper.LoadState += navigationHelper_LoadState;
}
private void setupObservableCollection()
{
Items = new ObservableCollection<ItemData>(ItemData.Values);
DataContext = Items;
}
private async void loadJson()
{
var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///DataModel/SampleData.json"));
string lines = await FileIO.ReadTextAsync(file);
jsonLines = JsonConvert.DeserializeObject<RootObject>(lines);
feedItems();
}
private async void getFavFile()
{
Windows.Storage.StorageFolder storageFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
fileFavourites = await storageFolder.GetFileAsync("MelbGetaway.fav");
}
private async void feedItems()
{
if (await FileIO.ReadTextAsync(fileFavourites) != "")
{
foreach (var line in await FileIO.ReadLinesAsync(fileFavourites))
{
foreach (var Group in jsonLines.Groups)
{
foreach (var Item in Group.Items)
{
if (Item.UniqueId == line)
{
var storage = new ItemData()
{
Title = Item.Title,
UniqueID = Item.UniqueId,
ImagePath = Item.ImagePath,
Group = Group.Title
};
ItemData.Add(storage.UniqueID, storage);
}
}
}
}
}
else
{//should only execute if favourites file is empty, first time use?
foreach (var Group in jsonLines.Groups)
{
foreach (var Item in Group.Items)
{
var storage = new ItemData()
{
Title = Item.Title,
UniqueID = Item.UniqueId,
ImagePath = Item.ImagePath,
Group = Group.Title
};
ItemData.Add(storage.UniqueID, storage);
await FileIO.AppendTextAsync(fileFavourites, Item.UniqueId + "\r\n");
}
}
}
setupObservableCollection();
}
public ObservableDictionary DefaultViewModel
{
get { return this.defaultViewModel; }
}
#region NavigationHelper loader
public NavigationHelper NavigationHelper
{
get { return this.navigationHelper; }
}
private async void MessageBox(string Message)
{
MessageDialog dialog = new MessageDialog(Message);
await dialog.ShowAsync();
}
private async void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
var sampleDataGroups = await SampleDataSource.GetGroupsAsync();
this.defaultViewModel["Groups"] = sampleDataGroups;
}
#endregion NavigationHelper loader
#region NavigationHelper registration
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
navigationHelper.OnNavigatedFrom(e);
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
navigationHelper.OnNavigatedTo(e);
}
#endregion NavigationHelper registration
}
public class ItemData
{
public string UniqueID { get; set; }
public string Title { get; set; }
public string Group { get; set; }
public string ImagePath { get; set; }
}
}
Without a good Minimal, Complete, and Verifiable code example it is impossible to know for sure what's wrong. However, one glaring error does appear in your code:
private void setupObservableCollection()
{
Items = new ObservableCollection<ItemData>(ItemData.Values);
DataContext = Items;
}
In your XAML, you bind to {Binding Items}. With the DataContext set to the Items property value, the correct binding would actually be just {Binding}.
Alternatively, if you want to keep the XAML the way it is, you would have to set DataContext = this; instead. Of course, if you did it that way, then you would run into the problem that you don't appear to be raising INotifyPropertyChanged.PropertyChanged, or even implementing that interface. You can get away with that if you are sure the property will be set before the InitializeComponent() method is called, but in the code you've shown that does not appear to be the case.
So if you want to set the binding to {Binding Items} you also need to implement INotifyPropertyChanged and make sure you raise the PropertyChanged event with the property name "Items" when you actually set the property.
If the above does not address your question, please improve the question by providing a good MCVE that reliably reproduces the problem.
I figured it out. my problem lied in the way I was trying to pass the Data to the page itself. Instead of using DataContext = Items;and trying to access the data that way. I instead set the direct ItemsSource for the GridView.
The end result was simply changing DataContext = Items to itemGridView.ItemsSource = Items;
Ad[a,o]pting Brundritt's example here, which he referenced in responding to my earlier BingMaps question, I have been able to display certain data in an "infobox" like so:
...but am not getting the BitmapImage that is part of the data I intend to display.
My question is why are the images not displaying, and what do I need to go in order to display them?
It's not the data - I've saved the thumbnail image to a SQLite database, where it is stored as an array of bytes, but which can be seen as images in SQLiteToolbox:
:
So as you can see, the String data is displaying fine, but not the image. Here is the pertinent code I've got for retrieving the data and displaying it:
// The class used to create the SQLite table
public class PhotraxBaseData
{
[SQLite.PrimaryKey]
[SQLite.AutoIncrement]
public int Id { get; set; }
. . .
public DateTime dateTimeTaken { get; set; }
public String filePath { get; set; }
public Byte[] thumbnail { get; set; }
}
private async void Gener8MapMarkers(List<PhotraxBaseData> _pbd)
{
foreach (PhotraxBaseData pbd in _pbd)
{
String descAndFilename = Path.GetFileName(pbd.filePath);
if (null != pbd.imgDescription)
{
descAndFilename = String.Format("{0} - {1}", pbd.imgDescription, descAndFilename);
}
BitmapImage bmi = await PhotraxUtils.ByteArrayToBitmapImage(pbd.thumbnail);
if (PhotraxUtils.ValidLatAndLong(pbd.latitude, pbd.longitude))
{
Location loc = new Location(pbd.latitude, pbd.longitude);
AddPushpin(loc, descAndFilename, pbd.dateTimeTaken, null, DataLayer);
}
}
}
public static async Task<BitmapImage> ByteArrayToBitmapImage(this byte[] byteArray)
{
// from http://dotnetspeak.com/2013/04/convert-byte-array-to-an-image-in-winrt
if (byteArray != null)
{
using (var stream = new InMemoryRandomAccessStream())
{
await stream.WriteAsync(byteArray.AsBuffer());
var image = new BitmapImage();
stream.Seek(0);
image.SetSource(stream);
return image;
}
}
return null;
}
public class PushpinMetadata
{
public string DescAndFileName { get; set; }
public DateTime DateTimeTaken { get; set; }
public BitmapImage Thumbnail { get; set; }
}
public void AddPushpin(Location latlong, string descAndFileName, DateTime dateTimeTaken, BitmapImage thumbnail, MapLayer layer)
{
Pushpin p = new Pushpin()
{
Tag = new PushpinMetadata()
{
DescAndFileName = descAndFileName,
DateTimeTaken = dateTimeTaken,
Thumbnail = thumbnail
}
};
MapLayer.SetPosition(p, latlong);
p.Tapped += PinTapped;
layer.Children.Add(p);
}
private void PinTapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
Pushpin p = sender as Pushpin;
PushpinMetadata m = (PushpinMetadata)p.Tag;
//Ensure there is content to be displayed
if (!String.IsNullOrEmpty(m.DescAndFileName))
{
Infobox.DataContext = m;
Infobox.Visibility = Visibility.Visible;
MapLayer.SetPosition(Infobox, MapLayer.GetPosition(p));
}
else
{
Infobox.Visibility = Visibility.Collapsed;
}
}
The XAML:
<bm:Map.Children>
<!-- Data Layer-->
<bm:MapLayer Name="DataLayer"/>
<!--Common Infobox-->
<bm:MapLayer>
<Grid x:Name="Infobox" Visibility="Collapsed" Margin="0,-115,-15,0">
<Border Width="300" Height="110" Background="Black" Opacity="0.8" BorderBrush="White" BorderThickness="2" CornerRadius="5"/>
<StackPanel Height="100" Margin="5">
<Grid Height="40">
<TextBlock Text="{Binding DescAndFileName}" FontSize="20" Width="250" TextWrapping="Wrap" HorizontalAlignment="Left" />
<Button Content="X" Tapped="CloseInfobox_Tapped" HorizontalAlignment="Right" VerticalAlignment="Top"/>
</Grid>
<Grid Height="40">
<Viewbox Height="200" Stretch="Uniform" StretchDirection="Both">
<Image Source="{Binding Thumbnail}" Width="Auto" Height="Auto" Margin="2"/>
</Viewbox>
</Grid>
<Grid Height="40">
<TextBlock Text="{Binding DateTimeTaken}" FontSize="16" Width="290" TextWrapping="Wrap" Height="Auto"/>
</Grid>
</StackPanel>
</Grid>
</bm:MapLayer>
<callisto:CustomDialog Content="CustomDialog" Height="100" Width="100"/>
</bm:Map.Children>
My guess is that the Image's Source property doesn't quite know what to do with the BitmapImage it's bound to via "Thumbnail"; but I don't know how to unbabelize the mismatch (presuming that's where the problem lies).
UPDATE
To answer Faye's brother Chris, here is the "database" (SQLite class) declaration:
public Byte[] thumbnail { get; set; }
Here is where I was calling the converter method, passing the appropriate member of the class instance, and then adding a pushpin:
BitmapImage bmi = await PhotraxUtils.ByteArrayToBitmapImage(pbd.thumbnail);
if (PhotraxUtils.ValidLatAndLong(pbd.latitude, pbd.longitude))
{
Location loc = new Location(pbd.latitude, pbd.longitude);
//AddPushpin(loc, descAndFilename, pbd.dateTimeTaken, null, DataLayer);
AddPushpin(loc, descAndFilename, pbd.dateTimeTaken, bmi, DataLayer);
}
However: MY BAD, PEOPLES!!!
I had neglected to replace my placeholder "null" arg with "bmi" (you can see my previous code commented out above, and my new working call to AddPushpin() below it). The image's size/scale is all wrong, but that can be fixed easily enough, I reckon.
I don't know whether to jump for joy or kick myself in the keister, so I think I'll do both simultaneously.
Thanks to Mr. Dunaway for making me look closer at my code - something I obviously should have done before posting.