Populate a WPF Listbox with Images - wpf

I am trying to populate a listbox with some images (3 of them), the images are coming from within my application, but I cannot seem to get this to work. I need to know which image is selected so that I can update my app theme accordingly.
I am trying to avoid code behind if I possibly can. TIA
Current xaml:
<ListBox ItemContainerStyle="{DynamicResource ThemeListBoxItem}"
ItemsSource="{Binding Themes}"
SelectedItem="{Binding SelectedTheme, Mode=TwoWay}"
Style="{DynamicResource ThemeListBox}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Theme.PreviewImagePath}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ItemSource Themes:
Public ITheme[] Themes => _themeService.Themes;
ThemeService:
Public ThemeService()
{
Themes = new[]
{
DarkTheme,
DefaultTheme,
MonoTheme
};
}
Public ITheme[] Themes { get; }
public ITheme DarkTheme { get; } = new Theme("Dark Theme",
"pack://application:,,,/MyApp;component/Themes/DarkTheme.xaml",
"pack://application:,,,/MyApp;component/Assets/Media/BG_DarkTheme.png");
public ITheme MonoTheme { get; } = new Theme("Monochrome Theme",
"pack://application:,,,/MyApp;component/Themes/MonochromeTheme.xaml",
"pack://application:,,,/MyApp;component/Assets/Media/BG_Monochrome.png");
public ITheme DefaultTheme { get; } = new Theme("Light Theme",
"pack://application:,,,/MyApp;component/Themes/DefaultTheme.xaml",
"pack://application:,,,/MyApp;component/Assets/Media/BG_LightTheme.png");
Theme:
public interface ITheme
{
string DisplayName { get; }
string PreviewImagePath { get; }
string ThemeName { get; }
string ThemePath { get; }
}
public Theme(string name, string themePath, string previewImagePath)
{
DisplayName = name;
PreviewImagePath = previewImagePath;
ThemePath = themePath;
}
Currently, my Listbox is populated with 3 items, but there is no image loaded (Green square indicates SelectedItem):

The Binding Path is incorrect. It should be
<Image Source="{Binding PreviewImagePath}"/>
because the DataContext of the Image is the associated element from the Themes collection, i.e. a Theme object.
Also make sure that a potential ControlTemplate in the ItemContainerStyle contains a ContentPresenter so that the ItemTemplate is actually used.
And ensure that the Build Action of the image files is set to Resource.

Related

ComboBox does not select proper item when bound to CompositeCollection

I have a ComboBox bound to a collection of animals. From it I select my favourite animal. I need a static null item above the bound items. I declare it using a CompositeCollection. When the ComboBox is bound it does not select my initial favourite animal. How can I fix that? Similar problem here but still unresolved.
Observations:
Binding to the static item works i.e. if I don't have an initial favourite animal the static item gets selected.
The problem disappears if the static item is removed. Of course this would make the CompositeCollection and this whole question obsolete.
I already applied these measures:
A CollectionContainer cannot bind directly to a property as outlined here.
The composite collection is also moved to a static resource as suggested here.
Complete C# code and XAML to demonstrate the problem:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public class Animal
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Zoo
{
private IEnumerable<Animal> _animals = new Animal[]
{
new Animal() { Id = 1, Name = "Tom" },
new Animal() { Id = 2, Name = "Jerry" }
};
public Zoo(int initialId)
{
FavouriteId = initialId;
}
public int FavouriteId { get; set; }
public IEnumerable<Animal> Animals { get { return _animals; } }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void BindComboBox(object sender, RoutedEventArgs e)
{
// Selecting the static item by default works.
//DataContext = new Zoo(-1);
// Selecting "Jerry" by default does not work.
DataContext = new Zoo(2);
}
}
}
XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1">
<Window.Resources>
<CollectionViewSource x:Key="AnimalsBridge" Source="{Binding Path=Animals}" />
<CompositeCollection x:Key="AnimalsWithNullItem">
<local:Animal Id="-1" Name="Pick someone..."/>
<CollectionContainer Collection="{Binding Source={StaticResource AnimalsBridge}}" />
</CompositeCollection>
</Window.Resources>
<StackPanel>
<Button Content="Bind" Click="BindComboBox"/>
<ComboBox x:Name="cmbFavourite"
SelectedValue="{Binding Path=FavouriteId}"
SelectedValuePath="Id" DisplayMemberPath="Name"
ItemsSource="{StaticResource AnimalsWithNullItem}"/>
</StackPanel>
</Window>
I dont have a solution to your problem but rather an alternative. I personally have view models dedicated to each view. I would then have a property on the view model to add the null value as required. I prever this method since it allows for better unit testing of my viewmodel.
For your example add:
public class ZooViewModel
{
.....
public IEnumerable<Animal> Animals { get { return _animals; } }
public IEnumerable<Animal> AnimalsWithNull { get { return _animals.WithDefault(new Animal() { Id = -1, Name = "Please select one" }); } }
}
The magic component
public static class EnumerableExtend {
public static IEnumerable<T> WithDefault<T>(this IEnumerable<T> enumerable,T defaultValue) {
yield return defaultValue;
foreach (var value in enumerable) {
yield return value;
}
}
}
Then in your XAML you just bind to
ComboBox x:Name="cmbFavourite"
SelectedValue="{Binding Path=FavouriteId}"
SelectedValuePath="Id" DisplayMemberPath="Name"
ItemsSource="{Binding AnimalsWithNull }"/>
Now you are binding directly to the source and can control the binding as normal. Also note because we are using "yield" we are not creating a new enum but rather just iterating over the existing list.

ListBox DataTemplate for dynamically loaded type

I have a sample MVVM WPF application and I'm having problems creating DataTemplates for my dynamically loaded model. Let me try explain:
I have the following simplified classes as part of my Model, which I'm loading dynamically
public class Relationship
{
public string Category { get; set; }
public ParticipantsType Participants { get; set; }
}
public class ParticipantsType
{
public ObservableCollection<ParticipantType> Participant { get; set; }
}
public class ParticipantType
{
}
public class EmployeeParticipant : ParticipantType
{
public EmployeeIdentityType Employee { get; set; }
}
public class DepartmentParticipant : ParticipantType
{
public DepartmentIdentityType Department { get; set; }
}
public class EmployeeIdentityType
{
public string ID { get; set; }
}
public class DepartmentIdentityType
{
public string ID { get; set; }
}
Here is how my View Model looks like. I created a generic object Model property to expose my Model:
public class MainViewModel : ViewModelBase<MainViewModel>
{
public MainViewModel()
{
SetMockModel();
}
private void SetMockModel()
{
Relationship rel = new Relationship();
rel.Category = "213";
EmployeeParticipant emp = new EmployeeParticipant();
emp.Employee = new EmployeeIdentityType();
emp.Employee.ID = "222";
DepartmentParticipant dep = new DepartmentParticipant();
dep.Department = new DepartmentIdentityType();
dep.Department.ID = "444";
rel.Participants = new ParticipantsType() { Participant = new ObservableCollection<ParticipantType>() };
rel.Participants.Participant.Add(emp);
rel.Participants.Participant.Add(dep);
Model = rel;
}
private object _Model;
public object Model
{
get { return _Model; }
set
{
_Model = value;
NotifyPropertyChanged(m => m.Model);
}
}
}
Then I tried creating a ListBox to display specifically the Participants Collection:
<ListBox ItemsSource="{Binding Path=Model.Participants.Participant}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<!-- WHAT TO PUT HERE IF PARTICIPANTS HAVE DIFFERENT PROPERTY NAMES -->
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
The problem is:
I don't know how to create a template that can handle both type of ParticipantTypes, in this case I could have EmployeeParticipant or DepartmentParticipant so depending on that, the data binding Path would be set to Employee or Department properties accordingly
I though about creating a DataTemplate for each type (e.g. x:Type EmployeeParticipant) but the problem is that my classes in my model are loaded dynamically at runtime so VisualStudio will complain that those types don't exist in the current solution.
How could I represent this data in a ListBox then if my concrete types are not known at compile time, but only at runtime?
EDIT: Added my test ViewModel class
You can still create a DataTemplate for each type but instead of using DataType declarations to have them automatically resolve you can create a DataTemplateSelector with a property for each template (assigned from StaticResource in XAML) that can cast the incoming data item to the base class and check properties or otherwise determine which template to use at runtime. Assign that selector to ListBox.ItemTemplateSelector and you'll get similar behavior to what DataType would give you.
That's not a good view-model. Your view-model should be view-centric, not business-centric. So make a class that can handle all four cases from a visual perspective, then bridge your business classes over to that view-model.
EDIT:
Working off your code:
<ListBox ItemsSource="{Binding Path=Model.Participants}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<TextBlock Text={Binding Id} />
<TextBlock Text={Binding Name} />
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I changed the binding, I assume that was a mistake?
I would create a ViewModel for Participant:
public class Participant_VM : ViewModelBase
{
private string _name = string.Empty;
public string Name
{
get
{
return _name ;
}
set
{
if (_name == value)
{
return;
}
_name = value;
RaisePropertyChanged(() => Name);
}
private string _id= string.Empty;
public string Id
{
get
{
return _id;
}
set
{
if (_id== value)
{
return;
}
_id = value;
RaisePropertyChanged(() => Id);
}
}
}
Modify the ListBox as follows.
<ListBox ItemsSource="{Binding Model.Participants.Participant}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type loc:DepartmentParticipant}">
<Grid>
<TextBlock Text="{Binding Department.ID}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type loc:EmployeeParticipant}">
<Grid>
<TextBlock Text="{Binding Employee.ID}"/>
</Grid>
</DataTemplate>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<!-- WHAT TO PUT HERE IF PARTICIPANTS HAVE DIFFERENT PROPERTY NAMES -->
<ContentPresenter Content="{Binding }"/>
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Edit:
loc refers to the namespace in which the DepartmentParticipant and EmployeeParticipant are present. Hope you are familiar with adding namespaces.

Wrapped image not displaying in combo box

I used the method described in the following question to populate a combo box.
ComboBox ItemTemplate does not support values of type 'Image'
The ComboBox is populated with instances of a wrapper class that contains the Image and some data. However as the poster of that question stated the images do not display in the combobox, however selecting one of the blank entries does bind the data correctly.
The data is bound in the model view as this interacts with the external data source, however the images are UI specific so are bound in the code of the user control.
How can I get the images displaying?
code:
public ObservableCollection<Wrapper> Images { get; set; }
public Class Wrapper
{
public Wrapper(int data, Image img)
{
Data = data;
Img = img;
}
public int Data { get; set; }
public Image Img { get; set; }
}
int selectedData;
public int SelectedData
{
get { return selectedData; }
set
{
selectedData = value;
(this.Resources["model"] as MyModel).External = Images[selectedData].Data;
SendPropertyChanged("SelectedData");
}
}
xaml:
<ComboBox ItemsSource="{Binding Images}" SelectedIndex="{Binding SelectedData}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Img}" Height="83" Width="71"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The Source of an Image control can not be another Image control. Change the type of your Img property to ImageSource:
public class Wrapper
{
...
public ImageSource Img { get; set; }
}

Switching the Binding Source of DataGrid in WPF

In my view there is a DataGrid and its ItemsSource is bound to a Filelist from 3 different folders.
Is it possible to switch the binding source programatically?
E.g. for the first click ItemsSource="{Binding FileList}
and for the second click ItemsSource="{Binding FileList1}
Is this possible in the same DataGrid? I'm following MVVM and i use Prism.
Yes it is possible to change the data context on click or on some other action..
You said that you are using MVVM prism... here is a sample i have created to assist you..
In this sample my source(in your case it is datagrid) itemssource property will always binded to a property "Sourcelist" and on click i am re assigning the Sourcelist to diffrent list..
so on every click we are reassigning the sourcelist property which is binded to datagrid or list
class Viewmodel : ViewModelBase
{
public Viewmodel()
{
ChangeDataSource = new DelegateCommand<object>(ChagneDataSource);
Filelist1 = new FileListOne();
FileList2 = new FileListTwo();
Filelist1.Files = new List<string>();
FileList2.Files = new List<string>();
for (int i = 0; i < 10; i++)
{
Filelist1.Files.Add("FileListOne " + i);
FileList2.Files.Add("FileListTwo " + i);
}
Sourcelist = Filelist1;
}
private object _sourcelist;
public object Sourcelist
{
get
{
return _sourcelist;
}
set
{
_sourcelist = value;
OnPropertyChanged("Sourcelist");
}
}
public ICommand ChangeDataSource { get; set; }
public FileListOne Filelist1 { get; set; }
public FileListTwo FileList2 { get; set; }
private void ChagneDataSource(object seder)
{
if (Sourcelist.GetType() == typeof(FileListOne))
Sourcelist = FileList2;
else
Sourcelist = Filelist1;
}
}
class FileListOne
{
public List<string> Files { get; set; }
}
class FileListTwo
{
public List<string> Files { get; set; }
}
XAML
<StackPanel>
<ListBox x:Name="listbox2" ItemsSource="{Binding Sourcelist.Files}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="button" Content="Button" Command="{Binding ChangeDataSource}"/>
</StackPanel>
Those XAML snippets just translate to
dataGrid.SetBinding(DataGrid.ItemsSourceProperty, new Binding("FileList"));
You need to keep track of how many clicks have occurred in a field, further you need to have a reference to the DataGrid of course. (You could create one via x:Reference and store that in the Button's Tag or CommandParameter if you use commands which would be more likely with MVVM i suppose)

ContextMenu bound to ObservableCollection<MenuItem> does not refresh data

Given the following XAML snippet:
<UserControl.Resources>
<ResourceDictionary>
<Style x:Key="ContextMenuItemStyle">
<Setter Property="MenuItem.Header" Value="{Binding Text}"/>
<Setter Property="MenuItem.ItemsSource" Value="{Binding Children}"/>
<Setter Property="MenuItem.Command" Value="{Binding Command}" />
</Style>
<ContextMenu x:Key="contextMenu" ItemsSource="{Binding MenuOptions}" ItemContainerStyle="{StaticResource ContextMenuItemStyle}" />
</ResourceDictionary>
</UserControl.Resources>
<DockPanel>
<TextBox Height="30" DockPanel.Dock="Top" ContextMenu="{StaticResource contextMenu}" />
<Button Content="Add Menu Item" DockPanel.Dock="Top" Command="{Binding AddMenuItem}" />
</DockPanel>
And View Model:
public class MyViewModel {
public ObservableCollection<MenuItem> DocumentExplorerMenuOptions { get; set; }
MenuItem firstMenuItem;
MenuItem secondMenuItem;
public MyViewModel() {
firstMenuItem = new MenuItem("First") { Command = new DelegatingCommand(x => MessageBox.Show("First Selected") };
secondMenuItem = new MenuItem("Second") { Command = new DelegatingCommand(x => MessageBox.Show("Second Selected") };
MenuOptions = new ObservableCollection<MenuItem> { firstMenuItem, secondMenuItem };
AddMenuItem = new DelegateCommand<object>(x => firstMenuItem.Children.Add(
new MenuItem("Child of First")));
}
public DelegateCommand<object> AddMenuItem { get; set; }
}
And class:
public class MenuItem {
public MenuItem(string text) {
Text = text;
Children = new List<MenuItem>();
}
public string Text { get; set; }
public List<MenuItem> Children { get; private set; }
public ICommand Command { get; set; }
}
Clicking the button does add the child to firstMenuItem but it does not appear in the context menu of the TextBox.
I can't figure out how to make the context menu show the dynamic content of the context menu. Any thoughts?
I would not bind to a collection of MenuItems but rather to a more data-driven collection which may contain the MenuItem header, a command which is executed upon click and another collection of such items for the sub-items. Then you could use a (Hierarchical)DataTemplate to generate the menu on the fly. Doing so would probably take care of update issues if your datatype implements the necessary interfaces.
Edit: You seem to have such a datatype already, could you post its code?
Edit2: I think the problem is that you use a style that explicitly needs to be applied (it is probably only being applied to the main context menu, not the sub-items), as noted before i'd suggest a HierarchicalDataTemplate.
Edit3: lol...
public List<MenuItem> Children { get; private set; }
Of course it's not going to update if it's a List and not an ObservableCollection.
(The class is quite badly designed overall by the way, Lists should normally not even have a private setter, they should be properties with just a getter to a readonly field)

Resources