I'm trying to have a TextBox's content be validated using IDataErrorInfo. The source of the list below is a List and each item is display. When i put ValidatesOnDataErrors=True in the Binding for the Text on the TextBox, it's not working as expected. How do I do this?
<ItemsControl ItemsSource="{Binding Trainings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel>
<TextBlock Text="{Binding MobileOperator}" />
<TextBlock Text="{Binding LastUpdate}"/>
</StackPanel>
<StackPanel>
<TextBlock Text="Number trained*" />
<!-- ValidatesOnDataErrors doesn't work here-->
<TextBox
Text="{Binding NumberTrained,
ValidatesOnDataErrors=True}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Update: Posting a stripped down version of Model, ViewModel, View and CodeBehind
ViewModel and Model
public class MyViewModel : IDataErrorInfo, INotifyPropertyChanged
{
public MyViewModel()
{
Trainings = new List<MyModel>
{
new MyModel { NumberTrained = 5, MobileOperator = "MO 1", LastUpdate = DateTime.Now },
new MyModel { NumberTrained = 1, MobileOperator = "MO 2", LastUpdate = DateTime.Now },
};
OkButtonCommand = new ButtonCommand(OnClick);
}
private void OnClick()
{
PropertyChanged(this, new PropertyChangedEventArgs(""));
}
public event PropertyChangedEventHandler PropertyChanged;
public ICommand OkButtonCommand { get; private set; }
public List<MyModel> Trainings { get; private set; }
public string Error { get { return null; } }
public string this[string columnName]
{
get
{
string error = null;
switch (columnName)
{
case "NumberTrained":
error = "error from IDataErrorInfo";
break;
}
return error;
}
}
}
public class MyModel
{
public string MobileOperator { get; set; }
public DateTime LastUpdate { get; set; }
public int NumberTrained { get; set; }
}
public class ButtonCommand : ICommand
{
private Action _handler;
public event EventHandler CanExecuteChanged;
public ButtonCommand(Action handler) { _handler = handler; }
public bool CanExecute(object parameter) { return true; }
public void Execute(object parameter) { _handler(); }
}
Code Behind
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
DataContext = new MyViewModel();
}
}
View
<Canvas x:Name="LayoutRoot" Background="White">
<ItemsControl ItemsSource="{Binding Trainings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="{Binding MobileOperator}" Margin="15,15,0,0" FontWeight="Bold"/>
<TextBlock Text="{Binding LastUpdate, StringFormat=' - Last Updated: \{0:M/d/yy\}'}"
Margin="5,15,15,0" Foreground="Gray"/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="Number trained*" />
<TextBox Width="50" Height="20"
Text="{Binding NumberTrained, Mode=TwoWay, ValidatesOnExceptions=True, ValidatesOnDataErrors=True}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="ok" Width="100" Height="20" Canvas.Left="248" Canvas.Top="207" Command="{Binding OkButtonCommand}"/>
</Canvas>
I feel implementing IDataErrorInfo on ViewModel is more appropriate rather than on Model.
So in your case, you could have created an additional ViewModel (ex: MyModelViewModel) and include it as a List<MyModelViewModel> inside MyViewModel to be used as the ItemsSource.
Going by MVVM, if you feel you should have a corresponding View for it, you can extract out the DataTemplate of the ItemsControl to a new XMAL.
You need to implement IDataErrorInfo on your Model, not your ViewModel.
As it is right now, your a validation check is throwing an error when you try and validate the property MyViewModel.NumberTrained, which doesn't exist, so the validation error never gets called.
Related
I have two types of users (user/stylist and stylist inherits from user) and in profile page I need to build it different so I'm using data template selector but the view is not calling the selector
Here is the view
<ContentPage.Resources>
<DataTemplate x:Key="UserTemplate">
<StackLayout>
<Label Text="I'm a user"/>
</StackLayout>
</DataTemplate>
<DataTemplate x:Key="StylistTemplate">
<StackLayout BackgroundColor="DarkBlue">
<Label Text="I'm a stylist"/>
</StackLayout>
</DataTemplate>
<local:UserTypeSelector
x:Key="personDataTemplateSelector"
StylistTemplate="{StaticResource StylistTemplate}"
UserTemplate="{StaticResource UserTemplate}" />
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout
Margin="10"
BindableLayout.ItemTemplateSelector="{StaticResource personDataTemplateSelector}"
BindableLayout.ItemsSource="{Binding User}" />
</ContentPage.Content>
The selector class
IMPORTANT
I debugged it and the OnSelectTemplate it's not called at all
public class UserTypeSelector : DataTemplateSelector
{
public DataTemplate UserTemplate { get; set; }
public DataTemplate StylistTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
//return ((User)item).IsStylist ? StylistTemplate : UserTemplate
return StylistTemplate; this is for testing the selector
}
}
The code behind
public partial class ProfilePage : ContentPage
{
public ProfilePage()
{
InitializeComponent();
BindingContext = new ProfileViewModel();
}
}
And the view model
private User user { get; set; }
public User User { get; set; }
public ProfileViewModel()
{
//User = new User()
//{
// IsStylist = true, I tried to use this to test the selector but it throws
//}; an error **Object must implement IConvertible**
}
BindableLayout.ItemsSource should specifie the collection of IEnumerable items to be displayed by the layout.
So i modify your viewmodel,then it will work.
ProfileViewModel:
class ProfileViewModel
{
public ObservableCollection<User> Users { get; set; }
public ProfileViewModel()
{
Users = new ObservableCollection<User>()
{
new User(){ IsStylist = false},new User(){ IsStylist = false}
};
}
public class User
{
public bool IsStylist { get; set; }
}
}
the xaml :
<ContentPage.Resources>
<DataTemplate x:Key="UserTemplate">
<StackLayout>
<Label Text="I'm a user"/>
</StackLayout>
</DataTemplate>
<DataTemplate x:Key="StylistTemplate">
<StackLayout BackgroundColor="DarkBlue">
<Label Text="I'm a stylist"/>
</StackLayout>
</DataTemplate>
<local:UserTypeSelector
x:Key="personDataTemplateSelector"
StylistTemplate="{StaticResource StylistTemplate}"
UserTemplate="{StaticResource UserTemplate}" />
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout
Margin="10"
BindableLayout.ItemTemplateSelector="{StaticResource personDataTemplateSelector}"
BindableLayout.ItemsSource="{Binding Users}" />
</ContentPage.Content>
i want set name for check box and use in code for post method for api
<ListView ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout BackgroundColor="#eee" Orientation="Vertical">
<StackLayout Orientation="Horizontal">
<controls:CheckBox DefaultText="{Binding Name}" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Since you had used MVVM . I suggest that you should handle all logic in your ViewModel .You can get the value and index of CheckBox in ViewModel.
I used the CheckBox plugin from https://github.com/enisn/Xamarin.Forms.InputKit .
in your xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:App12"
xmlns:input="clr-namespace:Plugin.InputKit.Shared.Controls;assembly=Plugin.InputKit"
mc:Ignorable="d"
x:Name="contentPage" // set the name of content page
x:Class="xxx.MainPage">
<ListView x:Name="listview" ItemsSource="{Binding MyItems}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout BackgroundColor="#eee" Orientation="Vertical">
<StackLayout Orientation="Horizontal">
<input:CheckBox Text="{Binding Name}" Type="Check" IsChecked="{Binding IsCheck,Mode=TwoWay}" CheckChangedCommand="{Binding Source={x:Reference contentPage}, Path=BindingContext.CheckCommand}" CommandParameter="{Binding }"/>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
in your model
public class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string Name { get; set; }
private bool isCheck;
public bool IsCheck
{
get
{
return isCheck;
}
set
{
if (isCheck != value)
{
isCheck = value;
NotifyPropertyChanged();
}
}
}
}
in Viewmodel or Code behind
public ObservableCollection<Model> MyItems { get; set; }
public ICommand CheckCommand { get; private set; }
public YourViewModel()
{
MyItems = new ObservableCollection<Model>() {
new Model(){Name="xxx",IsCheck=true },
//...
};
CheckCommand = new Command((arg)=> {
var model = arg as Model;
for(int i=0;i<MyItems.Count;i++)
{
if (model == MyItems[i])
{
// i is the index that you checked
bool ischeck = MyItems[i].IsCheck;
// do some thing you want
}
}
});
}
I would suggest adding a Binding for the CheckBox State:
<controls:CheckBox x:Name="chechBox" DefaultText="{Binding Name}" IsChecked="{Binding IsChecked}" />
And then, in the ListView ItemTapped event:
void OnSelection (object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem == null) {
return; //ItemSelected is called on deselection, which results in SelectedItem being set to null
}
var item = (YourModel)e.SelectedItem;
if(item != null)
{
var checkBoxState = item.IsChecked;
}
}
In my Xamarin Forms app Home.xaml are not displaying the image back after saving in database. While debugging I could see, bytes[] are displaying at PlayerImage against the player. In the xaml, I have Source="{Binding PlayerImage}" but couldn't figure the reason for not displaying. Are the bytes displaying correct at the break point ?
// Home.xaml
<ContentPage.Resources>
<DataTemplate x:Key="playerTemplate">
<ContentView>
<StackLayout Margin="5,5" BackgroundColor="#584961">
<Image x:Name="{PlayerImage}" Source="{Binding PlayerImage}" WidthRequest="25" HeightRequest="25"/>
<Label Text="{Binding FullName}" Font="Bold,18" TextColor="White"/>
<Label Text="{Binding Mobile}" Font="Bold,13" TextColor="White"/>
<Label Text="{Binding SoccerPosition}" Font="Bold,13" TextColor="White"/>
<Button Text="Remove Player" Clicked="DeleteButton_OnClicked" WidthRequest="120" HeightRequest="50" TextColor="White" BackgroundColor="#d6b947"></Button>
</StackLayout>
</ContentView>
</DataTemplate>
</ContentPage.Resources>
<StackLayout Margin="5">
<CollectionView x:Name="collectionview"
ItemTemplate="{StaticResource playerTemplate}">
<!--span here decides the number of items shows in one line. Now is 3 items one line-->
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="3" />
</CollectionView.ItemsLayout>
</CollectionView>
</StackLayout>
// PlayerDetails.cs
public byte[] PlayerImage { get; set; }
//Home.xaml.cs
public void DisplayDetails()
{
List<PlayerDetails> details = (from x in conn.Table<PlayerDetails>() select x).ToList();
for (int i = 0; i < details.Count; i++)
{
players.Add(details[i]);
}
}
// Added my PlayerDetails.cs class also
public class PlayerDetails : INotifyPropertyChanged
{
[PrimaryKey, AutoIncrement]
public int id { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
public byte[] PlayerImage { get; set; }
string fullname;
string mobile;
string soccerposition;
string email;
public PlayerDetails()
{
}
[Ignore]
public Image Image
{
get
{
var image = new Image();
image.Source = ImageSource.FromStream(() => new MemoryStream(PlayerImage));
return image;
}
set
{
//PlayerImage = Convert.ToByteArray(value.Source);
//Bitmap.FromStream(inStream);
}
}
public string FullName
{
set
{
if (fullname != value)
{
fullname = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("FullName"));
}
}
}
get
{
return fullname;
}
}
public string Mobile
{
set
{
if (mobile != value)
{
mobile = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Mobile"));
}
}
}
get
{
return mobile;
}
}
public string SoccerPosition
{
set
{
if (soccerposition != value)
{
soccerposition = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SoccerPosition"));
}
}
}
get
{
return soccerposition;
}
}
public string Email
{
set
{
if (email != value)
{
email = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Email"));
}
}
}
get
{
return email;
}
}
//public ImageSource Source { get; internal set; }
public event PropertyChangedEventHandler PropertyChanged;
}
If you load an Image from a byte[] array in the Xamarin.Forms, you can try the following code:
c# code:
byte[] bitmapData = ...;
ImageSource imageSource= ImageSource.FromStream(() => new MemoryStream(bitmapData));
PlayerImage.Source = imageSource;//binding in code
The xaml code:
<Image x:Name="PlayerImage" WidthRequest="25" HeightRequest="25"/>
Or binding in xaml
<image Source="{Binding imageSource}"/>
Noteļ¼
I found the x:Name="{PlayerImage}" is not correct.
It's should be: x:Name="PlayerImage" not x:Name="{PlayerImage}"
you only need to use one way of the following binding methods:
PlayerImage.Source = imageSource;// in code
And
<Image x:Name="PlayerImage" Source="{Binding imageSource}" />
Update:
You can try to make use of a Converter derived from IValueConverter which could create the image back based on the byte array.
ByteArrayToImageSourceConverter.cs
public class ByteArrayToImageSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ImageSource retSource = null;
if (value != null)
{
byte[] imageAsBytes = (byte[])value;
var stream = new MemoryStream(imageAsBytes);
retSource = ImageSource.FromStream(() => stream);
}
return retSource;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
PlayerDetails.cs
public class PlayerDetails
{
// other fields
public byte[] PlayerImage { get; set; }
}
xaml(a usage example):
<ContentPage.Resources>
<ResourceDictionary>
<myformapp1:ByteArrayToImageSourceConverter x:Key="ByteArrayToImage"
/>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout Margin="5">
<CollectionView x:Name="collectionView"
ItemsSource="{Binding YoudataList}"> <!--changd to your dataList-->
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"
x:Name="PlayerPic"
Source="{Binding PlayerImage, Converter={StaticResource ByteArrayToImage}}"
Aspect="AspectFill"
HeightRequest="60"
WidthRequest="60" />
<Label Grid.Column="1"
Text="test1"
FontAttributes="Bold" />
<Label Grid.Row="1"
Grid.Column="1"
Text="test2"
FontAttributes="Italic"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
It seems that I can't access it even if I put a name on it because it's inside a listview. If i wan row1 to not be visible on code behind how do i go around this?
I tried to a name for Label but I can't access it. or added I cant access on code behind.
<ListView x:Name="postListView" SeparatorVisibility="Default" HasUnevenRows="True" ItemsSource="{Binding Items}" SeparatorColor="White">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid BackgroundColor="Black" HorizontalOptions="CenterAndExpand"
VerticalOptions="FillAndExpand" Padding="1,2,1,0">
<Grid HorizontalOptions="CenterAndExpand"
VerticalOptions="FillAndExpand" ColumnSpacing="1" RowSpacing="1">
<Grid.RowDefinitions >
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" FontSize="Medium" Grid.Column="0" Text="right tst:" HorizontalTextAlignment="Start" BackgroundColor="cornflowerblue" />
<Label Grid.Column="1" Grid.Row="0" Text="{Binding drain1vol}" HorizontalTextAlignment="Center" BackgroundColor="cornflowerblue"/>
<Label Grid.Row="1" Grid.Column="0" Text="nothing" BackgroundColor="Yellow"/>
<Label Grid.Row="1" Grid.Column="1" Text="{Binding drain2vol}" HorizontalTextAlignment="Center" BackgroundColor="Yellow" />
</Grid>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
//Model and Source of data
using System;
using SQLite;
using Demo.Helpers;
namespace Demo.Model
{
//this is the source of Binding
public class Post
{
//ID primary key that we will autoincrement
//These are binding source for Historypage
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public bool showLabel { get; set; } //public class model
}
}
The source is the Post class.
Binding the label's IsVisible property to the property in your model to control the visiable ability of the label.
For example,
In xaml:
<Label Grid.Column="1" Grid.Row="0" IsVisible="{Binding showLabel}" Text="{Binding drain1vol}" HorizontalTextAlignment="Center" BackgroundColor="cornflowerblue"/>
And then in your model:
public class model
{
public string drain1vol { get; set; }
public bool showLabel { get; set; }
}
When you create the dataSource, you can set the label's isVisable:
Items.Add(new model { drain1vol = "Rob Finnerty" ,showLabel= false });
Items.Add(new model { drain1vol = "Bill Wrestler", showLabel = true });
Items.Add(new model { drain1vol = "Dr. Geri-Beth Hooper", showLabel = false });
Items.Add(new model { drain1vol = "Dr. Keith Joyce-Purdy", showLabel = true });
Items.Add(new model { drain1vol = "Sheri Spruce", showLabel = false });
postListView.ItemsSource = Items;
To change the visiable ability in code behind:
void test() {
//Get the model you want to change
model.showLabel = false / true;
}
Update:
Implement the INotifyPropertyChanged interface in your model:
class model : INotifyPropertyChanged
{
private bool showLabel { get; set; }
private string drain1vol { get; set; }
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
public bool ShowLabel
{
set
{
if (showLabel != value)
{
showLabel = value;
OnPropertyChanged("ShowLabel");
}
}
get
{
return showLabel;
}
}
public string Drain1vol
{
set
{
if (drain1vol != value)
{
drain1vol = value;
OnPropertyChanged("Drain1vol");
}
}
get
{
return drain1vol;
}
}
}
And in your xaml, binding to ShowLabel and Drain1vol(upper-case):
<Label Grid.Column="1" Grid.Row="0" IsVisible="{Binding ShowLabel}" Text="{Binding Drain1vol}" HorizontalTextAlignment="Center" BackgroundColor="cornflowerblue"/>
All My data photo is in server Example url: http://abcd.com/images/ and i have renamed all the photos file to my album id example 1.jpg,2.jpg
and i want to display those photos using albumid in grid view
class AlbumData
{
public Int32 AlbumId { get; set; }
public String Name { get; set; }
public String Language { get; set; }
public String Actors { get; set; }
public String Director { get; set; }
public String MusicDirector { get; set; }
public String Year { get; set; }
}
private void Search_Click(object sender, RoutedEventArgs e)
{
WebClient webclient = new WebClient();
webclient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(webclient_DownloadStringCompleted);
webclient.DownloadStringAsync(new Uri("http://albums.abcd.com/v1/Albums/English/1"));//--getting data using xml
}
void webclient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show("error");
}
XElement XmlTweet = XElement.Parse(e.Result);
listBox1.ItemsSource = from tweet in XmlTweet.Descendants("Album")
select new AlbumData()
{
Name = tweet.Element("Name").Value,
//--how to display image---//
};
}
Please help me with example
<DataTemplate>
<StackPanel>
<Image Source="{Binding ImageSource}" Height="100" Width="100" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock Text="{Binding Name}" Foreground="#FFC8AB14" FontSize="15" Width="120" TextAlignment="Center" TextWrapping="Wrap" />
<!--<TextBlock Text="{Binding Message}" TextWrapping="Wrap" FontSize="8" Width="100" TextAlignment="Center" />-->
<!--<TextBlock Text="{Binding MusicDirector}" TextWrapping="Wrap" FontSize="8" Width="100" TextAlignment="Center" />-->
<!--<TextBlock Text="{Binding UserName}" Style="{StaticResource PhoneTextSubtleStyle}" Width="100" TextAlignment="Center"/>-->
</StackPanel>
</DataTemplate>
Assuming the ElementName of your images is ImageUri, you must change your ItemsSource to:
listBox1.ItemsSource =
from tweet in XmlTweet.Descendants("Album")
select new AlbumData()
{
Name = tweet.Element("Name").Value,
ImageSource = new BitmapImage(new Uri(tweet.Element("ImageUri"), UriKind.Relative));
};
AlbumData needs to be extended by
public BitmapImage ImageSource { get; set; }