Wrapped image not displaying in combo box - wpf

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; }
}

Related

Populate a WPF Listbox with Images

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.

WPF Combo Box not displaying dynamically changed selected item

I am trying to change the selected item of a combo box based on a change in another combo box. The situation is complicated by the fact that both combo boxes appear in a list of item templates.
The XAML is as follows:
<ListBox ItemsSource="{Binding AncillaryExtendedPropertyViewModels}" ItemTemplateSelector="{StaticResource templateSelector}"/>
<DataTemplate x:Key="EnumDataTemplate"> <Grid Margin="4"
MinHeight="25"> <ComboBox SelectedItem="{Binding ExtendedPropertyEnum,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
ItemsSource="{Binding ExtendedPropertyEnumList}"
DisplayMemberPath="Value"/> </Grid> </DataTemplate>
The data context of the view containing the XAML is set to AncillaryBaseViewModel. The following is a cut down version of AncillaryBaseViewModel.
public class AncillaryBaseViewModel : ComplexOrderItemViewModel, IDataErrorInfo
{
private ObservableCollection<ExtendedPropertyViewModel> _ancillaryExtendedPropertyViewModels;
public ObservableCollection<ExtendedPropertyViewModel> AncillaryExtendedPropertyViewModels
{
get { return _ancillaryExtendedPropertyViewModels; }
set
{
_ancillaryExtendedPropertyViewModels = value;
OnPropertyChanged("AncillaryExtendedPropertyViewModels");
}
}
and the ExtendedPropertyViewModel class....
public class ExtendedPropertyViewModel : DataTemplateSelector
{
private ExtendedProperty _extendedProperty;
public DataTemplate DefaultnDataTemplate { get; set; }
public DataTemplate BooleanDataTemplate { get; set; }
public DataTemplate EnumDataTemplate { get; set; }
public ExtendedPropertyEnum ExtendedPropertyEnum
{
get
{ return ExtendedProperty.ExtendedPropertyEnum; }
set
{
if (ExtendedProperty.ExtendedPropertyEnum != value)
{
_extendedProperty.ExtendedPropertyEnum = value;
AncillaryBaseViewModel parent = RequestParent();
if (parent != null)
{
parent.AncillaryExtendedPropertyViewModels[7].ExtendedPropertyEnum =
ExtendedProperty.ExtendedPropertyEnum.DependentExtendedPropertyEnums[0];
}
parent.OrderItem.SetStockCode();
PropertyChanged += parent.OnExtendedPropertyChanged;
OnPropertyChanged();
}
}
}
and the ExtendedProperty class....
public class ExtendedProperty : ViewModelBase
{
private ExtendedPropertyEnum _extendedPropertyEnum;
public int ExtendedPropertyID { get; set; }
public int OrderItemTypeID { get; set; }
public string DisplayName {get; set;}
public ObservableCollection<ExtendedPropertyEnum> ExtendedPropertyEnumList { get; set; }
public string Value { get; set; }
public ExtendedPropertyEnum ExtendedPropertyEnum
{
get
{
return _extendedPropertyEnum;
}
set
{
_extendedPropertyEnum = value;
OnPropertyChanged("ExtendedPropertyEnum");
}
}
}
To summarise, when I change the value of combo box A through the UI, this calls the ExtendedPropertyEnum setter within ExtendedPropertyViewModel, which changes the ExtendedPropertyEnum bound to another combo box B, which is in the same list. I would expect this to update the value displayed in combo box B accordingly, which it does not.
As an aside, changing the value of combo box A does update a label that is not within a data template. The XAML for this label is....
<Label Content="{Binding StockCode}" MinWidth="100"/>
This is updated by the following code within AncillaryBaseViewModel....
public void OnExtendedPropertyChanged(object sender, EventArgs args)
{
OnPropertyChanged("StockCode");
}
I thought I could change this to the following to force the combo boxes in the list to update.
public void OnExtendedPropertyChanged(object sender, EventArgs args)
{
OnPropertyChanged("StockCode");
OnPropertyChanged("AncillaryExtendedPropertyViewModels");
}
However, this did not work either.
Any help greatly appreciated.
Roger.
if I understand the question correctly then you are expecting a changed value within an observablecollection to be reflected within the UI. This will not happen. observablecollections raise notifyproperty events when the collection itself changes, not when values within them change. You'll either need to raise a notifyproperty event on the value changing or remove the item from the list and add it back with a new value.

WPF Binding property of an Object contained within Another Object contained in a List

I have a ListBox showing some information about Cars. So, I have ViewModel with:
private ObservableCollection<Car> _cars;
public ObservableCollection<Car> CarsList
{
get
{
return _cars;
}
set
{
_cars= value;
OnPropertyChanged("CarsList");
}
}
I have implemented PropertyChanged, and I get my list of cars just fine. I load my CarsList from within my ViewModel (not from Window).
My Car object has Name, Country, Url and Image object like below:
public class Car
{
public string Name { get; set; }
public string Country { get; set; }
public Image Image { get; set; }
}
public class Image
{
public string Url { get; set; }
}
I need to bind Name, Country and Image.Url to text boxes in my ListBox. I did this like:
<ListBox Name="lbEvents" ItemsSource="{Binding Path=CarsList}">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<StackPanel>
<TextBlock Name="txtCarName" Text="{Binding Path=Name}" />
<TextBlock Name="txtCarCountry" Text="{Binding Path=Country}"/>
<!-- How do I bind this properly? -->
<Image Name="imgCarImage" Source="{Binding Path=Car.Image.Url}" />
</StackPanel>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Problem is that my Car Name and Country are properly displayed in ListBox for each Car. But Car.Image.Url shows nothing. I am new to WPF, how do I bind properly the Image.Url to the ImageBox?
Just as Name and Country are properties of the Car class, so is Image. Look at the way you are binding to Name and Country, you don't specify it as Car.Name, so why do that for Image? Just do it as Image instead of Car.Image.

Binding to Complex Objects in the ViewModel from the View?

Say for example I have the following type:
public class Site
{
public string Name { get; set; }
public int SiteId { get; set; }
public bool IsLocal { get; set; }
}
The above type can be assigned to be held in a Propety in a ViewModel like so assuming a corresponding backing field has been created but omitted here ofc:
public Site SelectedSite
{
get { return _selectedSite; }
set
{
_selectedSite = value;
// raise property changed etc
}
}
In my xaml a straight forward binding would be:
<TextBlock x:Name="StatusMessageTextBlock"
Width="Auto"
Height="Auto"
Style="{StaticResource StatusMessageboxTextStyle}"
Text="{Binding MessageToDisplay,
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" />
Can you extend a binding by using the dot notation syntax? e.g:
<TextBlock x:Name="StatusMessageTextBlock"
Width="Auto"
Height="Auto"
Style="{StaticResource StatusMessageboxTextStyle}"
**Text="{Binding SelectedSite.Name,**
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" />
Seems like a an interesting feature but my gut instinct is a no as my DC is being assigned at RunTime so at DesignTime or CompileTime, I can't see any clues that could make this feature work or not?
Correct me if have misunderstood what a complex object is, I have simplified mine down for the sake of this question.
Of course this is possible. However, WPF needs to know when any property along the path has changed. To that end, you need to implement INotifyPropertyChanged (or other supported mechanisms). In your example, both Site and the VM containing SelectedSite should implement change notification).
Here's how you could implement the functionality you specified in your question:
// simple DTO
public class Site
{
public string Name { get; set; }
public int SiteId { get; set; }
public bool IsLocal { get; set; }
}
// base class for view models
public abstract class ViewModel
{
// see http://kentb.blogspot.co.uk/2009/04/mvvm-infrastructure-viewmodel.html for an example
}
public class SiteViewModel : ViewModel
{
private readonly Site site;
public SiteViewModel(Site site)
{
this.site = site;
}
// this is what your view binds to
public string Name
{
get { return this.site.Name; }
set
{
if (this.site.Name != value)
{
this.site.Name = value;
this.OnPropertyChanged(() => this.Name);
}
}
}
// other properties
}
public class SitesViewModel : ViewModel
{
private readonly ICollection<SiteViewModel> sites;
private SiteViewModel selectedSite;
public SitesViewModel()
{
this.sites = ...;
}
public ICollection<SiteViewModel> Sites
{
get { return this.sites; }
}
public SiteViewModel SelectedSite
{
get { return this.selectedSite; }
set
{
if (this.selectedSite != value)
{
this.selectedSite = value;
this.OnPropertyChanged(() => this.SelectedSite);
}
}
}
}
And your view might look something like this (assuming a DataContext of type SitesViewModel):
<ListBox ItemsSource="{Binding Sites}" SelectedItem="{Binding SelectedSite}"/>
Below is what worked for me:
public Site SelectedSite
{
get { return _selectedSite; }
set
{
_selectedSite = value;
RaisePropertyChanged("SelectedSite");
}
}
In my xaml I was able to do:
<TextBox Name="tbSiteName"
Width="250"
Height="30"
Margin="0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
IsReadOnly="True"
Style="{StaticResource MainTextBoxStyle}"
Text="{Binding SelectedSite.Name,
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" />
This allows you to access data members off the Site Type without having to create individual properties that wrap each data member on the Site Type. Then individual controls can bind to each property declared in the VM. In a one to one fashion, this aproach can become rather verbose. The binding extension attached to the Text property of the TextBox control shown above, shows that we are not binding to a simple straight forward property but actually to a custom type. Potentially removing the need to create more public properties.

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)

Resources