I'm trying to realize when the ValidationSummary really loads and how can I force it to be loaded.
I've suscribed to loaded event to force a page validation and this is only triggered when I cause any "new" validation or open a ComboBox or something like this.
Any idea?? Thanks in advance.
Here goes my view:
<Grid Margin="0,3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"></ColumnDefinition>
<ColumnDefinition Width="60" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<sdk:Label Grid.Column="0" Target="{Binding ElementName=txtImporteTotal}" Content="Total Acto" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="5,0"></sdk:Label>
<TextBox Grid.Column="1" Name="txtImporteTotal" Margin="5,0" VerticalAlignment="Center" HorizontalAlignment="Stretch" Text="{Binding ActoMedico.importe_total, Mode=TwoWay, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"></TextBox>
<sdk:DescriptionViewer Target="{Binding ElementName=txtImporteTotal}" Grid.Column="2"></sdk:DescriptionViewer>
<sdk:Label Grid.Column="3" Target="{Binding ElementName=txtImporteMedico}" Content="Total Médico" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="5,0"></sdk:Label>
<TextBox Grid.Column="4" Name="txtImporteMedico" Margin="5,0" VerticalAlignment="Center" HorizontalAlignment="Stretch" Text="{Binding ActoMedico.importe_medico, Mode=TwoWay, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"></TextBox>
<sdk:DescriptionViewer Target="{Binding ElementName=txtImporteMedico}" Grid.Column="5"></sdk:DescriptionViewer>
This is its code behind, where I'm forcing the validation:
public ActoMedico()
{
InitializeComponent();
this.validationSummary.Loaded += new RoutedEventHandler(validationSummary_Loaded);
}
void validationSummary_Loaded(object sender, RoutedEventArgs e)
{
this.forzarValidacion();
}
private void forzarValidacion()
{
this.txtImporteMedico.GetBindingExpression(TextBox.TextProperty).UpdateSource();
this.txtImporteTotal.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
And finally, this is the model:
#region importe_medico
public const string importe_medicoPropertyName = "importe_medico";
private double? _importe_medico;
[Display(Description = "Importe")]
[Required(ErrorMessage = "Debe indicar el importe")]
public double? importe_medico
{
get
{
return _importe_medico;
}
set
{
Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = importe_medicoPropertyName });
_importe_medico = value;
RaisePropertyChanged(importe_medicoPropertyName);
}
}
#endregion
#region importe_total
public const string importe_totalPropertyName = "importe_total";
private double? _importe_total;
[Display(Description = "Importe total")]
[Required(ErrorMessage = "Debe indicar el importe total")]
public double? importe_total
{
get
{
return _importe_total;
}
set
{
Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = importe_totalPropertyName });
_importe_total = value;
RaisePropertyChanged(importe_totalPropertyName);
}
}
#endregion
As far as I see, you use the binding like {Binding ActoMedico.importe_total} in your text boxes. It means that the object bound to the UserControl must contain the property ActoMedico and the value of this property must contain the inner property importe_total.
I've added the following code to the code behind:
public class MainViewModel
{
public MainViewModel()
{
this.ActoMedico = new ItemViewModel();
}
public ItemViewModel ActoMedico { get; set; }
}
//...
public ActoMedico()
{
InitializeComponent();
this.DataContext = new MainViewModel(); //should be set in order to make bindings work
this.validationSummary.Loaded += new RoutedEventHandler(validationSummary_Loaded);
}
In the code above the ItemViewModel class is the class which contains those two properties from you last block of code. This class has a different name in your application, but I don't know it so I've named it in my way.
So now the ValidationSummary control will be displayed but with messages like Input string had an incorrect format. It is because the TextBox control expects the string data type whereas your view model has the double? data type. You can write a converter as a workaround to this issue:
public class DoubleToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var str = (string)value;
double d;
if (!double.TryParse(str, out d))
return null;
return d;
}
}
Usage:
<UserControl.Resources>
<local:DoubleToStringConverter x:Key="DoubleToStringConverter" />
</UserControl.Resources>
<!-- ... -->
<TextBox Text="{Binding ActoMedico.importe_total, Mode=TwoWay, Converter={StaticResource DoubleToStringConverter} ...
Anyway data annotations aren't easy to work with, so I reccomend to use the MVVM pattern and the INotifyDataErrorInfo interface. I've implemented the example of such validation here in my post, you can download source code and look how it is implemented.
Finally I get this by forcing a combobox to drop-down, force all the form validations and then undo the drop-down.
So I get the ValidationSummary from the beginning (I want every error from the start to be shown and disable the "save" button for the user.
Thanks so much for your help #vorrtex.
Related
I'm trying to use a view-model-first approach and I've created a view-model for my customized chart control. Now, in my form, I want a TabControl that will display a list of XAML-defined charts defined as such:
<coll:ArrayList x:Key="ChartListTabs" x:Name="ChartList">
<VM:MyChartViewModel x:Name="ChartVM_Today" ChartType="Today" ShortName="Today"/>
<VM:MyChartViewModel x:Name="ChartVM_Week" ChartType="Week" ShortName="This Week"/>
<VM:MyChartViewModel x:Name="ChartVM_Month" ChartType="Month" ShortName="This Month"/>
<VM:MyChartViewModel x:Name="ChartVM_Qtr" ChartType="Quarter" ShortName="This Quarter"/>
<VM:MyChartViewModel x:Name="ChartVM_Year" ChartType="Year" ShortName="This Year"/>
<VM:MyChartViewModel x:Name="ChartVM_Cust" ChartType="Custom" ShortName="Custom"/>
</coll:ArrayList>
Trying to specify data templates for my tab headers and content, I have this:
<DataTemplate x:Key="tab_header">
<TextBlock Text="{Binding ShortName}" FontSize="16" />
</DataTemplate>
<DataTemplate x:Key="tab_content" DataType="{x:Type VM:MyChartViewModel}" >
<local:MyChartControl/>
</DataTemplate>
My TabControl is like this:
<TabControl ItemsSource="{StaticResource ChartListTabs}"
ItemTemplate="{StaticResource tab_header}"
ContentTemplate="{StaticResource tab_content}"
IsSynchronizedWithCurrentItem="True">
<!-- nothing here :) -->
</TabControl>
What happens is that the designer shows the tabs correctly and the first tab content (can't switch tabs because they are dynamically created) showing apparently the right view for the first chart, but when I run the application, all tabs show the same, default, uninitialized content (i.e. the same chart control without any properties set). Also, the instance seems to be the same, i.e. changing something on my custom control (e.g. a date box) this shows on all tabs.
It seems to me that the control (view) in the TabControl content stays the same (TabControl does this, as I've read elsewhere) and should only change DataContext when the tab changes, but it clearly doesn't.
Notes:
All my classes are DependencyObjects and my collections are ObservableCollections (with the exception of the ChartListTabs resource)
ShortName is the view-model property I want to have as tab header text
This question seems related but I can't connect the dots
Here is my solution used your code inside, please try to check this out.
Xaml
<Window x:Class="TabControTemplatingHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
xmlns:tabControTemplatingHelpAttempt="clr-namespace:TabControTemplatingHelpAttempt"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<collections:ArrayList x:Key="ChartListTabs" x:Name="ChartList">
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Today" ChartType="Today" ShortName="Today"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Week" ChartType= "Week" ShortName="This Week"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Month" ChartType="Month" ShortName="This Month"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Qtr" ChartType= "Quarter" ShortName="This Quarter"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Year" ChartType= "Year" ShortName="This Year"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Cust" ChartType= "Custom" ShortName="Custom"/>
</collections:ArrayList>
<DataTemplate x:Key="TabHeader" DataType="{x:Type tabControTemplatingHelpAttempt:MyChartViewModel}">
<TextBlock Text="{Binding ShortName}" FontSize="16" />
</DataTemplate>
<DataTemplate x:Key="TabContent" DataType="{x:Type tabControTemplatingHelpAttempt:MyChartViewModel}" >
<tabControTemplatingHelpAttempt:MyChartControl Tag="{Binding ChartType}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{StaticResource ChartListTabs}"
ItemTemplate="{StaticResource TabHeader}"
ContentTemplate="{StaticResource TabContent}"
IsSynchronizedWithCurrentItem="True"/>
</Grid></Window>
Converter code
public class ChartType2BrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var key = (ChartType) value;
SolidColorBrush brush;
switch (key)
{
case ChartType.Today:
brush = Brushes.Tomato;
break;
case ChartType.Week:
brush = Brushes.GreenYellow;
break;
case ChartType.Month:
brush = Brushes.Firebrick;
break;
case ChartType.Quarter:
brush = Brushes.Goldenrod;
break;
case ChartType.Year:
brush = Brushes.Teal;
break;
case ChartType.Custom:
brush = Brushes.Blue;
break;
default:
throw new ArgumentOutOfRangeException();
}
return brush;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Main VM
public class MyChartViewModel:BaseObservableDependencyObject
{
private ChartType _chartType;
private string _shortName;
public ChartType ChartType
{
get { return _chartType; }
set
{
_chartType = value;
OnPropertyChanged();
}
}
public string ShortName
{
get { return _shortName; }
set
{
_shortName = value;
OnPropertyChanged();
}
}
}
public enum ChartType
{
Today,
Week,
Month,
Quarter,
Year,
Custom,
}
Inner user control XAML
<UserControl x:Class="TabControTemplatingHelpAttempt.MyChartControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tabControTemplatingHelpAttempt="clr-namespace:TabControTemplatingHelpAttempt">
<UserControl.Resources>
<tabControTemplatingHelpAttempt:ChartType2BrushConverter x:Key="ChartType2BrushConverterKey" />
<DataTemplate x:Key="UserContentTemplateKey" DataType="{x:Type tabControTemplatingHelpAttempt:MyChartViewModel}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Fill="{Binding ChartType, Converter={StaticResource ChartType2BrushConverterKey}}"/>
<TextBlock Grid.Row="0" Text="{Binding ShortName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Grid Grid.Row="1" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ChartType, UpdateSourceTrigger=PropertyChanged}">
<Grid.DataContext>
<tabControTemplatingHelpAttempt:TabContentDataContext/>
</Grid.DataContext>
<Rectangle VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Fill="{Binding BackgroundBrush}"/>
<TextBlock Text="{Binding Description, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentControl Content="{Binding }" ContentTemplate="{StaticResource UserContentTemplateKey}"/>
<!--<Grid.DataContext>
<tabControTemplatingHelpAttempt:TabContentDataContext/>
</Grid.DataContext>
<Rectangle VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Fill="{Binding BackgroundBrush}"/>
<TextBlock Text="{Binding Code, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center"/>-->
</Grid>
Please keep in attention, that if you comment out the Grid.DataContext tag and comment in the ContentControl tag, your inner content won't be updated since it doesn't created depending on delivered MyChartViewModel. Elsewhere
I can't see any problems with your code.
Inner user control VM
public class TabContentDataContext:BaseObservableObject
{
private string _code;
private Brush _backgroundBrush;
public TabContentDataContext()
{
Init();
}
private void Init()
{
var code = GetCode();
Code = code.ToString();
BackgroundBrush = code%2 == 0 ? Brushes.Red : Brushes.Blue;
}
public virtual int GetCode()
{
return GetHashCode();
}
public string Code
{
get { return _code; }
set
{
_code = value;
OnPropertyChanged();
}
}
public Brush BackgroundBrush
{
get { return _backgroundBrush; }
set
{
_backgroundBrush = value;
OnPropertyChanged();
}
}
}
Observable object code
/// <summary>
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
Update
Base Observable Dependency Object code
/// <summary>
/// dependency object that implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableDependencyObject : DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
Regards.
While testing Ilan's answer I found out that when a DataContext is declared inside the control (i.e. as an instance of some class via a UserControl.DataContext tag), it imposes a specific object instance on the control and the ability to databind some other object to it is lost (probably because the WPF run-time uses SetData instead of SetCurrentData).
The recommended way to "test" your control in the designer is the d:DataContext declaration (which works only for the designer).
Essentially, I have a markup issue. I have come up with a few solutions but I can't help but feel like this should be simpler. Rather than lead you down my convoluted path I thought I would share the simplest implementation and ask how you would address it.
MainPage.xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="6" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="6" />
<ColumnDefinition />
<!--Additional Columns-->
</Grid.ColumnDefinitions>
<!--Row Definitions-->
<Label Grid.Row="0" Grid.Column="0" Content="Vin:" HorizontalAlignment="Right" />
<ctrl:CommandTextBox Grid.Row="0" Grid.Column="2" Command="{Binding CreateVehicleCommand}" CommandParameter="{Binding Text, RelativeSource={RelativeSource Self}}" />
<Label Grid.Row="0" Grid.Column="3" Content="Manufacturer:" HorizontalAlignment="Right" />
<TextBox Grid.Row="0" Grid.Column="5" IsEnabled="False" Text="{Binding Vehicle.Manufacturer, Mode=OneWay}" />
<!--Additional Read Only Values-->
</Grid>
Given the example above, how can I get the Contents of the Grid into a View given the constraint that the Command to create the vehicle is outside of the DataContext to be created(Vehicle)?
If you do wish to look at my specific attempt, that question is here UserControl's DependencyProperty is null when UserControl has a DataContext
how can I get the Contents of the Grid into a View given the
constraint that the Command to create the vehicle is outside of the
DataContext to be created(Vehicle)?
This feels like a race condition more than an MVVM problem. I will address the issue first but make a secondary suggestion after.
There are no reasons in which a ViewModel cannot contain another viewmodel as a reference and that reference is bound to using the INotifyPropertyChanged mechanisim.
Or that your xaml (view) page contains a static reference to a ViewModel which the page (view) does not directly use in its DataContext, but that a certain control cannot bind to that static outside of the data context of the containing control.
Either way one can provide access (as also mentioned in the response to the other post you provided) by pointing to itself to get data or to provide an alternate plumbing which gets the data.
Or you can flatten your viewmodel to contain more information and handle this IMHO race condition so that this situation doesn't arise and the control as well as the grid can access information in a proper format.
I can't fully address the problem because you are more aware of the design goals and hazards which now must be worked around.
I've come up with something, I'm relatively happy with. This has saved me from creating 100s of composite ViewModel's and while it does introduce some unnecessary complexity it does dramatically reduce the amount copy/paste code I need to write.
VMFactoryViewModel.cs
public class CreatedViewModelEventArgs<T> : EventArgs where T : ViewModelBase
{
public T ViewModel { get; private set; }
public CreatedViewModelEventArgs(T viewModel)
{
ViewModel = viewModel;
}
}
public class VMFactoryViewModel<T> : ViewModelBase where T : ViewModelBase
{
private Func<string, T> _createViewModel;
private RelayCommand<string> _createViewModelCommand;
private readonly IDialogService _dialogService;
/// <summary>
/// Returns a command that creates the view model.
/// </summary>
public ICommand CreateViewModelCommand
{
get
{
if (_createViewModelCommand == null)
_createViewModelCommand = new RelayCommand<string>(x => CreateViewModel(x));
return _createViewModelCommand;
}
}
public event EventHandler<CreatedViewModelEventArgs<T>> CreatedViewModel;
private void OnCreatedViewModel(T viewModel)
{
var handler = CreatedViewModel;
if (handler != null)
handler(this, new CreatedViewModelEventArgs<T>(viewModel));
}
public VMFactoryViewModel(IDialogService dialogService, Func<string, T> createViewModel)
{
_dialogService = dialogService;
_createViewModel = createViewModel;
}
private void CreateViewModel(string viewModelId)
{
try
{
OnCreatedViewModel(_createViewModel(viewModelId));
}
catch (Exception ex)
{
_dialogService.Show(ex.Message);
}
}
}
VMFactoryUserControl.cs
public class VMFactoryUserControl<T> : UserControl where T : ViewModelBase
{
public static readonly DependencyProperty VMFactoryProperty = DependencyProperty.Register("VMFactory", typeof(VMFactoryViewModel<T>), typeof(VMFactoryUserControl<T>));
public VMFactoryViewModel<T> VMFactory
{
get { return (VMFactoryViewModel<T>)GetValue(VMFactoryProperty); }
set { SetValue(VMFactoryProperty, value); }
}
}
GenericView.xaml
<ctrl:VMFactoryUserControl x:Class="GenericProject.View.GenericView"
x:TypeArguments="vm:GenericViewModel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctrl="clr-namespace:SomeProject.Controls;assembly=SomeProject.Controls"
xmlns:vm="clr-namespace:GenericProject.ViewModel">
<Grid>
<!-- Column Definitions -->
<!-- Row Definitions -->
<Label Grid.Row="0" Grid.Column="0" Content="Generic Id:" HorizontalAlignment="Right" />
<ctrl:CommandTextBox Grid.Row="0" Grid.Column="2"
Command="{Binding VMFactory.CreateViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding Text, RelativeSource={RelativeSource Self}}" />
<Label Grid.Row="0" Grid.Column="3" Content="Generic Property:" HorizontalAlignment="Right" />
<TextBox Grid.Row="0" Grid.Column="5" IsEnabled="False" Text="{Binding GenericProperty, Mode=OneWay}" />
<!--Additional Read Only Values-->
</Grid>
</ctrl:VMFactoryUserControl>
GenericView.xaml.cs
public partial class GenericView : VMFactoryUserControl<GenericViewModel>
{
public GenericView()
{
InitializeComponent();
}
}
MainPageViewModel.cs
public class MainPageViewModel : ViewModelBase
{
private readonly IDialogService _dialogService;
private GenericViewModel _generic;
private readonly VMFactoryViewModel<GenericViewModel> _genericFactory;
public GenericViewModel Generic
{
get { return _generic; }
private set
{
if (_generic != value)
{
_generic = value;
base.OnPropertyChanged("Generic");
}
}
}
public VMFactoryViewModel<GenericViewModel> GenericFactory
{
get { return _genericFactory; }
}
private void OnGenericFactoryCreatedViewModel(object sender, CreatedViewModelEventArgs<GenericViewModel> e)
{
Generic = e.ViewModel;
}
public MainPageViewModel(IDialogService dialogService)
{
_dialogService = dialogService;
_genericFactory = new VMFactoryViewModel<GenericViewModel>(_dialogService, x => new GenericViewModel(_dialogService, GetGeneric(x)));
_genericFactory.CreatedViewModel += OnGenericFactoryCreatedViewModel;
}
private Generic GetGeneric(string genericId)
{
// Return some Generic model.
}
}
MainPage.xaml
<Page x:Class="GenericProject.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vw="clr-namespace:GenericProject.View">
<StackPanel>
<!-- Headers and Additional Content. -->
<vw:EventView DataContext="{Binding Generic}"
VMFactory="{Binding DataContext.GenericFactory, RelativeSource={RelativeSource AncestorType={x:Type Page}}}" />
</StackPanel>
</Page>
In Silverlight, MVVM I have to create a property window, but I have just a
List<AProperty> object, where AProperty is an abstract class with some child classes.
I want to bind it to a Silverlight control (but to which one?), with some conditions:
some child-properties must be shown as a textbox, some as a checkbox, and some as a combobox. It comes from the dynamic type.
the AProperty class has a PropertyGroup and a Name field. The order must be alphabetic (PropertyGroup > Name)
Any idea or working example?
My code:
public abstract class AProperty {
private String _Name;
private String _Caption;
private String _PropertyGroup;
public String Name {
get { return _Name; }
set { _Name = value; }
}
public String Caption {
get { return _Caption; }
set { _Caption = value; }
}
public String PropertyGroup {
get { return _PropertyGroup; }
set { _PropertyGroup = value; }
}
}
List<AProperty> Properties;
And the xaml:
<ListBox ItemsSource="{Binding ... Properties ...}">
<!-- Here order the items -->
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Width="250"
Text="{Binding Path=Caption}" />
<!-- And here need I a textbox, or a checkbox, or a combobox anyway -->
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I've found value converters just for a control attribute, not for a whole stackpanel content.
You could use a value converter to check what Type the object is and return the correct control type.
Do you have any sample code so I could give a better example?
The ValueConverter Andy mentions should work. Underneath your Textblock have a ContentPresenter, and bind it's Content.
Content={Binding , ConverterParameter={StaticResource NAMEOFCONVERTER}}.
The binding to the property is empty since you already have the object you want as the context. Then just check the type and return the control you want. you may want to make usercontrol's that are just Textblocks, etc. so that you can set the binding there, otherwise you will have to do it in code.
Thanks Jesse and Andy, here is the solution
The converter:
public class PropertyConverter : IValueConverter {
public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) {
Binding ValueBinding = new Binding() {
Source = value,
Path = new PropertyPath( "Value" ),
Mode = BindingMode.TwoWay
};
Binding EditableBinding = new Binding() {
Source = value,
Path = new PropertyPath( "Editable" ),
Mode = BindingMode.TwoWay
};
if( value is PropertyString ) {
TextBox tb = new TextBox();
tb.SetBinding( TextBox.TextProperty, ValueBinding );
tb.SetBinding( TextBox.IsEnabledProperty, EditableBinding );
return tb;
} else if( value is PropertyBoolean ) {
CheckBox cb = new CheckBox();
cb.SetBinding( CheckBox.IsCheckedProperty, ValueBinding );
cb.SetBinding( CheckBox.IsEnabledProperty, EditableBinding );
return cb;
} ....
return null;
}
public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) {
throw new NotImplementedException();
}
}
And the xaml code:
...
xmlns:Converters="clr-namespace:MyConverters"
...
<UserControl.Resources>
<Converters:PropertyConverter x:Key="PropertyInput"/>
</UserControl.Resources>
...
<ListBox ItemsSource="{Binding Path=ItemProperties.GeneralProperties}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="320" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" Grid.Column="0" />
<ContentPresenter Content="{Binding Converter={StaticResource PropertyInput}}" Grid.Column="1" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Two artikel:
http://shawnoster.com/blog/post/Dynamic-Icons-in-the-Silverlight-TreeView.aspx
http://www.silverlightshow.net/items/Silverlight-2-Getting-to-the-ListBoxItems-in-a-ListBox.aspx
Here's what I've got - I'm writing an App that, among other things, reads an RSS feed to get episodes of a certain podcast, then displays each episode's title and description, with a "listen" and "watch" button. But not all the episodes have both options - the RSS will return an empty string instead of a URL for either option if it's not available. So I'm trying to use IValueConverter that I can bind IsDisabled to, which returns true if the bound data length is 0, and false otherwise. For now, I'm just testing it on the "watch" buttons, since the binding will be nearly identical for the "listen" buttons.
A snippet of MainPage.xaml.cs:
using System.Xml.Linq;
using System.Windows.Data;
namespace appname
{
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
WebClient PodcastListDownloader = new WebClient();
PodcastListDownloader.DownloadStringCompleted += new DownloadStringCompletedEventHandler(PodcastListDownloadCompleted);
PodcastListDownloader.DownloadStringAsync(new Uri("http://domain.tld/mobile_app/podcastfeed"));
}
void PodcastListDownloadCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error != null)
return;
XElement xmlPodcastList = XElement.Parse(e.Result);
PodcastListBox.ItemsSource = from PodcastEpisode in xmlPodcastList.Descendants("item")
select new PodcastItem
{
title = PodcastEpisode.Element("date").Value + " " + PodcastEpisode.Element("title").Value,
subtitle = PodcastEpisode.Element("subtitle").Value,
description = PodcastEpisode.Element("summary").Value,
audio = PodcastEpisode.Element("audio").Value,
video = PodcastEpisode.Element("video").Value,
};
}
private void PlayPodcast(object sender, RoutedEventArgs e)
{
Button btn = (Button)sender;
Microsoft.Phone.Tasks.MediaPlayerLauncher PodcastPlay = new Microsoft.Phone.Tasks.MediaPlayerLauncher();
PodcastPlay.Media = new Uri(btn.Tag.ToString());
PodcastPlay.Show();
}
}
public class PodcastItem
{
public string title { get; set; }
public string description { get; set; }
public string audio { get; set; }
public string video { get; set; }
public string subtitle { get; set; }
}
public class StringLengthVisibilityConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null || value.ToString().Length == 0)
{
return false;
}
else
{
return true;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
A snippet of MainPage.xaml:
<phone:PhoneApplicationPage
x:Class="CCoFnow.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="False">
<Grid x:Name="LayoutRoot" Background="Transparent">
<!--Panorama control-->
<controls:Panorama Title="AppName">
<controls:Panorama.Background>
<ImageBrush ImageSource="PanoramaBackground.png"/>
</controls:Panorama.Background>
<controls:PanoramaItem Header="Podcast" Foreground="{StaticResource PhoneAccentBrush}">
<ListBox Margin="0,0,-12,0" ItemsSource="{Binding Items}" Name="PodcastListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432">
<TextBlock Text="{Binding title}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding description}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
<StackPanel Orientation="Horizontal">
<Button Content="Listen" Width="215" Tag="{Binding audio}" Click="PlayPodcast"/>
<Button Content="Watch" Width="215" Tag="{Binding video}" Click="PlayPodcast" IsEnabled="{Binding video, Converter={StringLengthVisibilityConverter}}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PanoramaItem>
</controls:Panorama>
</Grid>
</phone:PhoneApplicationPage>
But the debugger is throwing two errors:
1) The tag
'StringLengthVisibilityConverter' does
not exist in XML namespace
'http://schemas.microsoft.com/winfx/2006/xaml/presentation
2) The type
'StringLengthVisibilityConverter' was
not found. Verify that you are not
missing an assembly and that all
referenced assemblies have been built
I set the converter to {StaticResource StringLengthVisibilityConverter} instead (per http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter(v=VS.95).aspx), and now there's just one error: The resource "StringLengthVisibilityConverter" could not be resolved. With this error, I can debug (run) the code, but all the "watch" buttons remain enabled.
So I'm guessing I'm calling it in the wrong namespace, but I can't seem to figure out the correct one. Can someone please point me in the right direction?
Thanks!
Edit: In the process of putting this together, I realized that I need to do this differently - the feed now has additional values I can databind to. However, I'm quite sure I'm going to need this functionality at some point in the future, so I'm going to post anyway. If there's an easy solution for the question, please let me know so I can learn and do it sucessfully next time!
The way you reference the converter isn't quite right. You need an instance of the converter available somwhere, e.g. in the page's Resources section:
<phone:PhoneApplicationPage xmlns:conv="namespace reference for your converter goes here"
...>
<phone:PhoneApplicationPage.Resources>
<conv:StringLengthVisibilityConverter x:Key="Length" />
</phone:PhoneApplicationPage.Resources>
Then you reference that converter by using a StaticResource reference with the x:Key that you gave the converter.
<Button Content="Watch"
Width="215"
Tag="{Binding video}"
Click="PlayPodcast"
IsEnabled="{Binding video, Converter={StaticResource Length}}"/>
I'll leave the discussion of your approach versus using commands and MVVM for another day :)
I have a UserControl called SharpComboBox. I am using MVVM model to populate the SharpComboBox with Categories. For that I need to set the ItemsSource property. Here is the usage of the SharpComboBox control.
<sharpControls:SharpComboBox ItemsSource="{Binding Path=Categories}" Grid.Column="1" Grid.Row="1" DisplayMemberPath="Title">
</sharpControls:SharpComboBox>
The Window is called the AddBook.xaml and here is the code behind:
public AddBooks()
{
InitializeComponent();
this.DataContext = new AddBookViewModel();
}
And here is the implementation of the AddBookViewModel.
public class AddBookViewModel
{
private CategoryRepository _categoryRepository;
public AddBookViewModel()
{
_categoryRepository = new CategoryRepository();
}
public List<Category> Categories
{
get
{
return _categoryRepository.GetAll();
}
}
And finally here is the SharpComboBox control:
<StackPanel Name="stackPanel">
<ComboBox x:Name="comboBox">
<ComboBox.ItemTemplate>
<DataTemplate>
<ItemsControl>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="2*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
</Grid>
<TextBlock Grid.Column="0" Grid.Row="0" Text="{Binding Path=Title}" Margin="10" />
<Image Grid.Column="1" Margin="10" Grid.Row="0" Width="100" Height="100" Stretch="Fill" Source="{Binding Path=ImageUrl}">
</Image>
</ItemsControl>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
And here is the code behind:
public partial class SharpComboBox : UserControl
{
public static DependencyProperty ItemsSourceProperty;
public SharpComboBox()
{
InitializeComponent();
this.DataContextChanged += new System.Windows.DependencyPropertyChangedEventHandler(SharpComboBox_DataContextChanged);
ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof (IEnumerable),
typeof (SharpComboBox), null);
comboBox.ItemsSource = ItemsSource;
}
public IEnumerable ItemsSource
{
get { return (IEnumerable) GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
void SharpComboBox_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
}
For some reason the ItemsSource property is always null.
UPDATED:
void SharpComboBox_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
var binding = new Binding();
binding.Source = this.DataContext;
**binding.Path = new PropertyPath("Categories");**
comboBox.SetBinding(ComboBox.ItemsSourceProperty, binding);
}
kek444 is very close, but there is one critical element missing. I've noticed your ViewModel doesn't implement INotifyPropertyChanged. This will prevent bindings from automatically refreshing when you've set that property. So, as kek444 mentioned, you are intially binding to null (because it's early) and then when you set it, you aren't informing your View of the change. It's pretty simple to change, though.
public class AddBookViewModel : INotifyPropertyChanged
{
event PropertyChangedEventHandler PropertyChanged;
public AddBookViewModel()
{
_categoryRepository = new CategoryRepository();
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Categories");
}
}
...
}
Anytime you change your backing store (CategoryRepository), you'll want to do this. There may be some additional complication here depending on your your repository is implemented, but this information ought to at least explain what is going on.
As a practice, I generally create a base ViewModel class that implements INotifyPropertyChanged so I can add a few helper methods that wrap that PropertyChanged logic up... I just call OnPropertyChanged("MyProp"); and that's it.
One other thing that might help you is that bindings will report to the debug output if you configure them correctly: Debugging WPF Binding
Hope this helps.
You cannot simply set the comboBox.ItemsSource from your property once in the constructor, who knows how early that happens. You need to set a binding between those two properties.