This is such a basic question, but I have to ask.
In SL, I have this XAML:
<UserControl.Resources>
<local:Commands x:Key="MyCommands" />
</UserControl.Resources>
<Button Content="Click Me"
Command="{Binding Path=Click, Source={StaticResource MyCommands}}"
CommandParameter="Hello World" />
And this code behind:
public class Commands
{
public ClickCommand Click = new ClickCommand();
public sealed class ClickCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
MessageBox.Show(parameter.ToString());
}
}
}
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
}
But when I click the button, the Command's Execute() is never fired.
Is there a trick to it?
There's no trick, your problem lies in the binding between your XAML and C# class. You can't bind to a field only to a property.
public class Commands
{
public ClickCommand Click { get; set; }
public Commands()
{
Click = new ClickCommand();
}
public sealed class ClickCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
MessageBox.Show(parameter.ToString());
}
}
}
Related
I need your help. My project is currently based on two windows, one login and the other for other things. All commands for buttons, login, reset are made in a class "LoginViewModel" using MVVM.
How can I close the first window and run the second without problems?
LoginViewModel.CS
public class LoginViewModel : ViewModelBase
{
#region Login Credentials
public string Username { get; set; }
public string Password { get; set; }
#endregion
#region Commands
public ICommand LoginCommand { get; }
public ICommand ResetPasswordCommand { get; }
#endregion
public LoginViewModel()
{
LoginCommand = new RelayCommand(_ => Login());
ResetPasswordCommand = new RelayCommand(_ => Reset());
}
#region Private Methods
private void Login()
{
if (Username == "student" && Password == "password")
{
AppMainWindow nw = new AppMainWindow();
nw.Show();
if(nw.IsActive)
{
MainWindow cl = new MainWindow();
cl.Close();
}
} else {
MessageBox.Show("Username or password is incorect!");
}
}
private void Reset()
{
throw new NotImplementedException();
}
#endregion
}
}
First window is: MainWindow
Another Window is: AppMainWindow
I'm trying to make an expandable listview item when i press the name of an item .. the problem is that the value of IsVisiable change but that don't reflect on the page and it's stays the same (only the name of the item is shown not all the hidden details) .
First I added a IsVisiable prop to my model
public class Item
{
public int Id { get; set; }
public string UserId { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public string Quality { get; set; }
public int Size { get; set; }
public decimal Price { get; set; }
public bool IsVisiable { get; set; }
}
This is the content page
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModels="clr-namespace:XamarinApp.ViewModels;assembly=XamarinApp"
x:Class="XamarinApp.ViewModels.Views.CustomerProfilePage">
<ContentPage.BindingContext>
<viewModels:CustomerProfileViewModel/>
</ContentPage.BindingContext>
<ListView ItemsSource="{Binding Items}"
HasUnevenRows="True"
ItemTapped="ListView_OnItemTapped">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}" ></Label>
<StackLayout IsVisible="{Binding IsVisiable}">
<Label Text="{Binding Category}" ></Label>
<Label Text="{Binding Quality}" ></Label>
<Label Text="{Binding Size}" ></Label>
<Label Text="{Binding Price} "></Label>
</StackLayout>
<!-- <Label Text="المسافة بينك وبين العميل"></Label> -->
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I create a onItemTapped method
public partial class CustomerProfilePage : ContentPage
{
public CustomerProfilePage (string userId)
{
InitializeComponent ();
this.BindingContext = new CustomerProfileViewModel(userId);
}
private void ListView_OnItemTapped(object sender, ItemTappedEventArgs e)
{
var vm = BindingContext as CustomerProfileViewModel;
var Item = e.Item as Item;
vm?.HideOrShowItem(Item);
}
}
Then i added HideOrShow item method for control in my vm
public class CustomerProfileViewModel:INotifyPropertyChanged
{
public CustomerProfileViewModel()
{
}
public CustomerProfileViewModel(string cutomerId)
{
CustomerId = cutomerId;
if (GetItems.CanExecute(null))
GetItems.Execute(null);
}
public List<Item> Items
{
get => _items;
set
{
if (Equals(value, _items)) return;
_items = value;
OnPropertyChanged();
}
}
public string CustomerId { get;}
private List<Item> _items;
private Item _oldItem;
ApiServices _apiServices = new ApiServices();
public void HideOrShowItem(Item item)
{
if (_oldItem == item)
{
item.IsVisiable = !item.IsVisiable;
UpdateItems(item);
}
else
{
if (_oldItem != null)
{
_oldItem.IsVisiable = false;
UpdateItems(_oldItem);
}
item.IsVisiable = true;
UpdateItems(item);
}
_oldItem = item;
}
private void UpdateItems(Item item)
{
var index = Items.IndexOf(item);
Items.Remove(item);
Items.Insert(index, item);
}
public ICommand GetItems
{
get
{
return new Command(async () =>
{
var accesstoken = Settings.AccessToken;
Items = await _apiServices.GetItemsForSpecificUser(accesstoken, CustomerId);
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The problems I can see:
Your class Item must implement INotifyPropertyChanged interface
I also see that you can update your list Items. So to reflect the list changes in your UI, Items must be an ObservableCollection<Item>
Updated Item class (should look something like that):
public class Item : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _id;
private bool _isVisible;
// . . .
public int Id
{
get => _id;
set
{
if (_id != value)
{
_id = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Id)));
}
}
}
public bool IsVisible
{
get => _isVisible;
set
{
if (_isVisible != value)
{
_isVisible = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.IsVisible)));
}
}
}
// . . . Other properties
And in your ViewModel, declare the list of items:
private ObservableCollection<Item> _items;
public ObservableCollection<Item> Items{
get => _items;
private set{
_items = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Items)));
}
}
Try with this and tell us if it's ok...
In my XamarinForms project I am trying to bind a label text to a property of a class. when I pass in the object to my view the label is not being populated. can someone see what I am doing wrong?
In my view model I have
class ManagerLevelPageViewModel : INotifyPropertyChanged
{
private UserSelections _MyUserSelections;
public UserSelections MyUserSelections
{
get { return _MyUserSelections; }
set {
_MyUserSelections = value;
NotifyPropertyChanged();
}
}
public ManagerLevelPageViewModel(UserSelections _temp)
{
MyUserSelections = _temp;
MyUserSelections.selectedClientName = _temp.selectedClientName;
//myUserSelections = _myUserSelections;
//SetValues();
}
here is the class
public class UserSelections
{
public int selectedClientId { get; set; }
public string selectedClientName { get; set; }
public string selectedClientShortCode { get; set; }
public decimal selectedClientPL { get; set; }
public string TopdayIdentifier { get; set; }
}
here is the view.cs
ManagerLevelPageViewModel vm;
public ManagerLevelPage (UserSelections _myUserSelections)
{
vm = new ManagerLevelPageViewModel(_myUserSelections);
InitializeComponent ();
BindingContext = vm;
DownloadData();
}
lastly here is the xaml
<Label Text="{Binding MyUserSelections.ClientName}"/>
notify property changed
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I am making a UWP app and I have bound an ObservableCollection<EduRole> to a ComboBox. I can see the items populated in the ComboBox. But I also need to set it's SelectedItem property which I cant get to work.
XAML:
<ComboBox Name="EducationalLevelComboBoxUpdate"
ItemsSource="{x:Bind EduRoleList, Mode=OneWay}"
<!--IS THIS OKAY?-->
SelectedItem="{x:Bind Path=User.EduRole.Read, Mode=OneWay}"
PlaceholderText="Select educational role">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="viewModels:EduRoleViewModel">
<TextBlock Text="{x:Bind Read}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Source:
private ObservableCollection<EduRoleViewModel> EduRoleList { get; set; } = new ObservableCollection<EduRoleViewModel>();
ViewModel:
public class EduRoleViewModel
{
public string Key { get; set; }
public string Read { get; set; }
}
User is object of a class called UserViewModel that has EduRoleViewModel type property here's that class:
class UserViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private EduRoleViewModel _eduRole;
public EduRoleViewModel EduRole
{
get { return _eduRole; }
set
{
_eduRole = value;
this.OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I want the Read property of the EduRole property of UserViewModel to be the SelectedItem of the ComboBox
How would I go about implementing this?
Let's say this is my model:
public interface IAnimal
{
string Name { get; }
}
public class Fish : IAnimal
{
public string Name { get; set; }
public int ScalesCount { get; set; }
}
public class Dog : IAnimal
{
public string Name { get; set; }
public string CollarManufacturerName { get; set; }
}
public class ViewModel
{
public ObservableCollection<IAnimal> Animals { get; set; }
public ViewModel()
{
this.Animals = new ObservableCollection<IAnimal>();
this.Animals.Add(new Fish { Name = "Carl", ScalesCount = 9000 });
this.Animals.Add(new Dog { Name = "Fifi", CollarManufacturerName = "Macrosoft" });
}
}
For the sake of the amount of code in this question please assume that INotifyPropertyChanged is implemented where necessary, and that the ViewModel is correctly initialized in the page.
How can I use my own corresponding DataTemplates? In WPF I would just define multiple DataTemplates without an x:Key but with a defined DataType and let the ListView chose which to use based on the type of the item. UWP doesn't like that; the compiler simply states Dictionary Item "DataTemplate" must have a Key attribute. So how do I accomplish my goal?
Current Attempt
My current attempt is to make a custom DataTemplateSelector, which seems rather straight forward.
public class MyDataTemplateSelector: Windows.UI.Xaml.Controls.DataTemplateSelector
{
public ObservableCollection<TemplateMatch> Matches { get; set; }
public DataTemplateSelector()
{
this.Matches = new ObservableCollection<TemplateMatch>();
}
protected override DataTemplate SelectTemplateCore(object item)
{
return this.Matches.FirstOrDefault(m => m.TargetType.Equals(item))?.Template;
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
return this.Matches.FirstOrDefault(m => m.TargetType.Equals(item))?.Template;
}
}
public class TemplateMatch
{
public Type TargetType { get; set; }
public DataTemplate Template { get; set; }
}
Define it in XAML like this:
<ListView ItemsSource="{x:Bind ViewModel.Animals}">
<ListView.ItemTemplateSelector>
<cmp:MyDataTemplateSelector>
<cmp:MyDataTemplateSelector.Matches>
<cmp:TemplateMatch TargetType="model:Dog" Template="{StaticResource DogTemplate}"/>
<cmp:TemplateMatch TargetType="model:Fish" Template="{StaticResource FishTemplate}"/>
</cmp:MyDataTemplateSelector.Matches>
</cmp:MyDataTemplateSelector>
</ListView.ItemTemplateSelector>
</ListView>
Unfortunately when I run this, an Exception occurs during runtime, stating Failed to create a 'Ui.Components.TemplateMatch' from the text 'model:Dog'. So it seems binding to a Type property is not that easy.
Any help is appreciated!
Please note that I'd like to use a property of type Type, as opposed to string where I would pass the CLR type name and using reflection to invoke the type, mostly because I don't want mixed CLR and XML namespaces appear in XAML. If you can find a way to invoke the type using the XML namespace, I'll gladly take that as an answer.
I found workaround. If you able to create instances of these types - you can use it for detecting types:
[ContentProperty(Name = nameof(Matches))]
public class TypeTemplateSelector : DataTemplateSelector
{
public ObservableCollection<TemplateMatch> Matches { get; set; }
public TypeTemplateSelector()
{
this.Matches = new ObservableCollection<TemplateMatch>();
}
protected override DataTemplate SelectTemplateCore(object item)
{
return this.Matches.FirstOrDefault(m => m.ItemOfType.GetType().Equals(item.GetType()))?.TemplateContent;
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
return this.Matches.FirstOrDefault(m => m.ItemOfType.GetType().Equals(item.GetType()))?.TemplateContent;
}
}
[ContentProperty(Name = nameof(ItemOfType))]
public class TemplateMatch
{
public object ItemOfType { get; set; }
public DataTemplate TemplateContent { get; set; }
}
XAML:
<controls:TypeTemplateSelector>
<controls:TemplateMatch TemplateContent="{StaticResource FishTemplate}">
<models:Fish/>
</controls:TemplateMatch>
<controls:TemplateMatch TemplateContent="{StaticResource DogTemplate}">
<models:Dog/>
</controls:TemplateMatch>
</controls:TypeTemplateSelector>
The clue is in the error message.
Failed to create a 'Ui.Components.TemplateMatch' from the text
'model:Dog'
Note the 'model:Dog' is coming to your selector as text not a type.
Change your TemplateMatch class TargetType property to string instead of type like this:-
public class TemplateMatch
{
public string TargetType { get; set; }
public DataTemplate Template { get; set; }
}
Then change your template selector class to read
public class MyDataTemplateSelector : DataTemplateSelector
{
public ObservableCollection<TemplateMatch> Matches { get; set; }
public MyDataTemplateSelector()
{
Matches = new ObservableCollection<TemplateMatch>();
}
protected override DataTemplate SelectTemplateCore(object item)
{
return Matches.FirstOrDefault(m => m.TargetType.Equals(item.GetType().ToString()))?.Template;
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
return Matches.FirstOrDefault(m => m.TargetType.Equals(item.GetType().ToString()))?.Template;
}
}
Finally change your xaml to read
<ListView ItemsSource="{x:Bind ViewModel.Animals}">
<ListView.ItemTemplateSelector>
<cmp:MyDataTemplateSelector>
<cmp:MyDataTemplateSelector.Matches>
<cmp:TemplateMatch TargetType="YourFullNamespaceNotXamlNamespace.Dog" Template="{StaticResource DogTemplate}"/>
<cmp:TemplateMatch TargetType="YourFullNamespaceNotXamlNamespace.Fish" Template="{StaticResource FishTemplate}"/>
</cmp:MyDataTemplateSelector.Matches>
</cmp:MyDataTemplateSelector>
</ListView.ItemTemplateSelector>
</ListView>
The point is to forget trying to pass it to your selector as a type, and pass the typename as a string instead (Full namespace not Xaml namespace).