Using AppThemeBinding in a IConverter doesn’t react on theme changes - maui

I have created a bool to color like this:
public class BoolToColorConverter : BindableObject, IValueConverter
{
public BoolToColorConverter()
{
}
public static readonly BindableProperty TrueColorProperty =
BindableProperty.Create(nameof(TrueColor), typeof(Color), typeof(BoolToColorConverter), null, BindingMode.OneWay, null, null);
public Color TrueColor
{
get { return (Color) GetValue(TrueColorProperty); }
set { SetValue(TrueColorProperty, value); }
}
public Color FalseColor { get; set; } = null!;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool b && b)
{
return TrueColor!;
}
return FalseColor!;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
With this I could use AppThemeBinding when I create the converter:
<ContentPage.Resources>
<converts:BoolToColorConverter
x:Key="ColorConverter"
TrueColor="{AppThemeBinding Light=DarkRed, Dark=LightSalmon}"
FalseColor="#888" />
</ContentPage.Resources>
<VerticalStackLayout>
<Label
Text="Hello, World!"
TextColor="{Binding Source={x:Reference Checky}, Path=IsChecked, Converter={StaticResource ColorConverter}}"
FontSize="32"
HorizontalOptions="Center" />
<CheckBox x:Name="Checky" />
</VerticalStackLayout>
This works as expected if the theme is set on start up.
Dark on start:
Ligh on start:
But if the theme is changed when the application is running, the binding is not reevaluated and the old color is shown. Here is how looks like if thmese is changed from dark mode to light:
Is there some workaround for this?

You could use the Visual State Manager instead.
With StateTrigger
This is the easiest solution for this specific scenario. Add this converter:
internal class InvertedBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
return !(bool)value;
}
catch
{
return false;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And then use this XAML:
<ContentPage.Resources>
<converts:InvertedBoolConverter x:Key="InvertedBoolConverter" />
</ContentPage.Resources>
<VerticalStackLayout
x:Name="MainLayout"
Spacing="25"
Padding="30,0">
<Label
Text="Hello, World!"
FontSize="32"
HorizontalOptions="Center">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="Checked state">
<VisualState Name="Checked">
<VisualState.StateTriggers>
<StateTrigger
IsActive="{Binding Source={x:Reference Checky}, Path=IsChecked}"
/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light=DarkRed, Dark=LightSalmon}" />
</VisualState.Setters>
</VisualState>
<VisualState Name="Not checked">
<VisualState.StateTriggers>
<StateTrigger
IsActive="{Binding Source={x:Reference Checky}, Path=IsChecked, Converter={StaticResource InvertedBoolConverter}}"
/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="TextColor" Value="#888" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Label>
<CheckBox x:Name="Checky" />
</VerticalStackLayout>
With custom trigger
In a more complex scenario, you might want to use a customer trigger instead. Sample:
public class BoolTrigger : StateTriggerBase
{
public static readonly BindableProperty
ValueProperty = BindableProperty.Create(nameof(Value), typeof(object), typeof(BoolTrigger), null, propertyChanged: OnBindablePropertyChanged);
public object Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public bool OnValue { get; set; }
private static void OnBindablePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var trigger = bindable as BoolTrigger;
trigger.UpdateTrigger();
}
private void UpdateTrigger()
{
if (Value is not bool val)
{
return;
}
SetActive(OnValue == val);
}
}
And then like this in XAML:
<VerticalStackLayout
x:Name="MainLayout"
Spacing="25"
Padding="30,0">
<Label
Text="Hello, World!"
FontSize="32"
HorizontalOptions="Center">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="Checked state">
<VisualState Name="Checked">
<VisualState.StateTriggers>
<converts:BoolTrigger
OnValue="true"
Value="{Binding Source={x:Reference Checky}, Path=IsChecked}"
/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light=DarkRed, Dark=LightSalmon}" />
</VisualState.Setters>
</VisualState>
<VisualState Name="Not checked">
<VisualState.StateTriggers>
<converts:BoolTrigger
OnValue="false"
Value="{Binding Source={x:Reference Checky}, Path=IsChecked}"
/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="TextColor" Value="#888" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Label>
<CheckBox x:Name="Checky" />
</VerticalStackLayout>

Related

Xamarin forms same listview in different views [duplicate]

I have written a nice Grid with some other controls like: Entry and Image and now I would like to reuse it the simplest way.
This is my control for Email property:
<Grid
Style="{StaticResource gridEntryStyle}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="9*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="7" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<controls:ExtendedEntry
Grid.Row="0"
Grid.Column="0"
Text="{Binding UserEmail, Mode=TwoWay}"
Placeholder="{i18n:Translate UserEmailPlaceholder}"
Style="{StaticResource entryStyle}">
<controls:ExtendedEntry.Behaviors>
<behavior:EventToCommandBehavior
EventName="Focused"
Command="{Binding ControlFocusCommand}"
CommandParameter="UserEmail"/>
<behavior:EventToCommandBehavior
EventName="Unfocused"
Command="{Binding ControlUnfocusedCommand}"
CommandParameter="UserEmail"/>
</controls:ExtendedEntry.Behaviors>
</controls:ExtendedEntry>
<Image
Grid.Row="0"
Grid.Column="1"
Source="clear.png"
IsVisible="{Binding IsEntryFocused}"
Style="{StaticResource imageClearStyle}">
<Image.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding ClearCommand}"
CommandParameter="UserEmail"/>
</Image.GestureRecognizers>
</Image>
<Image
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Source="lineWhite.png"
Style="{StaticResource imageLineStyle}"/>
<Image
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Source="linePure.png"
Style="{StaticResource imageLineStyle}"
IsVisible="{Binding IsError}"/>
<Image
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Source="lineGradient.png"
Style="{StaticResource imageLineStyle}"
IsVisible="{Binding IsEntryFocused}"/>
<Label
Grid.Row="2"
Grid.Column="0"
Text="{Binding ErrorMessage}"
Style="{StaticResource labelErrorStyle}"
IsVisible="{Binding IsError}"/>
<Image
Grid.Row="2"
Grid.Column="1"
Source="error.png"
Style="{StaticResource imageErrorStyle}"
IsVisible="{Binding IsError}"/>
</Grid>
I would like to reuse it for example as follows:
<usercontrols:EntryControl
MainText="{Binding UserEmail}"
MainTextPlaceholder="{i18n:Translate UserEmailPlaceholder}" />
For now even this simple example is not working and I have no idea how to define Command in this control. For now I have:
public partial class EntryControl : ContentView
{
public EntryControl()
{
InitializeComponent();
}
public static readonly BindableProperty MainTextProperty =
BindableProperty.Create(
propertyName: "MainText",
returnType: typeof(string),
declaringType: typeof(string),
defaultValue: string.Empty,
defaultBindingMode: BindingMode.TwoWay);
public string MainText
{
get { return (string)this.GetValue(MainTextProperty); }
set { this.SetValue(MainTextProperty, value); }
}
public static readonly BindableProperty MainTextPlaceholderProperty =
BindableProperty.Create(
propertyName: "MainTextPlaceholder",
returnType: typeof(string),
declaringType: typeof(string),
defaultValue: string.Empty,
defaultBindingMode: BindingMode.TwoWay);
public string MainTextPlaceholder
{
get { return (string)this.GetValue(MainTextPlaceholderProperty); }
set { this.SetValue(MainTextPlaceholderProperty, value);}
}
}
Is this the right way? or is this even possible in Xamarin.Forms?
XAML:
<?xml version="1.0" encoding="utf-8" ?>
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ApplicationName.Controls.EntryControl"
Style="{StaticResource gridEntryStyle}">
</Grid>
xaml.cs:
namespace ApplicationName.Controls
{
public partial class EntryControl : Grid
{
public static readonly BindableProperty CommandProperty =
BindableProperty.Create(
propertyName: nameof(Command),
returnType: typeof(ICommand),
declaringType: typeof(EntryControl),
defaultValue: null,
defaultBindingMode: BindingMode.TwoWay);
public string Command
{
get { return (string)this.GetValue(CommandProperty); }
set { this.SetValue(CommandProperty, value); }
}
public EntryControl()
{
InitializeComponent();
}
}
}
using:
xmlns:controls="clr-namespace:ApplicationName.Controls;assembly=ApplicationName"
<controls:EntryLabel/>
Your issue with BindingContext
In short, you have to write down the bindings inside your control like this {Binding UserEmail, Mode=TwoWay, Source={x:Reference myControlTHIS}}, where 'myControlTHIS' is x:Name="TheCategoryHeader".
More info:
BindingContext is everything when it comes to getting things to bind and work right in an MVVM app – WPF or Xamarin. Controls inherit context from the parent control unless a different context is explicitly assigned. That’s what we need to do here. We need to tell each UI element (label, entry, button, etc.) to explicitly look at this control for its context in order to find those BindingProperties we just made. This is one of the rare occasions when we actually give a XAML element a name: When it is going to be referenced by another XAML element within the XAML itself. To the ContentView, add a tag naming the control ‘this’. That’s right. We’re going to keep to the Microsoft naming and have this item refer to itself as 'myControlTHIS'. It makes all of us comfortable and the code and markup easy to read and follow.
We can now use 'myControlTHIS' as a reference source telling the rest of our XAML where to look for properties to bind to.

Can I use TemplateBinding in a VisualState Setter?

I'm trying to bind a property value in a VisualState to a property of the templated FrameworkElement using TemplateBinding but it doesn't work. There are no errors but no results, either. Here's an example of what I tried:
public class ButtonEx : Button
{
public ButtonEx() : base() { }
public static readonly DependencyProperty BackgroundPointerOverProperty = DependencyProperty.Register(
"BackgroundPointerOver", typeof(SolidColorBrush), typeof(ButtonEx), new PropertyMetadata(null));
public SolidColorBrush BackgroundPointerOver
{
get => (SolidColorBrush)GetValue(BackgroundPointerOverProperty);
set => SetValue(BackgroundPointerOverProperty, value);
}
}
The Control Template:
<ControlTemplate TargetType="classes:ButtonEx">
<Grid x:Name="LayoutRoot" >
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="LayoutRoot.Background" Value="{TemplateBinding BackgroundPointerOver}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
Shouldn't the Setter set the ButtonEx background to the value of the parent property on mouse-over? This is in a WinUI 3 Desktop App.
You can make it work binding via RelativeSource.
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="LayoutRoot.Background" Value="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=BackgroundPointerOver}" />
</VisualState.Setters>
</VisualState>

Tap gesture with Command Parameters

I have a listview with an ItemTemplate defined as follows:
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.7*" />
<ColumnDefinition Width="0.3*" />
</Grid.ColumnDefinitions>
<Label Text="{Binding AddressTypeName}" Grid.Column="0"/>
<Label Text=""
Grid.Column="1"
FontFamily="{StaticResource SegoeMDL2Assets}" FontSize="Medium" HorizontalOptions="End">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="OnDelete_Tapped" CommandParameter="{Binding .}"/>
</Label.GestureRecognizers>
</Label>
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
My code behind file handles the OnDelete_Tapped as follows:
public void OnDelete_Tapped(object sender, EventArgs e)
{
viewModel.DeleteCommand.Execute(e);
viewModel.LoadAddressTypesCommand.Execute(true);
}
The EventArgs e indeed returns a EventArgs object that has the correct object in it (in my case, the correct AddressType object).
The ViewModel, has this defined:
public ICommand DeleteCommand => new Command<AddressType>(DeleteCommandExecute);
void DeleteCommandExecute(AddressType address)
{
if (IsBusy)
return;
IsBusy = true;
try
{
DataStore.DeleteAddressTypeAsync(address.Id);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
The DeleteCommand NEVER executes. I can step through the code, but it never calls the DeleteCommand in my viewModel. The LoadAddressTypesCommand runs properly - not only from the code behind but elsewhere I have that command bound as a Command to my viewModel. Any ideas what I am doing wrong? Thanks in advance.
you could try this:
in xaml:
<TapGestureRecognizer Tapped="OnDelete_Tapped" CommandParameter="{Binding .}"/>
then e.Parameter will be whatever you set in the CommandParameter.
public void OnDelete_Tapped(object sender, TappedEventArgs e)
{
var addressType = (e.Parameter) as AddressType;
viewModel.DeleteCommand.Execute(addressType );
viewModel.LoadAddressTypesCommand.Execute(true);
}
or directly Using ICommand
<TapGestureRecognizer
Command="{Binding DeleteCommand}"
CommandParameter="{Binding .}" />

Update textbox value based on combox selection in uwp using mvvm

I have view with a combobox and five textboxes which is used to add new customer. To add the customer, declared the properties in my viewmodel and bind those properties to all respective textboxes text properties like,
View:
<StackPanel>
<ComboBox ItemsSource="{Binding Customers}" SelectedItem="{Binding SelectedCustomers}" DisplayMemberPath="Name"/>
<Textbox Text="{Binding Name}"/>
<Textbox Text="{Binding Age}"/>
<Textbox Text="{Binding Phone}"/>
<Textbox Text="{Binding Address}"/>
<Textbox Text="{Binding Email}"/>
<StackPanel>
ViewModel:
public class myviewmodel
{
private string _name;
public string Name
{
get { return _name;}
set { _name = value; OnPropertyChanged("Name"); }
}
private string _age;
public string Age
{
get { return _age;}
set { _age = value; OnPropertyChanged("Age"); }
}
private Customer _selectedCustomer;
public Customer SelectedCustomer
{
get { return _selectedCustomer; }
set { selectedCustomer = value; OnPropertyChanged("SelectedCustomer"); }
}
}
I will load the existing customer names to the comboxbox. To update the existing
customer details, if i select a customer name in combobox the selected customer details
should bind in textboxes so that i can easily update them. but the textbox text
properties are already used to add the new customers. so how to add and update the
customers using the same textboxes??
The Textboxes' Bindings are not set to the SelectedCustomer. Give this a try.
<ComboBox ItemsSource="{Binding Customers}" SelectedItem="{Binding SelectedCustomer}" DisplayMemberPath="Name"/>
<Grid Visibility="{Binding ExistingCustomer, Converter={StaticResource VisibilityConverter}}">
<Textbox Text="{Binding SelectedCustomer.Name, Mode=TwoWay}"/>
<Textbox Text="{Binding SelectedCustomer.Age, Mode=TwoWay}}"/>
<Textbox Text="{Binding SelectedCustomer.Phone, Mode=TwoWay}}"/>
<Textbox Text="{Binding SelectedCustomer.Address, Mode=TwoWay}}"/>
<Textbox Text="{Binding SelectedCustomer.Email, Mode=TwoWay}}"/>
</Grid>
<Grid Visibility="{Binding ExistingCustomer, Converter={StaticResource VisibilityConverter}}">
<Textbox Text="{Binding Customer.Name, Mode=TwoWay}"/>
<Textbox Text="{Binding Customer.Age, Mode=TwoWay}}"/>
<Textbox Text="{Binding Customer.Phone, Mode=TwoWay}}"/>
<Textbox Text="{Binding Customer.Address, Mode=TwoWay}}"/>
<Textbox Text="{Binding Customer.Email, Mode=TwoWay}}"/>
</Grid>
And here is the code for the converter
public class VisibilityConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string culture)
{
bool displayControl = (bool)value;
if (displayControl == true)
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType, object parameter, string culture)
{
return null;
}
}
Be sure to reference the namespace where your converter lives in your page resources
Using Interaction Behaviors this can be achieved.
First of all, the Behaviors SDK is not built-in UWP, but has to be downloaded separately from NuGet.
You can use the following command to install it:
Install-Package Microsoft.Xaml.Behaviors.Uwp.Managed
Or just use the NuGet Package Manager and search for Microsoft.Xaml.Behaviors.Uwp.Managed.
After you install, you can just add the XAML using statements to the top of your page:
<Page ...
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core" />
You do not need to create another property SelectedCustomer if you are always going to bind your TextBox from Combobox SelectedItem. Unless you intend to use SelectedCustomer for some other code behind work.
Below is how you achieve your TextBox Text without SelectedCustomer Property.
<ComboBox x:Name="comboBox" ItemsSource="{Binding Customers}" DisplayMemberPath="Name" HorizontalAlignment="Stretch" Margin="5,0">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SelectionChanged">
<core:ChangePropertyAction TargetObject="{Binding ElementName=stkData}" PropertyName="DataContext" Value="{Binding SelectedItem, ElementName=comboBox}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ComboBox>
<StackPanel Orientation="Vertical" Grid.Row="1" Margin="5,20" DataContext="{Binding SelectedItem, ElementName=comboBox}">
<TextBox Text="{Binding Name, Mode=TwoWay}" PlaceholderText="Name" Name="Name"/>
<TextBox Text="{Binding Age, Mode=TwoWay}" PlaceholderText="Age" Name="Age"/>
<TextBox Text="{Binding Phone, Mode=TwoWay}" PlaceholderText="Phone" Name="Phone"/>
<TextBox Text="{Binding Address, Mode=TwoWay}" PlaceholderText="Address" Name="Address"/>
<TextBox Text="{Binding Email, Mode=TwoWay}" PlaceholderText="Email" Name="Email"/>
</StackPanel>
If you notice, I bound the data from comboBox SelectedItem Property to StackPanel DataContext and then Individual Properties directly to Text of your TextBox. This will take care of your ComboBox Binding to TextBox. Mode should always be TwoWay so that data can be updated when user Modifies Existing Customer Details.
Now after this is done, When you want to add a new Customer Info with current TextBoxes, You want to clear the selection without changing the current value. Below is how I added a Button with Interaction Behaviors.
And use the Clicked action to clear the TextBox Text. In UWP XAML you can use empty string as value and it works as expected.
<Button Content="+" Grid.Column="1" Margin="5,0" Tag="{Binding EmptyClass, Mode=TwoWay}" Name="btnNew">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">
<core:ChangePropertyAction TargetObject="{Binding ElementName=stkData}" PropertyName="DataContext" Value="{Binding Tag, ElementName=btnNew, Mode=TwoWay}" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=comboBox}" PropertyName="SelectedIndex" Value="-1" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
Also Note I am making the selected Index of ComboBox to -1 so that selected Data cannot be updated.
And now the saving portion should be pretty simple and straight forward. Below is a button that i used with TappedEvent
private void saveBtn_Tapped(object sender, TappedRoutedEventArgs e)
{
model.Customers.Add(model.EmptyClass);
model.EmptyClass = new MyClass();
}
You can see I am reinstantiating EmptyClass Again so that old bound data can be cleared off.
Below is Complete XAML
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App15"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Text="using:System.Text"
x:Class="App15.MainPage"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
Loaded="Page_Loaded"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Margin="0,100,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ComboBox x:Name="comboBox" ItemsSource="{Binding Customers}" DisplayMemberPath="Name" HorizontalAlignment="Stretch" Margin="5,0">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SelectionChanged">
<core:ChangePropertyAction TargetObject="{Binding ElementName=stkData}" PropertyName="DataContext" Value="{Binding SelectedItem, ElementName=comboBox}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ComboBox>
<Button Content="+" Grid.Column="1" Margin="5,0" Tag="{Binding EmptyClass, Mode=TwoWay}" Name="btnNew">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">
<core:ChangePropertyAction TargetObject="{Binding ElementName=stkData}" PropertyName="DataContext" Value="{Binding Tag, ElementName=btnNew, Mode=TwoWay}" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=comboBox}" PropertyName="SelectedIndex" Value="-1" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
</Grid>
<StackPanel Orientation="Vertical" Grid.Row="1" Margin="5,20" Name="stkData">
<TextBox Text="{Binding Name, Mode=TwoWay}" PlaceholderText="Name"/>
<TextBox Text="{Binding Age, Mode=TwoWay}" PlaceholderText="Age" />
<TextBox Text="{Binding Phone, Mode=TwoWay}" PlaceholderText="Phone" />
<TextBox Text="{Binding Address, Mode=TwoWay}" PlaceholderText="Address" />
<TextBox Text="{Binding Email, Mode=TwoWay}" PlaceholderText="Email" />
</StackPanel>
<Button Name="saveBtn" Content="Save" HorizontalAlignment="Stretch" Margin="5" Grid.Column="1" Grid.Row="2" Tapped="saveBtn_Tapped"/>
</Grid>
</Grid>
</Page>
Below is Complete Code Behind
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
namespace App15
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
MyViewModel model = new MyViewModel();
private void Page_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = model;
}
private void saveBtn_Tapped(object sender, TappedRoutedEventArgs e)
{
model.Customers.Add(model.EmptyClass);
model.EmptyClass = new MyClass();
}
}
}
Below is Complete View Model
public class MyViewModel
{
public MyViewModel()
{
Customers = new ObservableCollection<MyClass>();
EmptyClass = new MyClass();
for (int i = 0; i < 10; i++)
{
Customers.Add(new MyClass()
{
Name = "Item" + (i + 1).ToString(),
Address = "Address" + (i + 1).ToString(),
Age = "20" + (i + 1).ToString(),
Email = "Test" + (i + 1).ToString() + "#test.com",
Phone = (9876543210 + i).ToString()
});
}
}
public ObservableCollection<MyClass> Customers { get; set; }
public MyClass EmptyClass { get; set; }
}
public class MyClass
{
public string Name { get; set; }
public string Age { get; set; }
public string Phone { get; set; }
public string Address { get; set; }
public string Email { get; set; }
}
I know there is a way to even use Save Option in MVVM But I did not get the opportunity to use DelegateCommand. But if you would like to try it yourself, Check this Video by Jerry Nixon for Reference

Datagrid not showing data even though its boudn

I have a problem with my MVVM Light implementation where I have my WPF Toolkit DataGrid and Data context bound to the correct object but no data is showing up. Can someone tell me what I'm doing wrong?
Here is my code:
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
private static Logger logger = LogManager.GetCurrentClassLogger();
private C.Wsi.ClientSession privateSession;
private ObservableCollection<CashAccount> _cashaccounts;
public ObservableCollection<CashAccount> CashAccounts
{
get { return _cashaccounts; }
set
{
if (_cashaccounts.Equals(value))
{
return;
}
_cashaccounts = value;
RaisePropertyChanged("CashAccounts");
}
}
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
if (IsInDesignMode)
{
// Code runs in Blend --> create design time data.
}
else
{
_cashaccounts = new ObservableCollection<CashAccount>();
// Subscribe to CollectionChanged event
_cashaccounts.CollectionChanged += OnCashAccountListChanged;
logger.Info("----- Start -----");
// Code runs "for real"
Cs.Helper.Session session = new Session();
privateSession = session.getSession();
logger.Info("Private Session: " + privateSession.GetHashCode());
logger.Info("...Connected.....");
Cs.Helper.ResultSet results = new ResultSet();
PositionBean[] pos = results.getPositions(privateSession);
logger.Info("Positions returned: " + pos.Length);
SecurityBean[] secs = results.getSecurities(privateSession);
logger.Info("Securities returned: " + secs.Length);
ArrayBuilder ab = new ArrayBuilder(pos, secs);
CashAccount c = new CashAccount();
c.qtySod = 100.00;
c.name = "Hi";
c.account = "Acct1";
c.cashAmount = 67.00;
_cashaccounts.Add(c);
RaisePropertyChanged("CashAccounts");
//this._cashaccounts=ab.joinPositionSecurities();
}
}
void OnCashAccountListChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
}
////public override void Cleanup()
////{
//// // Clean up if needed
//// base.Cleanup();
////}
}
MainWindow.xaml
<Window x:Class="CreditSuisse.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CreditSuisse.Helper"
xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit"
mc:Ignorable="d"
Height="301"
Width="520"
Title="Credit Suisse - Custodial Cash Application"
DataContext="{Binding Path=Main, Source={StaticResource Locator}}">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Skins/MainSkin.xaml" />
</ResourceDictionary.MergedDictionaries>
<local:VisibilityConverter x:Key="VisibilityConverter" />
<!-- DataGrid Background -->
<LinearGradientBrush x:Key="BlueLightGradientBrush" StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="#FFEAF3FF"/>
<GradientStop Offset="0.654" Color="#FFC0DEFF"/>
<GradientStop Offset="1" Color="#FFC0D9FB"/>
</LinearGradientBrush>
<!-- DatGrid style -->
<Style TargetType="{x:Type dg:DataGrid}">
<Setter Property="Margin" Value="5" />
<Setter Property="Background" Value="{StaticResource BlueLightGradientBrush}" />
<Setter Property="BorderBrush" Value="#FFA6CCF2" />
<Setter Property="RowBackground" Value="White" />
<Setter Property="AlternatingRowBackground" Value="#FDFFD0" />
<Setter Property="HorizontalGridLinesBrush" Value="Transparent" />
<Setter Property="VerticalGridLinesBrush" Value="#FFD3D0" />
<Setter Property="RowHeaderWidth" Value="0" />
</Style>
<!-- Enable rows as drop targets -->
<Style TargetType="{x:Type dg:DataGridRow}">
<Setter Property="AllowDrop" Value="True" />
</Style>
</ResourceDictionary>
</Window.Resources>
<dg:DataGrid DataContext="{Binding MainViewModel}" ItemsSource="{Binding CashAccounts}">
Margin="5" AutoGenerateColumns="True" HorizontalScrollBarVisibility="False">
<dg:DataGrid.Columns>
<dg:DataGridTextColumn Binding="{Binding account}" Header="Account Code" />
<dg:DataGridTextColumn Binding="{Binding name}" Header="Security Name" />
<dg:DataGridTextColumn Binding="{Binding cashAmount}" Header="Quantity Start of Day" />
<dg:DataGridTextColumn Binding="{Binding price}" Header="Cash Delta (Price Delta)" />
<dg:DataGridTemplateColumn Header="Action">
<dg:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Get" Visibility="{Binding cashChanged, Converter={StaticResource VisibilityConverter}}" Background="Red" />
</DataTemplate>
</dg:DataGridTemplateColumn.CellTemplate>
</dg:DataGridTemplateColumn>
</dg:DataGrid.Columns>
</dg:DataGrid>
When you do DataContext="{Binding Path=Main, Source={StaticResource Locator}}" in your Window and after DataContext="{Binding MainViewModel}" in the Datagrid, WPF tries to bind to a property named MainViewModel on your MainViewModel class which will fail. You can confirm this by looking for the binding error in the Output window in VS.
Just remove DataContext="{Binding MainViewModel}" from the DataGrid and it should work.
EDIT:
There is an extra closing > at the end of the first line in your XAML:
<dg:DataGrid DataContext="{Binding MainViewModel}" ItemsSource="{Binding CashAccounts}">
Margin="5" AutoGenerateColumns="True" HorizontalScrollBarVisibility="False">
It should be:
<dg:DataGrid DataContext="{Binding MainViewModel}" ItemsSource="{Binding CashAccounts}"
Margin="5" AutoGenerateColumns="True" HorizontalScrollBarVisibility="Disabled">
Note: False is not a valid value for HorizontalScrollBarVisibility it supports Auto, Disabled, Hidden, Visible
But I don't know why WPF throws Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead for your original XAML instead of some more meaningful error.