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()); }
}
Related
I am using PRISM MVVM to show a Listview of files containing an image, a filename and the size of the image.
The user should be able to change the name of the file by typing in a new name.
When leaving the text box the filename in my ViewModel should be renamed. For this i need of course to know the Text as it was before and after.
I don't want to use Code behind, but I guess I need to work with GotFocus to store the value before and on LostFocus to get the new value. Right?
This is my XAML
<Grid>
<ListView x:Name="MiscFilesListView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding MiscFiles}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1" HorizontalAlignment="Stretch"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" VerticalAlignment="Top" HorizontalAlignment="Stretch">
<Image Source="{Binding ImageData}" HorizontalAlignment="Center" VerticalAlignment="Top" Height="100" Width="100" />
<TextBox Text="{Binding FileName}" HorizontalAlignment="Center" VerticalAlignment="Bottom" />
<TextBlock Text="{Binding Size}" HorizontalAlignment="Center" VerticalAlignment="Bottom" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
The Listview is bound to:
public ObservableCollection<MiscFile> MiscFiles
{
get => _miscFiles;
set => SetProperty(ref _miscFiles, value);
}
The viewmodel
public class MiscFile : INotifyPropertyChanged
{
public BitmapImage ImageData { get; set; }
public string FileName { get; set; }
public string FullFileName { get; set; }
public string Size { get; set; }
public void OnPropertyChanged(string propertyname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Any idea how I can achieve this inside the Viewmodel?
Do I need some sort of EventTrigger?
You could make a private field for the filename in the viewmodel. The public FileName property should check if the value is different from the value set in the private field. Also notify the INotifyPropertyChanged by calling OnPropertyChanged.
Doing this should update the filename property.
If you want to keep the old filename it is possible to call the Path.GetFileName(FullFileName) method of the static Path class.
private string _filename;
public BitmapImage ImageData
{
get;set;
}
public string FileName
{
get
{
return _filename;
}
set
{
if (_filename != value)
{
_filename = value;
OnPropertyChanged(nameof(FileName));
}
}
}
The previous value is already stored in your MiscFile object. Just define a backing field for your property:
private string _filename;
public string FileName
{
get { return _filename; }
set
{
string oldValue = _filename;
string newValue = value;
//update...
_filename = value;
}
}
This should work as the source property shouldn't be set until the TextBox loses focus since you haven't changed the UpdateSourceTrigger property of the binding from its default value of LostFocus:
<TextBox Text="{Binding FileName, UpdateSourceTrigger=LostFocus}" ... />
Currently i have an ObservableCollection of MyClass in my Viewmodel. I use the getter of this Collection to fill it from an other Datasource. I can now Display this Data in a Window(Grid) and the correct Data is shown, but when i change the Data the set is not fired(I think it is because not the Collection is changed, only a Element in the Collection). Should i create a Property for every Property of MyClass, so i can react to the changes of a single Value, the Questions i ask myself are:
How do i know what Element is selected at the moment
How to fill the Collection correct when i have a binding to every single item
I also thought of a Event when my Collection is changed, but i am not sure how to implement it right.
public ObservableCollection<MyClass<string>> SelectedParams
{
get
{
//Fill the Collection
}
set
{
//I think here i want to react to changed Textboxvalues in my View
}
}
public class MyClass<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private T _curValue;
private string _value1;
private string _value2;
public string Value1
{
get
{
return _value1;
}
set
{
if (_value1 != value)
{
_value1 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value1)));
}
}
}
public string Value2
{
get
{
return _value2;
}
set
{
if (_value2 != value)
{
_value2 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value2)));
}
}
}
public T curValue
{
get
{
return _curValue;
}
set
{
_curValue = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(curValue)));
}
}
public MyClass()
{
}
public MyClass(string val1, string val2, T curVal)
{
Value1 = val1;
Value2 = val2;
curValue = curVal;
}
}
The xaml Code looks something like this
<ItemsControl ItemsSource="{Binding SelectedParams}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Value1}"/>
<Label Grid.Column="1" Content="{Binding Value2}"/>
<TextBox Grid.Column="2" Text="{Binding curValue, Mode=TwoWay}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit1: Changed MyClass to INotifyPropertyChanged now the Collection changes internal Values but the Setter is still not called on change of a Value
You need to implement INotifyPropertChanged interface for MyClass and raise the PropertyChanged in setter to notify UI that the property value changed.
How do i know what Element is selected at the moment
If you want support for item selection you have to use an other control. ItemsControl does not support selection.
Use ListView for example. Bind ItemsSource and SelectedItem to your class. Now every time you click on an item, SelectedValue is updated. And if you change SelectedValue from code the UI updates the selected item in the list. You can also bind other controls to SelectedValue like I did with the TextBlock outside the ListView.
View
<StackPanel>
<ListView ItemsSource="{Binding Values}" SelectedItem="{Binding SelectedValue}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Item1}" />
<TextBlock Text="=" />
<TextBlock Text="{Binding Path=Item2}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Orientation="Horizontal">
<TextBox Text="Selected:" Background="DarkGray" />
<TextBox Text="{Binding SelectedValue.Item1, Mode=OneWay}" Background="DarkGray" />
</StackPanel>
</StackPanel>
Data
public class ListViewBindingViewModel : INotifyPropertyChanged
{
private Tuple<string,int> _selectedValue;
public ObservableCollection<Tuple<string,int>> Values { get; }
public Tuple<string, int> SelectedValue
{
get { return _selectedValue; }
set
{
_selectedValue = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedValue)));
}
}
public ListViewBindingViewModel()
{
Values = new ObservableCollection<Tuple<string, int>> {Tuple.Create("Dog", 3), Tuple.Create("Cat", 5), Tuple.Create("Rat",1)};
}
}
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>
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.
Hi all i have problem in this code, please help me..
I have view
<StackPanel Orientation="Horizontal" Margin="3">
<Label Content="Audit Type" MinWidth="100"/>
<Label Content=":"/>
<StackPanel Orientation="Vertical">
<ListBox ItemsSource="{Binding Items}" Margin="3" SelectionMode="Extended" MinWidth="180">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Name="check" Content="{Binding Value}" IsChecked="{Binding IsChecked, Mode=TwoWay}" Margin="3" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</StackPanel>
and for View model
private List<AuditTypeExport> _items;
private List<string> _value;
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
OnPropertyChanged("IsChecked");
}
}
public List<AuditTypeExport> Items
{
get { return _items; }
}
public List<string> Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged("Value");
}
}
And ViewModel Constractor
_items = _model.GetAuditType();
_value = _model.GetAuditType().Select(item => item.Name).ToList();
For your information
public class AuditTypeExport
{
private int _id;
private string _name;
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
The result : checkbox appeares, but the content doesn't and I don't have a clue why.
Question Number 2 : I want to get the value back, how can I do that?
Thank you
It is unclear how you are using your ViewModel. Is that bound to the form? Or each item in the ListBox?
It looks like your ListBox is bound to the Items collection of your VM, so the ItemTemplate will be used with a AuditTypeExport as the data context. You are binding to "Value" and "IsChecked" properties which do not exist on the AuditTypeExport class.
What you are trying to do here is bind a property of type List<String> Value to CheckBox's Content property which is of type Object.
To simplify, you are assigning a collection of strings to a string. Which is not a good thing. And that is why it does not work.
Try using ItemsControl to show Value property or use an IValueConverter to convert List<String> to comma separated string.