I've got a form with an Image component. I would like load image at runtime.
In my ViewModel, I've a property which represents the Path of the image source :
public string ImagePath { get; set; }
This property is binding to Source of my ImageComponent.
The problem is, when I start my application, ImagePath is null, and the default converter try to convert ImagePath to System.Windows.Media.ImageSource and raise an exception.
I've thought of 3 solutions :
- Create a custom converter (which can give a default ImageSource when string is null)
- Prevent view to get ImagePath (Don't know how)
- Use a System.Windows.Media.ImageSource instead of string. (not sure the MVVM pattern is fulfill cause System.Windows.Media is only used by the view)
So my question is: Which solution is better (not only my 3) and what would be the implementation?
The XAML Binding :
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Image Name="image" Stretch="Uniform" Source="{Binding Main.ImagePath, Mode=OneWay, Source={StaticResource Locator}}" />
</ScrollViewer>
The exception raised :
System.Windows.Data Error: 23 : Cannot convert '' from type 'String' to type 'System.Windows.Media.ImageSource' for 'en-US' culture with default conversions;
As I said in a previous comment, you don't need a custom converter to manage null values. You can use TargetNullValue in the binding of the source :
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Image Name="image" Stretch="Uniform" Source="{Binding Main.ImagePath, TargetNullValue={x:Null}, Mode=OneWay, Source={StaticResource Locator}}" />
</ScrollViewer>
Moreover you could specified a default path in TargetNullValue if you wanted to.
I've just started a vanilla MVVM Light project (.NET 4.5), and added the following stuff to my code and I don't get the error you are describing.
MainViewModel:
public class MainViewModel : ViewModelBase
{
public string ImagePath
{
get
{
return _imagePath;
}
set
{
_imagePath = value;
RaisePropertyChanged(() => ImagePath);
}
}
private string _imagePath;
public RelayCommand ImageCommand
{
get
{
return _imageCommand ??
(_imageCommand = new RelayCommand(() => ImagePath = "Image.png"));
}
}
private RelayCommand _imageCommand ;
public MainViewModel()
{
// I've tried both of those and it still works
//ImagePath = "";
//ImagePath = null;
}
}
MainWindow Content XAML:
<StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<Image x:Name="TestImage"
Source="{Binding Main.ImagePath,
Mode=OneWay,
Source={StaticResource Locator}}"
Stretch="Uniform" />
</ScrollViewer>
<Button Command="{Binding Main.ImageCommand,
Source={StaticResource Locator}}"
Content="Click" />
</StackPanel>
Related
I am trying to make a contactlist with 2 different types of contacts, FysiekContactPersoon (Fysical persons) and WinkelOfBedrijf (Corporates). They both are inherited from the class ContactPersoon.
my MainWindow.xaml.cs
public partial class MainWindow : Window
{
ContactPersoonViewModel _viewmod = null;
public ContactPersoonViewModel ViewMod
{
get { _viewmod ??= new ContactPersoonViewModel(); return _viewmod; }
set => _viewmod = value;
}
public MainWindow()
{
InitializeComponent();
ViewMod.Import();
DataContext = ViewMod;
}
private void InfoButton_Click(object sender, RoutedEventArgs e)
{
DialogInfo dlg = new DialogInfo(ViewMod) { Owner = this };
if (dlg.ShowDialog() == true) { }
}
}
When the user selects a contact from a datagrid on mainwindow and presses the Info button, the dialog window opens.I have created two templates that normally have to be applied each to its corresponding class.But the dialogwindow is empty, despite the fact that the current item is shown properly in the viewModel when debugging.
My dialoginfo.xaml (simplified):
<ContentControl DataContext="{Binding CurrentCP}" Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="x:Type local:FysiekeContactpersoon">
<StackPanel Margin="5,5,5,5" HorizontalAlignment="Center" VerticalAlignment="Center">
<Label Content="Person:" HorizontalAlignment="Center" VerticalAlignment="Center" Width="114" Height="26" />
<TextBox x:Name="ContactNaam" HorizontalAlignment="Center" TextWrapping="Wrap" Text="{Binding Naam}" VerticalAlignment="Center" Width="218" Height="22"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="x:Type local:WinkelOfBedrijf">
<StackPanel Margin="5,5,5,5" HorizontalAlignment="Center" VerticalAlignment="Center">
<Label Content="Corporate:" HorizontalAlignment="Center" VerticalAlignment="Center" Width="114" Height="26" />
<TextBox x:Name="ContactNaam" HorizontalAlignment="Center" TextWrapping="Wrap" Text="{Binding Naam}" VerticalAlignment="Center" Width="218" Height="22"/>
</StackPanel>
</DataTemplate>
</ContentControl.Resources>
and my dialoginfo.xaml.cs
public partial class DialogInfo : Window
{
ContactPersoonViewModel _viewModel = null;
public ContactPersoonViewModel ViewModel { get => _viewModel; set => _viewModel = value; }
public DialogInfo(ContactPersoonViewModel vm)
{
ViewModel = vm;
InitializeComponent();
DataContext = vm.CurrentCP;
}
What am I doing wrong here? I was through a lot of similar threads, mostly pointing at this solution as correct and the simplest one, comparing with DataTemplateSelector or Property setters and triggers (which also aren't working with me- I've tried :().
Moreover, I have each second time a compilation fail "The key had already been added" of something, but the next compilation is perfectly succeeded after no code has been changed at all(WTF??!).Needless to say, how disappointed I am in XAML. I would appreciate some help in the form of a piece of a suitable code, or a very good tutorial link.
It looks like your code should basically work. The only problem I found is you type declaration on the DataTemplate.
For properties of type Type like Style.TargetType the XAML engine will convert the string representation of a type to an actual Type instance.
But this is not the case for properties like DataTemplate.DataType. Since DataTemplate defines the property DataType of type object, there will be no internal conversion from string to Type.
This is because DataTemplate.DataType expects a string for XML types and Type for objects.
Because you assigned a string to DataTemplate.DataType, no object type is resolved, as the data object is expected to be a XML object.
Using x:Type in order to define a Type rather than a string is correct, but you simply forgot to mark the declaration as markup extension using curly braces! Without this braces you are just defining a string value.
The correct syntax is:
<DataTemplate DataType="{x:Type local:FysiekeContactpersoon}">
...
</DataTemplate>
I am making a small UWP app in which each page has a similar layout. I have created a custom UserControl following this thread, but I am not able to wrap my head around how to pass an Image Source to the background image of the UserControl.
If I remove the references to bgImage, and only mainContent, the whole thing works as expected.
How do I pass a background image source or URI to a UserControl for use with a control?
UserControl XAML:
<UserControl
x:Class="App1.MainTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid Background="Black">
<!-- Background image grid -->
<Grid Margin="0">
<Grid.Background>
<ImageBrush Opacity="100" ImageSource="{x:Bind bgImage}" Stretch="UniformToFill"/>
</Grid.Background>
<!-- Content grid -->
<Grid Margin="25" x:Name="contentGrid" Opacity="100">
<!-- Darkened insert -->
<Border BorderThickness="1" CornerRadius="8" BorderBrush="Black">
<Rectangle Name="Background" Opacity="0.55" Fill="Black" />
</Border>
<StackPanel VerticalAlignment="Center">
<!-- Content here. -->
<ContentPresenter Content="{x:Bind mainContent}" />
</StackPanel>
</Grid>
</Grid>
</Grid>
</UserControl>
MainTemplate.CS:
public sealed partial class MainTemplate : UserControl
{
public MainTemplate()
{
this.InitializeComponent();
}
public static readonly DependencyProperty bgImageProperty = DependencyProperty.Register("bgImage", typeof(ImageSource), typeof(MainTemplate), new PropertyMetadata(null));
public object bgImage
{
get { return GetValue(bgImageProperty); }
set { SetValue(bgImageProperty, value); }
}
public static readonly DependencyProperty mainContentProperty = DependencyProperty.Register("mainContent", typeof(object), typeof(MainTemplate), new PropertyMetadata(null));
public object mainContent
{
get { return GetValue(mainContentProperty); }
set { SetValue(mainContentProperty, value); }
}
}
And finally, an example from one of the pages I am trying to use (MainPage.xaml):
<local:MainTemplate>
<local:MainTemplate.bgImage>
<Image Source="Assets/backgrounds/tailings 2.jpg"/>
</local:MainTemplate.bgImage>
<local:MainTemplate.mainContent>
<Button
x:Name="app1"
Content=""
HorizontalAlignment="Center"
Click="app1_Click"
Width="426"
Height="134"
Style="{StaticResource noMouseoverHover}"
>
<Button.Background>
<ImageBrush ImageSource="Assets/logos/image.png"/>
</Button.Background>
</Button>
</local:MainTemplate.mainContent>
</local:MainTemplate>
The error I get on compile/run is:
Error Invalid binding path 'bgImage' : Cannot bind type 'System.Object' to 'Windows.UI.Xaml.Media.ImageSource' without a converter App1
As already shown in Matt's answer, the type of the bgImage property should be ImageSource:
public ImageSource bgImage
{
get { return (ImageSource)GetValue(bgImageProperty); }
set { SetValue(bgImageProperty, value); }
}
Besides that, you can't assign an Image control to the property, as you are trying to do here:
<local:MainTemplate>
<local:MainTemplate.bgImage>
<Image Source="Assets/backgrounds/tailings 2.jpg"/>
</local:MainTemplate.bgImage>
...
</local:MainTheme>
Instead, you should assign a BitmapImage, like:
<local:MainTemplate>
<local:MainTemplate.bgImage>
<BitmapImage UriSource="Assets/backgrounds/tailings 2.jpg"/>
</local:MainTemplate.bgImage>
...
</local:MainTheme>
Or shorter, taking advantage of built-in type conversion:
<local:MainTheme bgImage="/Assets/backgrounds/tailings 2.jpg">
...
</local:MainTheme>
In addition to the above, you should also set the x:Bind Mode to OneWay, because the default is OneTime. Later changes to the bgImage property would otherwise be ignored:
<ImageBrush ImageSource="{x:Bind bgImage, Mode=OneWay}" ... />
Finally, there are widely accepted naming conventions regarding property names in .NET. They should start with an uppercase letter, so yours should probably be BgImage.
Don't you just need to set the property to be an ImageSource and not an object
public ImageSource bgImage
{
get { return (ImageSource)GetValue(bgImageProperty); }
set { SetValue(bgImageProperty, value); }
}
I have a ViewModel
public class ViewModel:ViewModelObject
{
public ViewModel()
{
ProjectionDataElementList = new ObservableCollection<ProjectionDataElement>();
}
public ObservableCollection<ProjectionDataElement> ProjectionDataElementList { get; set; }
private ProjectionDataElement _currentSelectedProjectionDataElement;
public ProjectionDataElement CurrentSelectedProjectionDataElement
{
get
{
return _currentSelectedProjectionDataElement;
}
set
{
_currentSelectedProjectionDataElement = value;
OnPropertyChanged("CurrentSelectedProjectionDataElement");
}
}
}
A control called ProjectorDisplayControl
<UserControl x:Class="Fast_Project.ProjectorDisplayControl"
..................>
<Viewbox>
<StackPanel>
<TextBlock Text="{Binding Path=TextBody}"/>
</StackPanel>
</Viewbox>
public partial class ProjectorDisplayControl : UserControl
{
public ProjectorDisplayControl()
{
InitializeComponent();
}
public static readonly DependencyProperty ProjectedDataProperty = DependencyProperty.Register("ProjectedData", typeof(ProjectionDataElement), typeof(ProjectorDisplayControl),
new PropertyMetadata(new PropertyChangedCallback((objectInstance, arguments) =>
{
ProjectorDisplayControl projectorDisplayControl = (ProjectorDisplayControl)objectInstance;
projectorDisplayControl._projectedData = (ProjectionDataElement)arguments.NewValue;
})));
public ProjectionDataElement ProjectedData
{
get
{
return (ProjectionDataElement)GetValue(ProjectedDataProperty);
}
set
{
SetValue(ProjectedDataProperty, value);
}
}
private ProjectionDataElement _projectedData
{
get
{
return this.DataContext as ProjectionDataElement;
}
set
{
this.DataContext = value;
}
}
}
That control is used in two places.
First place is in a ListBox where it works great:
<ListBox Grid.Column="0" ItemsSource="{Binding Path=ProjectionDataElementList}" Name="projectedDataListBox"
SelectedItem="{Binding Path=CurrentSelectedProjectionDataElement, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border BorderThickness="1" BorderBrush="Black">
<controls:ProjectorDisplayControl x:Name="_projectorDisplay" ProjectedData="{Binding}"/>
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Second place is on top of a form where I set the form's DataContext with a ViewModel object. To make the ProjectorDisplayControl consume the CurrentSelectedProjectionDataElement in the ViewModel I would expect to have to do this:
<Window x:Class="Fast_Project.DisplayWindow"
................>
<Viewbox>
<StackPanel>
<controls:ProjectorDisplayControl x:Name="_projectorDisplay" ProjectedData="{Binding CurrentSelectedProjectionDataElement}"/>
</StackPanel>
</Viewbox>
That code gives me two binding errors:
System.Windows.Data Error: 40 :
BindingExpression path error:
'TextBody' property not found on 'object' ''ViewModel' (HashCode=2512406)'.
BindingExpression:Path=TextBody;
DataItem='ViewModel' (HashCode=2512406);
target element is 'TextBlock' (Name='');
target property is 'Text' (type 'String')
System.Windows.Data Error: 40 :
BindingExpression path error:
'CurrentSelectedProjectionDataElement' property not found on 'object' ''ProjectionDataElement' (HashCode=37561097)'.
BindingExpression:Path=CurrentSelectedProjectionDataElement;
DataItem='ProjectionDataElement' (HashCode=37561097);
target element is 'ProjectorDisplayControl' (Name='_projectorDisplay');
target property is 'ProjectedData' (type 'ProjectionDataElement')
When I watch the setter of the private property _projectedData on ProjectorDisplayControl which sets the DataContext I first see it get set with a valid value and then set to null.
In the DisplayWindow that holds the single ProjectorDisplayControl I can remove the binding to the CurrentSelectedProjectionDataElement and then I only get the first binding error message:
<Viewbox>
<StackPanel>
<controls:ProjectorDisplayControl x:Name="_projectorDisplay" />
</StackPanel>
</Viewbox>
The first binding error makes me feel like the ProjectorDisplayControl's DataContext is getting set with a ViewModel object when the DisplayWindow's DataContext is getting set. But from what I've read controls don't share the same data context with their parent window unless you set it so.
I've treating the binding path for the ProjectorDisplayControl.ProjectedData in DisplayWindow like it was a ProjectionDataElement object as the second error message states.
<Viewbox>
<StackPanel>
<controls:ProjectorDisplayControl x:Name="_projectorDisplay" ProjectedData="{Binding }"/>
</StackPanel>
</Viewbox>
Then is tells me:
Cannot create default converter to perform 'one-way' conversions between types 'Fast_Project.ViewModel' and 'Fast_Project.ProjectionDataElement'
Like it really was the ViewModel object like I thought it was in the first place...
Anyways I suspect that the root of my problem lies in the how I see the ProjectorDisplayControl having a DataContext set to the ViewModel object. Anyone see where I messed up?
Thank you all for your help!
The solution was:
ProjectorDisplayControl
<StackPanel>
<TextBlock Text="{Binding Path=ProjectedData.TextBody, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type controls:ProjectorDisplayControl}}}"/>
</StackPanel>
DisplayWindow
<StackPanel>
<controls:ProjectorDisplayControl x:Name="_projectorDisplay" ProjectedData="{Binding Path=ViewModel.CurrentSelectedProjectionDataElement,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type controls:DisplayWindow}}}"/>
</StackPanel>
Child control inherits the Dependency property value (or DataContext in this case) from their Parents.
When you are using the UserControl inside the itemTempalte, then the DataContext of each item is already ProjectionDataElement and hence the DataContext of your control is set to ProjectionDataElement.
When you are using the control inside parent it will inherit its DataContext.
The problem is that you are setting your ProjectedData property on control and not using it inside it. If you want that each of your control should be bind to the value set on ProjectedData then you should update the bindings like:
<UserControl x:Class="Fast_Project.ProjectorDisplayControl"
..................>
<Viewbox>
<StackPanel>
<TextBlock Text="{Binding Path=ProjectedData.TextBody, RelativeSource={RelativeSource Self}"/>
</StackPanel>
For you second error, you must be setting the DataContext of that window to ProjectionDataElement somewhere that is why is searching it inside it.
In a LongListSelector, I have multiple items shown, according to the following DataTemplate :
<TextBlock Text="{Binding Subject}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="Last modified :" Margin="15, 0, 5, 0" Foreground="LightGray" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock Text="{Binding LastModified}" Foreground="#989696" Style="{StaticResource PhoneTextNormalStyle}"/>
</StackPanel>
At this point, everything works fine, the MVVM and bindings are OK.
I wanted to move this XAML into an UserControl and bind those properties from it. And, I have thought to proceed in this way :
<UserControl x:Class="..."
xmlns=" ... "
Foreground="{StaticResource PhoneForegroundBrush}"
d:DesignHeight="100" d:DesignWidth="480">
<StackPanel x:Name="LayoutRoot" Background="Transparent">
<TextBlock x:Name="TitleTextBlock" Style="{StaticResource PhoneTextExtraLargeStyle}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="Last modified :" Margin="15, 0, 5, 0" Foreground="LightGray" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="LastModifiedDateTextBlock" Foreground="#989696" Style="{StaticResource PhoneTextNormalStyle}"/>
</StackPanel>
</StackPanel>
</UserControl>
And this is the C# class :
public partial class LongListSelectorItemControl
{
private DateTime _lastModifiedDate;
public string Title
{
get
{
return TitleTextBlock.Text;
}
set
{
TitleTextBlock.Text = value;
}
}
public DateTime LastModifiedDate
{
get
{
return _lastModifiedDate;
}
set
{
LastModifiedDateTextBlock.Text = value.ToString(CultureInfo.InvariantCulture);
_lastModifiedDate = value;
}
}
public LongListSelectorItemControl()
{
InitializeComponent();
_lastModifiedDate = new DateTime();
}
}
I have thought to use the user control in XAML in this way :
<userControls:LongListSelectorItemControl Title="{Binding Subject}" LastModifiedDate="{Binding LastModified}"/>
But something went wrong and I can't figure out what. I guess it has to do something with an incorrect binding... because in my application, a page is loaded with this XAML I presented in this issue and the app doesn't crash. Then the user has to navigate to another page, where some data is added and the ViewModel will have some data to show, so when it returns to the main page, this time, it simply crashes... (gets me to Application_UnhandledException method in App.xaml.cs to break the debugger.
Additional research
I've managed to track down the exception and it seems...
MS.Internal.WrappedException: Object of type 'System.Windows.Data.Binding' cannot be converted to type 'System.String'. ---> System.ArgumentException: Object of type 'System.Windows.Data.Binding' cannot be converted to type 'System.String'
I am still confused on how to fix this...
Any suggestions are welcome to aid me into figuring out what's wrong. Thanks!
To be able to bind to a property, it need to be a dependency property. Here is how the title property need to be modified:
public partial class LongListSelectorItemControl
{
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(LongListSelectorItemControl), new PropertyMetadata(default(string), TitlePropertyChanged));
private static void TitlePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LongListSelectorItemControl myControl=d as LongListSelectorItemControl;
myControl.TitleTextBlock.Text = e.NewValue as string;
}
public string Title
{
get { return (string) GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
....
}
You will need to do the same thing with the LastModifiedDate property.
I have an MVVM application that uses a listbox which is populated with images. The image string always comes from an object that I can't modify because it's generated using an edmx model.
To cut a story shory, I need to put into the following xaml a way to trim the whitespace put onto the end of the image path by SQL from the string.
<ListBox ItemsSource="{Binding AllImages}" x:Name="listBox1" Width="300" Margin="10,10,0,10">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Grid.Column="0" Source="{Binding imagePath}" Height="100" Width="100" />
<TextBlock Grid.Column="1" Text="{Binding imageId}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Is this possible?
Use a value converter in the binding which does the trimming for you.
If you do not want to use a converter your can do it right into your Property
INotifyChangedProperty Solution
private string _ImageID;
public string ImageID
{
get
{
return _ImageID;
}
set
{
value = (value == null ? value : value.Trim());
NotifyPropertyChanged("ImageID");
}
}
DependencyProperty Solution
public static readonly DependencyProperty ImageIDProperty =
DependencyProperty.Register("ImageID", typeof(string), typeof(MainWindowViewModel), new PropertyMetadata(string.Empty));
public string ImageID
{
get { return (string)GetValue(ImageIDProperty); }
set { SetValue(ImageIDProperty, value == null ? value : value.Trim()); }
}