i am having an issue while trying to bind an image to an ImageSource. I have tried some of the other fix on stackoverflow but none of them works.
I seem to get an error on this line saying that collection "Items" must be empty.
ImageList.ItemsSource = List;
The bind works well while using the "url" member of the FlickrData class.
MainWindow.xaml
<ScrollViewer>
<ListView x:Name="ImageList" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<Rectangle Margin="5" Width="100" Height="100">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding imageBinding}"/>
</Rectangle.Fill>
</Rectangle>
</ListView>
</ScrollViewer>
FlickrData Class:
public class FlickrData
{
public String url { get; set;}
public FlickrData(Photo photo)
{
url = photo.SmallUrl;
}
public ImageBrush imageBinding
{
get
{
ImageBrush brush = new ImageBrush();
brush.ImageSource = new BitmapImage(new Uri(url));
return brush;
}
}
}
MainWindow class:
public partial class MainWindow : Window
{
public ObservableCollection<FlickrData> List = new ObservableCollection<FlickrData>();
public static Flickr flickr = new Flickr("XXXXXXXXXXXXXX");
public MainWindow()
{
InitializeComponent();
}
public void SearchWithInput(object sender, RoutedEventArgs e)
{
var options = new PhotoSearchOptions { Tags = SearchInput.Text, PerPage = 20, Page = 1 };
PhotoCollection photos = flickr.PhotosSearch(options);
List.Clear();
foreach (Photo photo in photos)
{
String flickrUrl = photo.WebUrl;
Console.WriteLine("Photo {0} has title {1} with url {2}", photo.PhotoId, photo.Title, photo.WebUrl);
List.Add(new FlickrData(photo));
}
ImageList.ItemsSource = List;
}
}
Do this to clean up the process
Change your XAML's ListView to ItemsSource="{Binding List}" which only needs to be done once.
Remove the now redundant ImageList.ItemsSource = List;
The list control will update itself accordingly because an ObservableCollection sends notifications of the change which are subscribed to by the list control.
Related
I am trying to bind thumbnail of my video StorageFile to Image XAML element through data template with x:Bind. I am using MVVM approach and I've used same method to achieve this in past but I don't know why it isn't working now.
I use live property explorer and source of Image is 0. other properties like video title are just working fine but image is not working. But problem occurs even with duration, sometimes duration shows up and sometimes it doesn't, which is odd.
I am providing my code below.
Model
public class VideoItem : LibraryItem
{
#region Props
public string Views { get; set; }
public string Duration { get; set; }
public BitmapImage Display { get; set; }
public VideoProperties MyVideoProperties { get; set; }
public StorageFile MyVideoFile { get; set; }
#endregion
public VideoItem(StorageFile File)
{
MyVideoFile = File;
Initialize();
}
#region PrivateMethods
private async void Initialize()
{
Title = MyVideoFile.DisplayName;
MyVideoProperties = await MyVideoFile.Properties.GetVideoPropertiesAsync();
var dur = MyVideoProperties.Duration;
Duration = $"{dur.Hours.ToString()} : {dur.Minutes.ToString()} : {dur.Seconds.ToString()}";
Display = await GetDisplay();
Views = MyVideoProperties.Rating.ToString();
}
private async Task<BitmapImage> GetDisplay()
{
var bitm = new BitmapImage();
using (var imgSource = await MyVideoFile.GetScaledImageAsThumbnailAsync(ThumbnailMode.VideosView))
{
if (imgSource != null) { bitm.SetSource(imgSource); }
else
{
var storelogoFolder = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFolderAsync("Assets");
var storageLogoFile = await storelogoFolder.GetFileAsync("StoreLogo.png");
bitm.UriSource = new Uri(storageLogoFile.Path);
}
}
return bitm;
}
#endregion
}
public class LibraryItem
{
public string Title { get; set; }
}
ViewModel
public class VideoLibraryViewModel
{
#region Constructor
public VideoLibraryViewModel(StorageFolder mainFolder)
{
VideoItems = new ObservableCollection<VideoItem>();
MainFolder = mainFolder;
Initialize();
}
#endregion
#region Props
public ObservableCollection<VideoItem> VideoItems { get; set; }
#endregion
#region PrivateFields
private StorageFolder MainFolder;
private IEnumerable<StorageFile> Videos;
private char[] sep = new char[] { '/' };
#endregion
#region PrivateMethods
private async void Initialize()
{
Videos = await MainFolder.GetFilesAsync();
Videos = Videos.Where(a => a.ContentType.Split(sep)[0] == "video");
FillUp();
}
private void FillUp()
{
foreach (var file in Videos)
{
VideoItems.Add(new VideoItem(file));
}
}
#endregion
}
View
<controls:AdaptiveGridView Name="VideosLibraryGridView" Grid.Row="1"
Header="Videos"
Style="{StaticResource MainGridView}"
ItemClick="VideosLibraryGridView_ItemClicked"
ItemsSource="{x:Bind VideoLibraryVM.VideoItems, Mode=OneWay}">
<controls:AdaptiveGridView.ItemTemplate>
<DataTemplate x:DataType="data:VideoItem">
<StackPanel Margin="4" >
<Grid>
<Image Source="{x:Bind Display, Mode=OneWay}" Style="{StaticResource GridViewImage}"/>
<Border Style="{StaticResource TimeBorder}">
<TextBlock Text="{x:Bind Duration, Mode=OneWay}" Foreground="White"/>
</Border>
</Grid>
<TextBlock Text="{x:Bind Title,Mode=OneWay}" Style="{StaticResource GridViewVideoName}"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<TextBlock Text="{x:Bind Views,Mode=OneWay}" Style="{StaticResource GridViewViews}"/>
<TextBlock Text="Views" HorizontalAlignment="Right"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</controls:AdaptiveGridView.ItemTemplate>
</controls:AdaptiveGridView>
Style For Image
<Style TargetType="Image" x:Key="GridViewImage">
<Setter Property="Stretch" Value="UniformToFill"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
Output in the app, you can see gridview items show no image and no duration, sometimes duration show and image never shows up:
Why no image?
Why no duration?
UPDATE
I have checked with breakpoints, all properties of items are appearing null, apart from title, all properties apart from title are retrieved asyncronosly, maybe that is the reason?
all properties of items are appearing null, apart from title, all properties apart from title are retrieved asyncronosly
It's because the UI just render grid items with all available properties without waiting any results from asynchronous methods, hence why sometimes you'll get some items with a proper duration text shows up and sometimes It doesn't.
So, the logical solution is to run the asynchronous method after the gridview load those items, right?
But how? placing it under loaded event inside datatemplate doesn't change anything since It'll just do the same problem again.
well, you can do it by abusing ContainerContentChanging event inside Gridview control due to how that event itself works.
Page.xaml
<controls:AdaptiveGridView
Name="VideosLibraryGridView" Grid.Row="1
ContainerContentChanging="VideosLibraryGridView_ContainerContentChanging"
Header="Videos"
Style="{StaticResource MainGridView}"
ItemClick="VideosLibraryGridView_ItemClicked"
ItemsSource="{x:Bind VideoLibraryVM.VideoItems, Mode=OneWay}">
<!--something something-->
</controls:AdaptiveGridView>
Page.xaml.cs
private void VideosLibraryGridView_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args
{
args.RegisterUpdateCallback(LoadImage);
}
private async void LoadImage(ListViewBase sender, ContainerContentChangingEventArgs args)
{
var templateRoot = args.ItemContainer.ContentTemplateRoot as Grid;
var imageurl = (args.Item as model).ThumbnailUri;
var cache = await getimagefromfileasync(imageurl);
//check your image location based on your template first.
var image = templateRoot.Children[0] as Image;
image.Source = new BitmapImage()
{
UriSource = new Uri(cache.Path)
};
image.Opacity = 1;
}
The code above is what I did in order to load the cached thumbnail asynchronously.
source:
Dramatically Increase Performance when Users Interact with Large Amounts of Data in GridView and ListView
ContainerContentChanging Event
Update ListView and GridView items progressively
I have several UI elements in a DataTemplate bound to an ObservableCollection of Video objects. I want to call a method of a Video object when I click on the ContextMenuItem [Test] of the corresponding UI element.
Here is my XAML:
<ItemsControl Name="VideoUIElment" >
<ItemsControl.ItemTemplate>
<DataTemplate x:Uid="videoTemplate">
<Border CornerRadius="10" Padding="10, 10" Background="Silver" >
<TextBlock Name="label" Text="{Binding Name}" FontSize="30" Foreground="Black" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="[TEST]" Name="Test" Click="Test_Click"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is the collection:
public MainWindow()
{
//ctor
InitializeComponent();
pathToLauncher = string.Empty;
videos = new ObservableCollection<Video>();
VideoUIElment.ItemsSource = videos;
}
I know that, to do this, I have to identify which Video object inside the collection is bound to the specific UI element I click on, and I could come up with some trick to achieve this, but I would like to do it in a graceful and intelligent way.
I've seen some suggestions already, but none of them seemed to have been applicable here. I guess, it is supposed to be something easy, but I'm not very versed in WPF yet.
Try this:
MainWindow:
public partial class MainWindow : Window
{
ObservableCollection<Video> videos { get; set; }
public MainWindow()
{
InitializeComponent();
videos = new ObservableCollection<Video>
{
new Video {Name = "Video 1"},
new Video {Name = "Video 2"},
new Video {Name = "Video 3"}
};
VideoUIElment.ItemsSource = videos;
}
private void Test_Click(object sender, RoutedEventArgs e)
{
MenuItem item = (MenuItem)sender;
Video video = (Video)item.DataContext;
MessageBox.Show(video.VideoMethod());
}
}
Video:
public class Video
{
public string Name { get; set; }
public string VideoMethod()
{
return string.Format(" Clicked {0}", Name);
}
}
I've got a GridviewItem. This GridviewItem has a background which is an ImageBrush. Now I want to change this ImageBrush to a new source when clicking a certain button.
For this I'm using:
blck.Background = new ImageBrush(new BitmapImage(new Uri("ms-appx:///Assets/SensorBG.png")));
It does work however the new image only shows whenever I click on the corresponding GridviewItem. Can anyone tell me how to update it without having to click on the GridviewItem?
I've already tried to put it within this block with no success:
CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
blck.Background = new ImageBrush(new BitmapImage(new Uri("ms-appx:///Assets/SensorBG.png")));
}
);
The best would be, if you have defined your ItemClass with suitable properties and implement INotifyPropertyChanged - with appropriate binding, every change will update the UI. Here is a small sample - XAML:
<StackPanel>
<Button Content="Change background of second item" Click="Button_Click"/>
<GridView Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" ItemsSource="{x:Bind Items}">
<GridView.ItemTemplate>
<DataTemplate x:DataType="local:ItemClass">
<Border>
<Border.Background>
<ImageBrush ImageSource="{x:Bind Image, Mode=OneWay}"/>
</Border.Background>
<TextBlock Text="{x:Bind Name}"/>
</Border>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</StackPanel>
and the code behind:
public sealed partial class MainPage : Page
{
public List<ItemClass> Items = new List<ItemClass>();
public MainPage()
{
Items.Add(new ItemClass { Name = "First item", Image = new BitmapImage(new Uri("ms-appx:///Assets/StoreLogo.png")) });
Items.Add(new ItemClass { Name = "Second item", Image = new BitmapImage(new Uri("ms-appx:///Assets/StoreLogo.png")) });
this.InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e) => Items[1].Image = new BitmapImage(new Uri("ms-appx:///test.jpg"));
}
public class ItemClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseProperty(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
private ImageSource image;
public ImageSource Image
{
get { return image; }
set { image = value; RaiseProperty(nameof(Image)); }
}
public string Name { get; set; }
}
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; }
}
I have a static class named Building which contains a List<Beam> Beams as its property;
public static class Building
{
public static readonly List<Beam> Beams = new List<Beam>();
}
public class Beam
{
public string Story;
public double Elevation;
}
I'm trying to Bind the Building.Beams to a combobox in XAML so that Elevation and Story properties of each item in Building.Beams list is displayed in different columns in the combobox. I have been able to implement the two columns, I just can't Bind these properties.
Here is what I have tried so far:
<ComboBox x:Name="cmbBuilding" ItemsSource="{Binding}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Width="300">
<TextBlock Width="150" Text="{Binding Path=Story }"/>
<TextBlock Width="150" Text="{Binding Path=Elevation}"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
var b1 = new Beam { Elevation = 320, Story = "ST1" };
var b2 = new Beam { Elevation = 640, Story = "ST2" };
Building.Beams.Add(b1);
Building.Beams.Add(b2);
First of all you can't bind with fields.
Convert Story and Elevation to properties (automatic properties in your case will do)
public class Beam
{
public string Story { get; set;}
public double Elevation { get; set;}
}
Second, you should use ObservableCollection in case you are adding items to the list after loading finishes so that UI gets notification.
public static readonly ObservableCollection<Beam> Beams
= new ObservableCollection<Beam>();
Try this example:
XAML
<Grid>
<ComboBox x:Name="cmbBuilding" Width="100" Height="25" ItemsSource="{Binding Path=Beams}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Width="300">
<TextBlock Width="150" Text="{Binding Path=Story}" HorizontalAlignment="Left" />
<TextBlock Width="150" Text="{Binding Path=Elevation}" HorizontalAlignment="Right" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="Add item" VerticalAlignment="Top" Click="Button_Click" />
</Grid>
Code-behind
public partial class MainWindow : Window
{
Building building = new Building();
public MainWindow()
{
InitializeComponent();
building.Beams = new List<Beam>();
building.Beams.Add(new Beam
{
Elevation = 320,
Story = "ST1"
});
this.DataContext = building;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var b1 = new Beam { Elevation = 320, Story = "ST1" };
var b2 = new Beam { Elevation = 640, Story = "ST2" };
building.Beams.Add(b1);
building.Beams.Add(b2);
cmbBuilding.Items.Refresh();
}
}
public class Building
{
public List<Beam> Beams
{
get;
set;
}
}
public class Beam
{
public string Story
{
get;
set;
}
public double Elevation
{
get;
set;
}
}
Some notes
When you use properties in the Binding, you need to be properties with get and set, not fields.
Properties, what were added to the List<T> will automatically update, you should call MyComboBox.Items.Refresh() method, or use ObservableCollection<T>:
ObservableCollection represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
Is it maybe because you have declared Beams as readonly yet you try to ADD items to it? Beams is also defined as a variable, try removing the readonly and making it a property with a getter and setter