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.
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>
This question already has answers here:
How to get clicked item in ListView
(5 answers)
Closed 7 years ago.
I've got a ListView with a DataTemplate like this, using MVVM pattern
<ListView ItemsSource="{Binding Source}"
IsItemClickEnabled="True"
commands:ItemsClickCommand.Command="{Binding ItemClickedCommand}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding A}" />
<Button Content="{Binding B}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ItemsClickCommand is defined in this way
public static class ItemsClickCommand
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(BindableCommand), typeof(ItemsClickCommand), new PropertyMetadata(null, OnCommandPropertyChanged));
public static void SetCommand(DependencyObject d, BindableCommand value)
{
d.SetValue(CommandProperty, value);
}
public static BindableCommand GetCommand(DependencyObject d)
{
return (BindableCommand)d.GetValue(CommandProperty);
}
private static void OnCommandPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = d as ListViewBase;
if (control != null)
control.ItemClick += OnItemClick;
}
private static void OnItemClick(object sender, ItemClickEventArgs e)
{
var control = sender as ListViewBase;
var command = GetCommand(control);
if (command != null && command.CanExecute(e.OriginalSource))
command.ExecuteWithMoreParameters(e.OriginalSource, e.ClickedItem);
}
}
What I'm asking is how can I know if user tap on the TextBlock or Button.
I tried to handle ItemClickCommand event in this way in ViewModel to search controls in VisualTree (is this the best solution?), but the cast to DependencyObject doesn't work (returns always null)
public void ItemClicked(object originalSource, object clickedItem)
{
var source = originalSourceas DependencyObject;
if (source == null)
return;
}
There are a few solutions that come to mind
Solution 1
<ListView
x:Name="parent"
ItemsSource="{Binding Source}"
IsItemClickEnabled="True"
Margin="20">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding A}" />
<Button
Content="{Binding B}"
Command="{Binding DataContext.BCommand, ElementName=parent}"
CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Note how the ListView has the name set to "parent" with the attribute: x:Name="parent" and how the binding for the button's command uses that. Also note that the command will be provided with a parameter that is the reference to the data source for the element that was clicked.
The view model for this page will look like this:
public class MainViewModel : MvxViewModel
{
public ObservableCollection<MySource> Source { get; private set; }
public MvxCommand<MySource> BCommand { get; private set; }
public MainViewModel()
{
Source = new ObservableCollection<MySource>()
{
new MySource("e1", "b1"),
new MySource("e2", "b2"),
new MySource("e3", "b3"),
};
BCommand = new MvxCommand<MySource>(ExecuteBCommand);
}
private void ExecuteBCommand(MySource source)
{
Debug.WriteLine("ExecuteBCommand. Source: A={0}, B={1}", source.A, source.B);
}
}
'MvxCommand' is just a particular implementation of ICommand. I used MvvMCross for my sample code but you don't have to do that - you can use whatever MvvM implementation you need.
This solution is appropriate if the responsibility to handle the command lies with the view model for the page that contains the list.
Solution 2
Handling the command in the view model for the page that contains the list may not always be appropriate. You may want to move that logic in code that is closer to the element that is being clicked. In that case, isolate the data template for the element in its own user control, create a view model class that corresponds to the logic behind that user control and implement the command in that view model. Here is how the code would look like:
The XAML for the ListView:
<ListView
ItemsSource="{Binding Source}"
IsItemClickEnabled="True"
Margin="20">
<ListView.ItemTemplate>
<DataTemplate>
<uc:MyElement DataContext="{Binding Converter={StaticResource MySourceToMyElementViewModelConverter}}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The XAML for the user control representing one element:
<Grid>
<StackPanel>
<TextBlock Text="{Binding Source.A}" />
<Button Content="{Binding Source.B}" Command="{Binding BCommand}" />
</StackPanel>
</Grid>
The source code for MySourceToMyElementViewModelConverter:
public class MySourceToMyElementViewModelConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return new MyElementViewModel((MySource)value);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotSupportedException();
}
}
The view model for the main page:
public class MainViewModel : MvxViewModel
{
public ObservableCollection<MySource> Source { get; private set; }
public MainViewModel()
{
Source = new ObservableCollection<MySource>()
{
new MySource("e1", "b1"),
new MySource("e2", "b2"),
new MySource("e3", "b3"),
};
}
}
The view model for the user control representing one element in the list:
public class MyElementViewModel : MvxViewModel
{
public MySource Source { get; private set; }
public MvxCommand BCommand { get; private set; }
public MyElementViewModel(MySource source)
{
Source = source;
BCommand = new MvxCommand(ExecuteBCommand);
}
private void ExecuteBCommand()
{
Debug.WriteLine("ExecuteBCommand. Source: A={0}, B={1}", Source.A, Source.B);
}
}
Solution 3
Your sample assumes that the view model for the main page exposes a list of data model elements. Something like this:
public ObservableCollection<MySource> Source { get; private set; }
The view model for the main page could be changed so that it exposes a list of view model elements instead. Something like this:
public ObservableCollection<MyElementViewModel> ElementViewModelList { get; private set; }
Each element in ElementViewModelList would correspond to an element in Source. This solution can get slightly complex if the contents of Source changes at run time. The view model of the main page will need to observe Source and change ElementViewModelList accordingly. Going further don this path you may want to abstract the concept of a collection mapper (something similar with an ICollectionView) and provide some generic code for doing so.
For this solution, the XAML will look like this:
<ListView
ItemsSource="{Binding ElementViewModelList}"
IsItemClickEnabled="True"
Margin="20">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding A}" />
<Button Content="{Binding B}" Command="{Binding BCommand}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Notes for Solution 1, 2 and 3
I see that your original sample associates a commanding not with the button inside of the element but with the entire element. That raises the question: what are you going to do with the inner button? Will you have a situation where the user can click either on the element or on the inner button? That may not be the best solution as far as UI/UX goes. Be mindful of that. Just as an exercise and in order to get closer to your original sample, here is what you can do if you want to associate commanding with the entire element.
Wrap your entire element in a button with a custom style. That style will modify the way a click is handled visually. The simplest form of that is to have the click not create any visual effect. This change applied to Solution 1 (it can easily be applied to Solution 2 and Solution 3 as well) would look something like this:
<ListView
x:Name="parent"
ItemsSource="{Binding Source}"
IsItemClickEnabled="True"
Margin="20">
<ListView.ItemTemplate>
<DataTemplate>
<Button
Command="{Binding DataContext.BCommand, ElementName=parent}"
CommandParameter="{Binding}"
Style="{StaticResource NoVisualEffectButtonStyle}">
<StackPanel>
<TextBlock Text="{Binding A}" />
<TextBlock Text="{Binding B}" />
</StackPanel>
</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In this case you would have to write NoVisualEffectButtonStyle but that is a simple task. You would also need to decide what kind of commanding you want to associate with the inner button (otherwise why would you have an inner button). Or, more likely you could transform the inner button in something like a textbox.
Solution 4
Use Behaviors.
First, add a reference to "Behaviors SDK".. Then modify your XAML code:
...
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
...
<Grid>
<ListView ItemsSource="{Binding Source}" IsItemClickEnabled="True" Margin="20">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ItemClick">
<core:InvokeCommandAction
Command="{Binding BCommand}"
InputConverter="{StaticResource ItemClickedToMySourceConverter}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding A}" />
<TextBlock Text="{Binding B}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
ItemClickedToMySourceConverter is just a normal value converter:
public class ItemClickedToMySourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return (MySource)(((ItemClickEventArgs)value).ClickedItem);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotSupportedException();
}
}
The view model will look like this:
public class Main4ViewModel : MvxViewModel
{
public ObservableCollection<MySource> Source { get; private set; }
public MvxCommand<MySource> BCommand { get; private set; }
public Main4ViewModel()
{
Source = new ObservableCollection<MySource>()
{
new MySource("e1", "b1"),
new MySource("e2", "b2"),
new MySource("e3", "b3"),
};
BCommand = new MvxCommand<MySource>(ExecuteBCommand);
}
private void ExecuteBCommand(MySource source)
{
Debug.WriteLine("ExecuteBCommand. Source: A={0}, B={1}", source.A, source.B);
}
}
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>
I created a attached property for a Button to set a TextBlock Text property in a canvas in Button ControlTemplate but I get this exception in design time in Visual Studio 2013. In Blend show an errorbox in place of button, but at Run time it`s working fine.
This is the attached property Class:
public class FButton
{
public static readonly DependencyProperty TextBlockTextProperty =
DependencyProperty.RegisterAttached("TextBlockText",
typeof(string),
typeof(FButton),
new FrameworkPropertyMetadata(null));
public static string GetTextBlockText(DependencyObject d)
{
return (string)d.GetValue(TextBlockTextProperty);
}
public static void SetTextBlockText(DependencyObject d, string value)
{
d.SetValue(TextBlockTextProperty, value);
}
}
This is the TextBlock in ControlTemplate:
<TextBlock x:Name="F1" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(custom:FButton.TextBlockText)}" Foreground="Black" FontSize="14" IsHyphenationEnabled="True" LineStackingStrategy="BlockLineHeight" LineHeight="14" TextAlignment="Left" TextWrapping="Wrap" Opacity="0.895"/>
And this is the Button:
<Button x:Name="btnF1" Template="{StaticResource TmpBtnF}" custom:FButton.TextBlockText="F1" Content="Button" Grid.Column="2" Margin="0,7,-1,618.669" Grid.Row="1"/>
And if is not a problem, can you give me some idea, how I can push this button on F1 keypress?
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()); }
}