Hi I have a Category class where I am downloading data from API and displaying in Pivot, now for index 0 of Pivot the data loads, I have added a Pivot selection changed event when user flips to other PivotItems and data get loaded. But the issue is the ItemsSource is not updating. If I do like this pivot.itemssource=""; and pivot.itemssource=mylist; I have to change the pivot.selectedIndex to current index also, in this case the flip is not smooth, it first goes to 0 index because of pivot.itemssource=""; and then come to actual place. Please help me getting a solution for this so that it binds and update automatically. My classes are below:
Category.xaml
<Grid x:Name="LayoutRoot" >
<controls:Pivot Margin="0,53,0,28" x:Name="CategoryList" Foreground="White" SelectionChanged="Pivot_SelectionChanged" ItemsSource="{Binding Constants.categoryDetails}" >
<controls:Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock x:Name="PivotTitle" Text="{Binding name}"></TextBlock>
</DataTemplate>
</controls:Pivot.HeaderTemplate>
<!--Panorama item one-->
<controls:Pivot.ItemTemplate>
<DataTemplate>
<Grid>
<ListBox x:Name="subCatList" ItemsSource="{Binding subcategories}" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="2" BorderBrush="White">
<Grid Width="420" Height="70" Background="#85000000">
<TextBlock Text="{Binding name}" FontFamily="Verdana" FontSize="35" Margin="10,10,64,5" TextWrapping="Wrap" ></TextBlock>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</DataTemplate>
</controls:Pivot.ItemTemplate>
</controls:Pivot>
</Grid>
<!--Panorama-based applications should not show an ApplicationBar-->
</phone:PhoneApplicationPage>
Category.cs
namespace MyApp.Views
{
public partial class Category : PhoneApplicationPage
{
List<WebClient> webclientsList = new List<WebClient>();
private int selectedIndex=0;
private ListBox subCatList;
public readonly DependencyProperty ListVerticalOffsetProperty;
public Category()
{
InitializeComponent();
for (int i = 0; i < Constants.catList.Length; i++)
{
WebClient wb = new WebClient();
CategoriesClass ct = new CategoriesClass();
ct.name = Constants.catList[i];
webclientsList.Add(wb);
Constants.categoryDetails.Add(ct);
}
loadCategory();
}
private void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
selectedIndex = CategoryList.SelectedIndex;
if (Constants.categoryDetails[selectedIndex].subcategories==null) {
loadCategory();
}
else{
}
}
private void loadCategory()
{
try
{
String Url = Constants.BASE_URL + "/get-category-data/?client_key=" + Constants.CLIENT_KEY;
webclientsList[selectedIndex].DownloadStringCompleted += new DownloadStringCompletedEventHandler(webClientCategoryDownload);
webclientsList[selectedIndex].DownloadStringAsync(new Uri(Url));
}
catch (Exception e)
{
Console.WriteLine("Error Occured"+e.StackTrace);
}
}
void webClientCategoryDownload(object sender, DownloadStringCompletedEventArgs e)
{
try
{
if (e.Error != null)
{
Console.WriteLine("Error geting category");
return;
}
CategoryClass ctClass = new CategoryClass();
ctClass = JsonConvert.DeserializeObject<CategoryClass>(e.Result);
if (ctClass.name!=null)
{
Console.WriteLine("Category Loaded");
Constants.categoryDetails[selectedIndex].categoryDetails = ctClass;
Constants.categoryDetails[selectedIndex].subcategories = new ObservableCollection<SubCategories>();
foreach (var its in ctClass.categories)
{
foreach(var chi in its.children){
Constants.categoryDetails[selectedIndex].subcategories.Add(chi);
}
}
CategoryList.ItemsSource = "";
CategoryList.SelectedIndex = selectedIndex;
CategoryList.ItemsSource = Constants.categoryDetails;
for(int i=0;i<Constants.categoryDetails.Count;i++){
foreach (var x2 in Constants.categoryDetails[selectedIndex].subcategories)
{
Console.WriteLine("SubCategory is :"+x2.name);
}
}
}
else {
Console.WriteLine("Categories Doesnt Exists ");
}
}
catch (Exception e1)
{
Console.WriteLine("Exception Occured:"+e1.StackTrace);
}
}
private void Facebook_login(object sender, RoutedEventArgs e)
{
NavigationService.Navigate(new Uri("/Views/Login.xaml", UriKind.Relative));
}
}
}
First of all try to set
pivot.itemssource=null;
And then
pivot.itemssource=myList;
But it's wrong way.
If you want to become a good Windows Phone developer, you must to know what is MVVM.
First of all, you must read and UNDERSTAND this article
Then try to understand how to use existing mvvm toolkit, for example MVVM Light or Caliburn.Micro.
You should have a good understanding of how to work with MVVM and then you can solve your problem yourself.
Hope it's help.
Also check this useful question.
And this,this, this and this
Below are the steps I followed for MVVM use in my project.
Install MVVM from silverlight
http://www.galasoft.ch/mvvm/installing/ Dont know the exact link but I think its available here.
Now restart your visual studio.
Go to Right click on your project and add new Items to your project.
Now add Model View Locator inside ViewModel folder. (It will be good practice to keep these file
into a new folder like ViewModel, View, Model)
Now you have created a viewlocator which handles all the process like cleaning for a viewmodel.
Now in the same folder create a modelview for your requirement edit like below, and also you are
free to add more parameters acc to your requirement.
using GalaSoft.MvvmLight;
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using MyProject.Model;
namespace MyProject.ViewModel
{
public class ProductViewModel : INotifyPropertyChanged
{
private ObservableCollection<ProductsClass> productData;
public ObservableCollection<ProductsClass> ProductData
{
get
{
return productData;
}
set {
productData = value;
RaisePropertyChanged("ProductData");
}
}
public ProductViewModel(ObservableCollection<ProductsClass> productData)
{
ProductData = productData;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
/// Initializes a new instance of the ProductViewModel class.
/// </summary>
public ProductViewModel()
{
////if (IsInDesignMode)
////{
//// // Code runs in Blend --> create design time data.
////}
////else
////{
//// // Code runs "for real": Connect to service, etc...
////}
}
////public override void Cleanup()
////{
//// // Clean own resources if needed
//// base.Cleanup();
////}
}
}
Now we have created our modelview, we have to mention something for this in our locator as
follows(Make the necessary changes only):
namespace Myproject.ViewModel
{
public class ViewModelLocator
{
private static MainViewModel _main;
/// <summary>
/// Initializes a new instance of the ViewModelLocator class.
/// </summary>
private static ProductViewModel _viewModelProduct;
public ViewModelLocator()
{
CreateMain();
CreateViewModelProduct();
}
/// <summary>
/// Gets the Main property.
/// </summary>
public static MainViewModel MainStatic
{
get
{
if (_main == null)
{
CreateMain();
}
return _main;
}
}
/// <summary>
/// Gets the Main property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public MainViewModel Main
{
get
{
return MainStatic;
}
}
/// <summary>
/// Provides a deterministic way to delete the Main property.
/// </summary>
public static void ClearMain()
{
_main.Cleanup();
_main = null;
}
/// <summary>
/// Provides a deterministic way to create the Main property.
/// </summary>
public static void CreateMain()
{
if (_main == null)
{
_main = new MainViewModel();
}
}
/// <summary>
/// Cleans up all the resources.
/// </summary>
public static void Cleanup()
{
ClearMain();
ClearViewModelProduct();
}
//Product model declaration
/// <summary>
/// Gets the ViewModelPropertyName property.
/// </summary>
public static ProductViewModel ProductViewModelStatic
{
get
{
if (_viewModelProduct == null)
{
CreateViewModelProduct();
}
return _viewModelProduct;
}
}
/// <summary>
/// Gets the ViewModelPropertyName property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public ProductViewModel ViewModelProduct
{
get
{
return ProductViewModelStatic;
}
}
/// <summary>
/// Provides a deterministic way to create the ViewModelPropertyName property.
/// </summary>
public static void CreateViewModelProduct()
{
if (_viewModelProduct == null)
{
_viewModelProduct = new ProductViewModel();
}
}
/// <summary>
/// Provides a deterministic way to delete the ViewModelPropertyName property.
/// </summary>
public static void ClearViewModelProduct()
{
//_main.Cleanup();
_viewModelProduct = null;
}
}
}
Now inside your VIew create a variable like
private ProductViewModel pvm;
inside constructor add this:
pvm = (ProductViewModel)Resources["pviewModel"];
Add this in your loaded method:
Binding binding = new Binding("ProductData") { Source = pvm };
Like
private void Product_Loaded(object sender, RoutedEventArgs e)
{
//Additional Code
pvm.ProductData = Constants.productDet;
//Additional Code
Binding binding = new Binding("ProductData") { Source = pvm };
}
Now after adding some more code in your xaml file whenever you update anything in .cs it will
automatically reflect in your view.
Make following in your xaml file:
DataContext="{Binding ViewModelProduct, Source={StaticResource Locator}}"
Loaded="Product_Loaded"> at the top declaration
<phone:PhoneApplicationPage.Resources>
<viewModel:ProductViewModel x:Key="pviewModel" />
</phone:PhoneApplicationPage.Resources>
Now the productsclass will be similar like this(its on u how u add parameters acc to ur requirement)
namespace Myproject.Model
{
public class ProductsClass : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
// NotifyPropertyChanged("Name");
}
}
}
private ProductClass productDetailsOverview;
public ProductClass ProductDetailsOverview
{
get { return productDetailsOverview; }
set
{
if (productDetailsOverview != value)
{
productDetailsOverview = value;
NotifyPropertyChanged("ProductDetailsOverview");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class ProductClass
{
public long product_start_offset { get; set; }
public ObservableCollection<FiltersData> filters_data { get; set; }
public long product_end_offset { get; set; }
public long product_count { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and you can use ProductDetailsOverview in the itemssource to access any parameter like
ItemsSource="{Binding ProductDetailsOverview.product_list}
Related
Can you point me into the right direction: How can I achieve in Xamarin.Forms a clear entry button with behavior.
The behavior would be: When tapping to clear icon the entry's content on the android and iOS side the content will be deleted.
By default the entry control does't have this.
The result would be:
After some research I managed to do this with effects.
The downsize is that you must do it on the Android project and on iOS project separately.
Like Jason suggested it can be done with custom renderers too. But still you must implement it on each Android/iOS project.
On the android project side you can do this by adding an effect that looks like this:
Note: Before sharing the code I must inform you that you must have an icon into the Resources/drawable with the name of ic_clear_icon.png.
/// <summary>
/// Adding a clear entry effect.
/// </summary>
public class ClearEntryEffect : PlatformEffect
{
/// <summary>
/// Attach the effect to the control.
/// </summary>
protected override void OnAttached()
{
ConfigureControl();
}
protected override void OnDetached()
{
}
private void ConfigureControl()
{
EditText editText = ((EditText)Control);
editText.AddTextChangedListener(new OnTextChangedListener(editText));
editText.FocusChange += EditText_FocusChange;
}
/// <summary>
/// If the entry looses focus, delete the x icon.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void EditText_FocusChange(object sender, Android.Views.View.FocusChangeEventArgs e)
{
var editText = (EditText)sender;
if (e.HasFocus == false)
editText.SetCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0);
}
}
/// <summary>
/// Adding an OnTextChangedListener to my entry.
/// </summary>
public class OnTextChangedListener : Java.Lang.Object, Android.Text.ITextWatcher
{
private EditText _editText;
public OnTextChangedListener(EditText editText)
{
_editText = editText;
}
public void AfterTextChanged(IEditable s)
{
}
public void BeforeTextChanged(ICharSequence s, int start, int count, int after)
{
}
public void OnTextChanged(ICharSequence s, int start, int before, int count)
{
if (count != 0)
{
_editText.SetCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, Resource.Drawable.ic_clear_icon, 0);
_editText.SetOnTouchListener(new OnDrawableTouchListener());
}
else
_editText.SetCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0);
}
}
/// <summary>
/// Adding a Touch listener so it can be clicked in order to remove the text.
/// </summary>
public class OnDrawableTouchListener : Java.Lang.Object, Android.Views.View.IOnTouchListener
{
public bool OnTouch(Android.Views.View v, MotionEvent e)
{
if (v is EditText && e.Action == MotionEventActions.Up)
{
EditText editText = (EditText)v;
if (editText.Text != null)
editText.SetCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, Resource.Drawable.ic_clear_icon, 0);
if (editText.GetCompoundDrawables()[2] != null)
{
//If the region on which i tapped is the region with the X the text will be cleaned
if (e.RawX >= (editText.Right - editText.GetCompoundDrawables()[2].Bounds.Width()))
{
editText.Text = string.Empty;
return true;
}
}
}
return false;
}
}
On the iOS project side is more simple. Because it has it natively:
public class ClearEntryEffect : PlatformEffect
{
protected override void OnAttached()
{
ConfigureControl();
}
protected override void OnDetached()
{
}
private void ConfigureControl()
{
((UITextField)Control).ClearButtonMode = UITextFieldViewMode.WhileEditing;
}
}
Now you create an effect class on the PCL project in witch you reference the two ClearEntryEffect classes(from Android/iOS projects).
This effect class is needed so can it be refferenced from the XAML file where you declare your Entry.
public class ClearEntryEffect : RoutingEffect
{
public ClearEntryEffect() : base("Effects.ClearEntryEffect")
{
}
}
Now you just refference it on the shared forms project(PCL in my case) into the xaml:
1) Refferencing the namespace where the effect is located:
xmlns:effects="clr-namespace:YourNamespace.Common.Effects"
2) Adding the effect to the entry:
<Entry x:Name="OrderNo"
Text="{Binding OrderNo, Mode=TwoWay}"
<Entry.Effects>
<effects:ClearEntryEffect/>
</Entry.Effects>
</Entry>
This works on both Android and iOS.
Here is my xaml.
<Grid>
<Entry x:Name="search" TextChanged="SearchChanged" Placeholder="Search"/>
<Image x:Name="clearSearch" Source="delete.png" HeightRequest="16" WidthRequest="16" HorizontalOptions="End">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="OnSearchTap" NumberOfTapsRequired="1" />
</Image.GestureRecognizers>
</Image>
</Grid>
Here is my c#
private void OnSearchTap(object sender, EventArgs args)
{
search.Text = "";
}
The ClearButtonVisibility property has been added in a new Xamarin.Forms, so there is no need for custom renderers right now. For example:
<Entry Text="Xamarin.Forms"
ClearButtonVisibility="WhileEditing" />
See Display a clear button in Xamarin.Forms documentation.
I think u need renderer for this and for example in android platform set android:drawableRight. On iOS platform set RightView property of UITextview.
Another option is wrap your Entry in Grid with Image.
<Grid>
<Entry></Entry>
<Image Source="your image"
HeightRequest="24" // some height
WidthRequest="24" //some width
HorizontalOptions="End"
.... some margins>
</Image>
</Grid>
I'm doing a login view. Problem is that the PasswordBox cant be binded to the view model so im mapping a property of the view to the viewmodel.
This is the View
public partial class LoginView : MetroDataWindow
{
/// <summary>
/// Initializes a new instance of the <see cref="LoginView"/> class.
/// </summary>
public LoginView()
: this(null) { }
/// <summary>
/// Initializes a new instance of the <see cref="LoginView"/> class.
/// </summary>
/// <param name="viewModel">The view model to inject.</param>
/// <remarks>
/// This constructor can be used to use view-model injection.
/// </remarks>
public LoginView(LoginViewModel viewModel)
: base(viewModel)
{
InitializeComponent();
}
[ViewToViewModel(MappingType = ViewToViewModelMappingType.ViewToViewModel)]
public SecureString Contrasena
{
get { return (SecureString)GetValue(ContrasenaPropiedad); }
set { SetValue(ContrasenaPropiedad, value); }
}
// Using a DependencyProperty as the backing store for MapCenter. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ContrasenaPropiedad = DependencyProperty.Register("Contrasena", typeof(SecureString),
typeof(LoginView), new PropertyMetadata(null, (sender, e) => ((LoginView)sender).UpdateContrasena()));
private void UpdateContrasena()
{
MessageBox.Show("VIEW: FirstName changed to " + ContrasenaPropiedad.ToString());
}
private void tbPassword_PasswordChanged(object sender, RoutedEventArgs e)
{
Contrasena = tbPassword.SecurePassword;
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == ContrasenaPropiedad)
{
int i = 0;
}
}
}
this is the viewmodel part with the property
public static readonly PropertyData ContrasenaPropiedad = RegisterProperty("Contrasena", typeof(SecureString), null, (sender, e) => ((LoginViewModel)sender).OnContrasenaChange());
public void OnContrasenaChange()
{
_messageService.Show("VIEW MODEL: FirstName changed to " + Contrasena.ToString());
}
public SecureString Contrasena
{
get
{
return GetValue<SecureString >(ContrasenaPropiedad);
}
set
{
SetValue(ContrasenaPropiedad, value);
}
}
the onChange function of the viewmodel never triggers.
I based this code on the example given in the last comment in this question
Catel ViewToViewModel attribute
But it does not work. Am I missing something or was the bug commented there never fixed?
Also since the view is the only one changing the property should i use the ViewToViewModelMappingType.ViewToViewModel type instead? does it change the implementation of the mapping in any way?
Passwords are a special kind of breed. But Catel has a solution for that problem, the UpdateBindingOnPasswordChanged behavior:
<PasswordBox>
<i:Interaction.Behaviors>
<catel:UpdateBindingOnPasswordChanged Password="{Binding Password, Mode=TwoWay}" />
</i:Interaction.Behaviors>
</PasswordBox>
ps. are you aware of Catel.Fody? It makes your code more readable and easier to write.
Hi i'm new using MVVM and i'm trying to binding a listbox but it doesn't work. Here's my code
Model
public class Musicmodel : INotifyPropertyChanged
{
//variables privadas
private String _artista;
private Uri _href;
private String _informacion;
private Double _Dvalue;
public String artista
{
get
{
return this._artista;
}
set
{
this._artista= value;
this.RaisePropertyChanged("artista");
}
}
public Uri href {
get {
return this._href;
}
set
{
this._href = value;
this.RaisePropertyChanged("href");
}
}
public String informacion {
get
{
return this._informacion;
}
set
{
this._informacion = value;
this.RaisePropertyChanged("informacion");
}
}
public Double Dvalue
{
get
{
return this._Dvalue;
}
set
{
this._Dvalue = value;
this.RaisePropertyChanged("Dvalue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
ViewModel
public class DownloadFileViewModel : INotifyPropertyChanged
{
private WebClient clienteDownload;
private ObservableCollection<Model.Music>_musicSource= new ObservableCollection<Model.Music>();
public ObservableCollection<Model.Music> musicSource
{
get
{
return this._musicSource;
}
set
{
this._musicSource = value;
RaisePropertyChanged("musicSource");
}
}
private int index = 0;
//request para descargar la canciĆ³n
public void request(Model.Musicmodel item)
{
this.clienteDownload = new WebClient();
this.clienteDownload.DownloadProgressChanged += new DownloadProgressChangedEventHandler(clienteDownload_DownloadProgressChanged);
//agregamos el item al music
this.musicSource.Add(item);
this.clienteDownload.OpenReadAsync(this.musicSource[index].href);
}
private void clienteDownload_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
this.musicSource[index].Dvalue=(double)e.ProgressPercentage;
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
View
<ListBox x:Name="list" ItemsSource="{Binding musicSource}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding artista}"/>
<ProgressBar Value="{Binding Dvalue}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code Behind
protected override void OnNavigatedTo(NavigationEventArgs e)
{
DownloadFileViewModel download = new DownloadFileViewModel();
Model.Music newMusic = new Model.Music() { href = new Uri("http://media1.li.ru/b/4/mp3/2/95366/953662_14Friday_Im_In_Love.mp3", UriKind.Absolute), artista = "the cure" };
download.request(newMusic);
this.DataContext = download;
base.OnNavigatedTo(e);
}
I've debuged this and the download works fine and my ObservableCollection fills correctly whithout any problem but when i try to binding my listbox fails.
please what do i'm doing wrong?
thanks
The problem is quite simple. You initialize your musicSource property at the begining in
private ObservableCollection<Model.Music>_musicSource= new ObservableCollection<Model.Music>();
And then just add stuff to it after the request completes. The RaiseProperyChanged("Property") will only fire when you add a new observable collection but not when you add items to it.
Add this line again to the end of the request (when you populate your musicSource)
RaisePropertyChanged("musicSource");
This will trigger another update in the view
EDIT:
Another approach is to have an additional field like
private ObservableCollection<Model.Music>_anotherMusicSource= new ObservableCollection<Model.Music>();
And do everything on it and after that just say:
musicSource = _anotherMusicSource;
This will then trigger the notification and everything should work
You have an underscore in your property name
private ObservableCollection<Model.Musicmodel> musicSource= new ObservableCollection<Model.Musicmodel>();
public ObservableCollection<Model.Musicmodel> _musicSource
{
get
{
return this.musicSource;
}
set
{
this.musicSource = value;
RaisePropertyChanged("musicSource");
}
}
You have this mixed up - the underscore should (traditionally) be on the private member, not the public - your binding is targeting musicSource which is private
The standard convention which is advocated for .NET is:
// Private member variables
private int _someInteger;
// Public ones
public int SomeInteger { get { ... } set { ... } }
I have created custom User Control which contain TextBox and PasswordBox. I bind TextBox to UserName and PassowrdBox also.
The UserName is defined in my LoginViewModel class with [Required] attribute. Now my cursor is leaving from TextBox without entering any value then UserName property fire property changeed notifcation (INotifyPropertyChanged),
but dose not mark my Textbox (which is inside the User Control) with Red border.
Following is code of my User Control.
RestrictedBox.xaml
<Grid x:Name="LayoutRoot" Background="Transparent" Margin="0" >
<TextBox x:Name="txtTextBox" HorizontalAlignment="Stretch" Height="25" />
<PasswordBox x:Name="txtPasswordBox" HorizontalAlignment="Stretch" Height="25" />
</Grid>
RestrictedBox.xaml.cs
public partial class RestrictedBox : UserControl
{
#region Properties
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(string), typeof(RestrictedBox), new PropertyMetadata("", ValueChanged));
private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public bool Updateable
{
get { return (bool)GetValue(UpdateableProperty); }
set { SetValue(UpdateableProperty, value); }
}
public static readonly DependencyProperty UpdateableProperty = DependencyProperty.Register("Updateable", typeof(bool), typeof(RestrictedBox), new PropertyMetadata(UpdateableChanged));
private static void UpdateableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public bool Redactable
{
get { return (bool)GetValue(RedactableProperty); }
set { SetValue(RedactableProperty, value); }
}
public static readonly DependencyProperty RedactableProperty = DependencyProperty.Register("Redactable", typeof(bool), typeof(RestrictedBox), new PropertyMetadata(RedactableChanged));
private static void RedactableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
#endregion
#region Constructors
public RestrictedBox()
{
InitializeComponent();
txtTextBox.SetBinding(TextBox.TextProperty, new Binding { Source = this, Path = new PropertyPath("Value"), Mode = BindingMode.TwoWay});
txtTextBox.SetBinding(TextBox.VisibilityProperty, new Binding("Redactable") { Source = this, Converter = new BoolToVisibilityConverterReverse() });
txtPasswordBox.SetBinding(PasswordBox.PasswordProperty, new Binding { Source = this, Path = new PropertyPath("Value"), Mode = BindingMode.TwoWay });
txtPasswordBox.SetBinding(TextBox.VisibilityProperty, new Binding("Redactable") { Source = this, Converter = new BoolToVisibilityConverter() });
}
#endregion
}
Following is the code where i used my Custom User Control
LoginView.xaml
<Control:RestrictedBox x:Name="UserName" VerticalAlignment="Top" TabIndex="2" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" Height="40" Value="{Binding Path=LoginModelValue.UserName, Mode=TwoWay, ValidatesOnNotifyDataErrors=True, ValidatesOnExceptions=True,
ValidatesOnDataErrors=True, NotifyOnValidationError=True}" Validatevalue:UpdateSourceTriggerHelper.UpdateSourceTrigger="True" Redactable="True" Updateable="True" />
LoginView.xaml.cs
[Export(typeof(LoginView))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class LoginView : UserControl, IPageTitle
{
#region Constuctors
public LoginView()
{
InitializeComponent();
}
[Import]
public LoginViewModel ViewModel
{
get {return this.DataContext as LoginViewModel;}
set { DataContext = value; }
}
#endregion
}
LoginViewModel.cs
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class LoginViewModel : INotifyPropertyChanged, IRegionMemberLifetime
{
private LoginModel _LoginModelValue;
public LoginModel LoginModelValue
{
get { return _LoginModelValue; }
set
{
_LoginModelValue = value;
OnPropertyChanged("LoginModelValue");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
void LoginModelValue_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (LoginModelValue.IsValidObject())
{
LoginCommand.RaiseCanExecuteChanged();
IsEnabled = LoginModelValue.IsValidObject();
SetIncorrectLogin(!IsEnabled);
}
}
#endregion
}
Can anybody has idea why i am not getting Red Border surrounded with my TextBox which is inside my Custom User Control?
Any help, suggestions and comments would be highly appreciated!
Thanks,
Imdadhusen
As I've already said, the validation works only for one binding and is not inherited by consequent bindigns as in your case.
The easiest way would be to add the Required annotation directly to the Value property of your control and validate it once again:
[Required]
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(string), typeof(RestrictedBox), new PropertyMetadata("", ValueChanged));
private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var rb = d as RestrictedBox;
Validator.ValidateProperty(rb.Value, new ValidationContext(rb, null, null) { MemberName = "Value" });
}
And add the ValidatesOnExceptions attribute to your bindings so that validation works:
txtTextBox.SetBinding(TextBox.TextProperty, new Binding { Source = this, Path = new PropertyPath("Value"), Mode = BindingMode.TwoWay,
ValidatesOnExceptions = true });
//...
txtPasswordBox.SetBinding(PasswordBox.PasswordProperty, new Binding { Source = this, Path = new PropertyPath("Value"), Mode = BindingMode.TwoWay,
ValidatesOnExceptions = true });
//...
Another way: to remove all the properties and bind the RestrictedBox control directly to your view model.
<TextBox x:Name="txtTextBox" HorizontalAlignment="Stretch" Height="25"
Text="{Binding LoginModelValue.UserName, Mode=TwoWay, ValidatesOnExceptions=True}" />
<PasswordBox x:Name="txtPasswordBox" HorizontalAlignment="Stretch" Height="25"
Password="{Binding LoginModelValue.UserName, Mode=TwoWay, ValidatesOnExceptions=True}" />
These solutions seem far from ideal, but actually the validation by data annotations is not good by design. I would recommend to use the INotifyDataErrorInfo interface.
Now i resolved issue using following code. I have replaced following line
txtTextBox.SetBinding(TextBox.VisibilityProperty, new Binding("Redactable") { Source = this, Converter = new BoolToVisibilityConverterReverse() });
with
this.MapBinding(RestrictedControl.ValueProperty, txtTextBox, TextBox.TextProperty);
and added following code. that's it.
namespace QSys.Library.Helpers
{
public static class FrameworkElementExtension
{
public static void MapBinding(this FrameworkElement element, DependencyProperty firstProperty, FrameworkElement targetElement, DependencyProperty secondProperty)
{
BindingExpression firstExpression = element.GetBindingExpression(firstProperty);
if (firstExpression != null && firstExpression.ParentBinding != null)
{
targetElement.SetBinding(secondProperty, firstExpression.ParentBinding);
}
}
}
}
I specially thanks to everybody how was looking for this. and i am also very thanksful Rakesh Gunijan (http://www.codeproject.com/Articles/293302/Silverlight-user-control-validation) how expain in very much clear.
Thanks,
Imdadhusen
Does anyone have sample code on how to create a Region Adapter for AvalonDock's DocumentPane and DockingPane?
The Markus Raufer has added two region adapters to the CompositeWpfContrib project at CodePlex that supports both DocumentPane and DockingPane.
I have used Raffaeu Bermuda snippets to support Avalon tab region adapter, but found that there is some issues are not solved:
1- It does not support Activating a a certain view (aka - tab - DockableContent), so the code Region.Activate(object view) will not work.
2- All the Tabs are active by default in the region. So Region.ActiveViews collection by default has all the views, this is not ideal, as sometimes I needed to verify if a view is active or not (you could imagine a save button on a tool bar region that executes a SaveCommand only on the current active view = tab in our case)
3- Closed views doesn't actually get closed, only hidden. Even if you set the HideOnClose = true when adding the newDockableContent, it is still not removed from Region.Views collection. This could lead to memory leaks issues.
4- If you have a previously added DockableContent in the Pane, they will not get synchronized and added to the Region.Views collection.
So here are the code I am using now, it is just a small tweak from the Selector Adapter and Selector Sync Behavior found in PRISM source code:
AvalonRegionAdapter Class:
public class AvalonRegionAdapter : RegionAdapterBase<DocumentPane>
{
public AvalonRegionAdapter(IRegionBehaviorFactory factory) : base(factory) {}
protected override void AttachBehaviors(IRegion region, DocumentPane regionTarget)
{
if (region == null) throw new System.ArgumentNullException("region");
//Add the behavior that syncs the items source items with the rest of the items
region.Behaviors.Add(AvalonDocumentSyncBehavior.BehaviorKey,
new AvalonDocumentSyncBehavior()
{
HostControl = regionTarget
});
base.AttachBehaviors(region, regionTarget);
}
protected override void Adapt(IRegion region, DocumentPane regionTarget){ }
protected override IRegion CreateRegion()
{
return new Region();
}
}
AvalonDocumentSyncBehavior Behavior Code:
public class AvalonDocumentSyncBehavior : RegionBehavior, IHostAwareRegionBehavior
{
/// <summary>
/// Name that identifies the SelectorItemsSourceSyncBehavior behavior in a collection of RegionsBehaviors.
/// </summary>
public static readonly string BehaviorKey = "AvalonDocumentSyncBehavior";
private bool updatingActiveViewsInHostControlSelectionChanged;
private Selector hostControl;
/// <summary>
/// Gets or sets the <see cref="DependencyObject"/> that the <see cref="IRegion"/> is attached to.
/// </summary>
/// <value>
/// A <see cref="DependencyObject"/> that the <see cref="IRegion"/> is attached to.
/// </value>
/// <remarks>For this behavior, the host control must always be a <see cref="Selector"/> or an inherited class.</remarks>
public DependencyObject HostControl
{
get
{
return this.hostControl;
}
set
{
this.hostControl = value as Selector;
}
}
/// <summary>
/// Starts to monitor the <see cref="IRegion"/> to keep it in synch with the items of the <see cref="HostControl"/>.
/// </summary>
protected override void OnAttach()
{
bool itemsSourceIsSet = this.hostControl.ItemsSource != null;
if (itemsSourceIsSet)
{
//throw new InvalidOperationException(Resources.ItemsControlHasItemsSourceException);
}
this.SynchronizeItems();
this.hostControl.SelectionChanged += this.HostControlSelectionChanged;
this.Region.ActiveViews.CollectionChanged += this.ActiveViews_CollectionChanged;
this.Region.Views.CollectionChanged += this.Views_CollectionChanged;
}
private void Views_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
int startIndex = e.NewStartingIndex;
foreach (object newItem in e.NewItems)
{
UIElement view = newItem as UIElement;
TabViewModel viewModel = ((UserControl)view).DataContext as TabViewModel;
if (view != null)
{
DockableContent newDockableContent = new DockableContent();
newDockableContent.Content = newItem;
//if associated view has metadata then apply it.
newDockableContent.Title = view.GetType().ToString();
if (viewModel != null)
{
//Image img = new Image();
//img.Source = new BitmapImage(new Uri(#"Resources/Alerts.png", UriKind.Relative));
newDockableContent.Title = viewModel.TabModel.Title;
newDockableContent.IsCloseable = viewModel.TabModel.CanClose;
//newContentPane.Icon = img.Source;
}
//When contentPane is closed remove from the associated region
newDockableContent.Closed += new EventHandler(newDockableContent_Closed);
newDockableContent.HideOnClose = false;
this.hostControl.Items.Add(newDockableContent);
newDockableContent.Activate();
}
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (object oldItem in e.OldItems)
{
this.hostControl.Items.Remove(oldItem);
}
}
}
void newDockableContent_Closed(object sender, EventArgs e)
{
var dockableContent = sender as DockableContent;
if(dockableContent != null)
if (this.Region.Views.Contains(dockableContent.Content))
{
this.Region.Remove(dockableContent.Content);
}
}
private void SynchronizeItems()
{
List<object> existingItems = new List<object>();
// Control must be empty before "Binding" to a region
foreach (object childItem in this.hostControl.Items)
{
existingItems.Add(childItem);
}
foreach (object view in this.Region.Views)
{
this.hostControl.Items.Add(view);
}
foreach (object existingItem in existingItems)
{
this.Region.Add(existingItem);
}
}
private void ActiveViews_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (this.updatingActiveViewsInHostControlSelectionChanged)
{
// If we are updating the ActiveViews collection in the HostControlSelectionChanged, that
// means the user has set the SelectedItem or SelectedItems himself and we don't need to do that here now
return;
}
if (e.Action == NotifyCollectionChangedAction.Add)
{
var selectedDockableContent = this.hostControl.SelectedItem as DockableContent;
if (selectedDockableContent != null
&& selectedDockableContent.Content != null
&& selectedDockableContent.Content != e.NewItems[0]
&& this.Region.ActiveViews.Contains(selectedDockableContent.Content))
{
this.Region.Deactivate(selectedDockableContent.Content);
}
var _UIElement = e.NewItems[0] as FrameworkElement;
this.hostControl.SelectedItem = _UIElement.Parent;
}
else if (e.Action == NotifyCollectionChangedAction.Remove &&
e.OldItems.Contains(this.hostControl.SelectedItem))
{
this.hostControl.SelectedItem = null;
}
}
private void HostControlSelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
// Record the fact that we are now updating active views in the HostControlSelectionChanged method.
// This is needed to prevent the ActiveViews_CollectionChanged() method from firing.
this.updatingActiveViewsInHostControlSelectionChanged = true;
object source;
source = e.OriginalSource;
if (source == sender)
{
foreach (object item in e.RemovedItems)
{
// check if the view is in both Views and ActiveViews collections (there may be out of sync)
var dockableContent = item as DockableContent;
if (this.Region.Views.Contains(dockableContent.Content) && this.Region.ActiveViews.Contains(dockableContent.Content))
{
this.Region.Deactivate(dockableContent.Content);
}
}
foreach (object item in e.AddedItems)
{
var dockableContent = item as DockableContent;
if (this.Region.Views.Contains(dockableContent.Content) &&
!this.Region.ActiveViews.Contains(dockableContent.Content))
{
this.Region.Activate(dockableContent.Content);
}
}
}
}
finally
{
this.updatingActiveViewsInHostControlSelectionChanged = false;
}
}
}
Code on bootstrapper to configure the Adapter
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
var mappings = base.ConfigureRegionAdapterMappings();
mappings.RegisterMapping(typeof(AvalonDock.DocumentPane),
this.Container.Resolve<AvalonRegionAdapter>());
return mappings;
}
Then you need the TabModel and the TabViewModel as fromRaffaeu Bermuda
public sealed class TabModel : DependencyObject
{
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
// Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(TabModel));
public bool CanClose
{
get { return (bool)GetValue(CanCloseProperty); }
set { SetValue(CanCloseProperty, value); }
}
// Using a DependencyProperty as the backing store for CanClose. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CanCloseProperty =
DependencyProperty.Register("CanClose", typeof(bool), typeof(TabModel));
public bool IsModified
{
get { return (bool)GetValue(IsModifiedProperty); }
set { SetValue(IsModifiedProperty, value); }
}
// Using a DependencyProperty as the backing store for IsModified. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsModifiedProperty =
DependencyProperty.Register("IsModified", typeof(bool), typeof(TabModel));
}
And a TabViewModel acting as a base class:
public class TabViewModel : INotifyPropertyChanged
{
private TabModel _tabModel;
public TabModel TabModel
{
get { return this._tabModel; }
set
{
this._tabModel = value;
OnPropertyChanged("TabModel");
}
}
public TabViewModel()
{
this.TabModel = new TabModel();
this.TabModel.CanClose = true;
this.TabModel.IsModified = false;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Let me know if you need further help, I will post a blog this in the near future.
Since the Avalon DocumentPane and DockingPane are both based on the System.Windows.Controls.Primitives.Selector you can use the default SelectorRegionAdapter in Prism.
Just base your control on DockableContent
<ad:DockableContent x:Class="DesignerWPF.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:ad="clr-namespace:AvalonDock;assembly=AvalonDock"
d:DesignHeight="300" d:DesignWidth="300" Title="dans">
<Grid>
<TextBox Text="sdfdf"></TextBox>
</Grid>
</ad:DockableContent>
on your main Shell.xmal set the regions in the dockablepane
<ad:DockingManager x:Name="dockManager" Grid.Row="1" Margin="0,4,0,0">
<ad:ResizingPanel Orientation="Horizontal">
<ad:DockablePane cal:RegionManager.RegionName="LeftRegion">
</ad:DockablePane>
<ad:DocumentPane cal:RegionManager.RegionName="DocumentRegion">
</ad:DocumentPane>
</ad:ResizingPanel>
</ad:DockingManager>
then when you initialize your presenter for your control it will get displayed in the dock.
public class UserTestControl : IModule
{
public UserTestControl(IUnityContainer container, IRegionManager regionManager)
{
Container = container;
RegionManager = regionManager;
}
public void Initialize()
{
var addFundView = Container.Resolve<UserControl1>();
RegionManager.Regions["LeftRegion"].Add(addFundView);
}
public IUnityContainer Container { get; private set; }
public IRegionManager RegionManager { get; private set; }
}
My advice would be to look in Microsoft.Practices.Composite.Presentation.Regions in the Prism source. Specifically, take a look at the ItemsControlRegionAdapter and use it as a template. Remember to inherit from RegionAdapterBase<>:
public class ItemsControlRegionAdapter : RegionAdapterBase<ItemsControl>
and to override ConfigureRegionAdapterMappings() in the bootstrapper. That would look something like:
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
RegionAdapterMappings mappings = base.ConfigureRegionAdapterMappings();
mappings.RegisterMapping(typeof(Canvas), Container.Resolve<CanvasRegionAdapter>());
return mappings;
}