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.
Related
I am developing Facebook like feeds page where i have a list of Urls ( videos and images ). How can i load in xaml the Ui control specific to the Url type ( image or video ).
Example :
```<CollectionView ItemsSource="{Binding UrlList}">
<Grid>
If ( url is image)
<Image Source="{Binding Url}"/>
If ( url is video )
<MediaElement Source="{Binding Url}" />
</Grid>
</CollectionView>```
Yes, you can use DataTemplateSelector to achieve this.
A DataTemplateSelector can be used to choose a DataTemplate at runtime based on the value of a data-bound property. This enables multiple DataTemplates to be applied to the same type of object, to customize the appearance of particular objects.
1.Creating a DataTemplateSelector
A data template selector is implemented by creating a class that inherits from DataTemplateSelector. The OnSelectTemplate method is then overridden to return a particular DataTemplate.
You can refer the following code:
public class UrlTemplateSelector : DataTemplateSelector
{
public DataTemplate ImageTemplate { get; set; }
public DataTemplate VideoTemplate { get; set; }
public DataTemplate OtherTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
ItemModel model = (ItemModel)item;
if (model.Url.EndsWith(".mp4")|| model.Url.EndsWith(".avi")|| model.Url.EndsWith(".rmvb")) // you can add multiple video file suffixes
{
return VideoTemplate;
}
else if (model.Url.EndsWith(".png")|| model.Url.EndsWith(".bmp") || model.Url.EndsWith(".jpg"))//you can add multiple image file suffixes
{
return ImageTemplate;
}
else {
return OtherTemplate;
}
}
}
Suppose ItemModel is the binded item.
public class ItemModel
{
public string Name { get; set; }
public string Url { get; set; }
}
2. Usage(suppose the page is TestPage)
TestPage.xaml
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="imageTemplate">
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.4*" />
<ColumnDefinition Width="0.6*" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Name}" TextColor="Green" FontAttributes="Bold" />
<Image Grid.Column="1" Source="{Binding Url}" />
</Grid>
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="videoTemplate">
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.4*" />
<ColumnDefinition Width="0.6*" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Name}" TextColor="Red" FontAttributes="Bold" />
<behaviors:MediaElement Grid.Column="1" Source="{Binding Url}" />
</Grid>
</ViewCell>
</DataTemplate>
<local:UrlTemplateSelector x:Key="mediaUrlTemplateSelector" ImageTemplate="{StaticResource imageTemplate}" VideoTemplate="{StaticResource videoTemplate}" />
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout Margin="20">
<Label Text="ListView with a DataTemplateSelector" FontAttributes="Bold" HorizontalOptions="Center" />
<ListView x:Name="listView" Margin="0,20,0,0" ItemTemplate="{StaticResource mediaUrlTemplateSelector}" />
</StackLayout>
For more details, you can check:https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/templates/data-templates/selector.
And there is a sample included in above document, you can try it.
I am trying to implement a MVVM ContentPage with a ListView that needs to bind to a populated generic list of XML model objects in a ViewModel but the binding fails. The code that is shown calls an API that does return a valid list of XML data. The same code works fine when the binding is done directly in the code behind of the XAML Xamarin contentpage by setting the ItemSource in the codebehind. As said, the problem only happens when trying to pass the ListView through the ViewModel assigned to the contentpage. I have stepped through the code in the ViewModel and the ListView is populated successfully but the binding just doesn't work. I have other controls that are on the page that the model binding does work for but the only control that doesn't work is the ListView. The code is shown below:
ViewModel:
using RestDemo.Model;
using RestDemo.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using System.ComponentModel;
using System.Windows.Input;
using System.Collections.ObjectModel;
namespace RestDemo.ViewModel
{
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel ()
{
GetRequest();
}
List<XmlPizzaDetails> _objPizzaList;
string _selectedDescription = "Descriptions: ";
bool _progress;
string _cusButtonText = "Hello";
public bool Progress
{
get { return _progress; }
set { _progress = value; }
}
public string CusButtonText
{
get { return _cusButtonText; }
set { _cusButtonText = value; }
}
public string SelectedDescription
{
get { return _selectedDescription; }
set { _selectedDescription = value; }
}
public List<XmlPizzaDetails> ObjPizzaList
{
get { return _objPizzaList; }
set
{
if (_objPizzaList != value)
{
_objPizzaList = value;
OnPropertyChanged("ObjPizzaList");
}
}
}
public string Description
{
get { return _selectedDescription; }
set { _selectedDescription = value; }
}
public ICommand SelectedCommand => new Command(() =>
{
CusButtonText = "Goodby";
});
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add
{
}
remove
{
}
}
public async void GetRequest()
{
if (NetworkCheck.IsInternet())
{
Uri geturi = new Uri("http://api.androidhive.info/pizza/?format=xml"); //replace your xml url
HttpClient client = new HttpClient();
HttpResponseMessage responseGet = await client.GetAsync(geturi);
string response = await responseGet.Content.ReadAsStringAsync();
//Xml Parsing
ObjPizzaList = new List<XmlPizzaDetails>();
XDocument doc = XDocument.Parse(response);
foreach (var item in doc.Descendants("item"))
{
XmlPizzaDetails ObjPizzaItem = new XmlPizzaDetails();
ObjPizzaItem.ID = item.Element("id").Value.ToString();
ObjPizzaItem.Name = item.Element("name").Value.ToString();
ObjPizzaItem.Cost = item.Element("cost").Value.ToString();
ObjPizzaItem.Description = item.Element("description").Value.ToString();
ObjPizzaList.Add(ObjPizzaItem);
}
Progress = false;
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
}
XAML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xlocal="clr-namespace:RestDemo.ViewModel"
xmlns:local="clr-namespace:RestDemo"
xmlns:Views="clr-namespace:RestDemo.Views"
x:Class="RestDemo.XmlParsingPageBehavior">
<ContentPage.BindingContext>
<xlocal:ViewModel />
</ContentPage.BindingContext>
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Views:CustomButton Grid.Row="0" Grid.Column="0" Text="HOME" />
<Views:CustomButton Grid.Row="0" Grid.Column="1" Text="Administrative Maintence" />
<Views:CustomButton Grid.Row="0" Grid.Column="2" Text="User Maintence" />
<Views:CustomButton Grid.Row="0" Grid.Column="3" Text="About" />
</Grid>
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Views:CustomButton Grid.Row="0" Grid.Column="0" Text="{Binding CusButtonText, Mode=TwoWay}">
<Views:CustomButton.Behaviors>
<local:ItemSelectedToCommandBehavior />
</Views:CustomButton.Behaviors>
</Views:CustomButton>
</Grid>
<Frame Margin="5, 5, 5, 5" Grid.Row="2" Grid.Column="1" BackgroundColor = "Cyan">
<ListView x:Name="PizzaListView" ItemsSource="{Binding ObjPizzaList}" Margin="5, 0, 5, 0" Grid.Row="2" Grid.Column="1" HorizontalOptions="FillAndExpand" HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid HorizontalOptions="FillAndExpand" Margin="0,0,0,0" Padding="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Text="{Binding Name}" HorizontalOptions="StartAndExpand" Grid.Row="0" TextColor="Blue" FontAttributes="Bold"/>
<Label Text="{Binding Cost}" HorizontalOptions="StartAndExpand" Grid.Row="1" TextColor="Orange" FontAttributes="Bold"/>
<Label Text="{Binding Description}" HorizontalOptions="StartAndExpand" Grid.Row="2" TextColor="Gray" FontAttributes="Bold"/>
<BoxView HeightRequest="2" Margin="0,10,10,0" BackgroundColor="Gray" Grid.Row="3" HorizontalOptions="Fill" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Frame>
</Grid>
<ActivityIndicator x:Name="ProgressLoader" IsVisible="{Binding Progress}" IsRunning="True"/>
</Grid>
</ContentPage>
I have it working. Yes ObservableCollections are the way to go but generic Lists are fine as well. The problem is that when the Model is bound the WebService call has not completed so when the List property is bound it is still null. Even when updated at this point the ObservableCollection won't work because it has not been seeded. The solution is to seed the ObservableCollection, or List, on the Page's OnAppearing event and bind the ViewModel as the BindingContext in this event. My solution is below:
protected override async void OnAppearing()
{
var vm = new ViewModel.ViewModel();
if (vm == null)
return;
HttpClient client = new HttpClient();
HttpResponseMessage responseGet = await client.GetAsync(vm.Geturi);
string response = await responseGet.Content.ReadAsStringAsync();
//Xml Parsing
var _objPizzaList = new ObservableCollection<XmlPizzaDetails>();
XDocument doc = XDocument.Parse(response);
vm.GetRequest(doc);
this.BindingContext = vm;
}
I have a single Database EstimateInformationTable with CATEGORY, DESCRIPTION. 100 records total. There are 100 Descriptions and 10 Categories, so obviously the Categories are used more than once.
example:
+----------+-------------+
| CATEGORY | DESCRIPTION |
+----------+-------------+
| ACC | DescAAAA |
| ACC | DescBBBB |
| BMX | DescCCCC |
+----------+-------------+
I want to use the first ComboBox (CATEGORY) to limit the choices in the second ComboBox (DESCRIPTION).
I have two ComboBoxes bound to two DataSet TableAdaptors. Each TableAdaptor returns a simple, but different Schema.
ComboBox1:
SELECT DISTINCT CATEGORY
FROM EstimateInformationTable
ORDER BY CATEGORY
ComboBox2:
SELECT DESCRIPTION
FROM EstimateInformationTable
WHERE (CATEGORY = #Category)
ORDER BY DESCRIPTION
I can kind of get this to work in CodeBehind using an extra ButtonClickEvent, but obviously this is not ultimately what I want.
I want to use MVVM and the correct Notify Properties (if that is what needs to be done) so that changing the Category ComboBox updates the Description ComboBox.
This must be done all the time, but I am new to a lot of this. I was making progress, but I am having a hard time wrapping my head around this. This is a self imposed learning exercise, so feel free to dumb the explanation down for me. Thanks, mike
XAML:
<Page
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"
xmlns:local="clr-namespace:gobo.Pages"
xmlns:gobo="clr-namespace:gobo" x:Class="gobo.Pages.TESTSTUFF01"
xmlns:vm="clr-namespace:gobo.ViewModel"
mc:Ignorable="d"
Title="TESTSTUFF01" Loaded="Page_Loaded">
<Page.Resources>
<vm:CategoryChangedViewModel x:Key="CategoryChangedViewModel"/>
<gobo:gobo2018DataSet1 x:Key="gobo2018DataSet1"/>
<CollectionViewSource x:Key="estimateInformationTableViewSource" Source="{Binding EstimateInformationTable, Source={StaticResource gobo2018DataSet1}}"/>
<gobo:gobo2018EstimateInformationTableDescriptionDataSet x:Key="gobo2018EstimateInformationTableDescriptionDataSet"/>
<CollectionViewSource x:Key="estimateInformationTableViewSource1" Source="{Binding EstimateInformationTable, Source={StaticResource gobo2018EstimateInformationTableDescriptionDataSet}}"/>
</Page.Resources>
<Page.Background>
<LinearGradientBrush EndPoint="0.5,1" MappingMode="RelativeToBoundingBox" StartPoint="0.5,0">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="Red" Offset="1"/>
</LinearGradientBrush>
</Page.Background>
<StackPanel Orientation="Vertical" >
<Label Content="TEST STUFF 01 Tab Page1" VerticalAlignment="Top" Background="{x:Null}"/>
<Button Content="Change Description contents" Click="Button_Click"/>
<Grid x:Name="grid1" DataContext="{StaticResource estimateInformationTableViewSource}" HorizontalAlignment="Left" VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Content="CATEGORY:" Grid.Column="0" HorizontalAlignment="Left" Margin="3" Grid.Row="0" VerticalAlignment="Center"/>
<ComboBox x:Name="cATEGORYComboBox" Grid.Column="1" DisplayMemberPath="CATEGORY" HorizontalAlignment="Left" Height="Auto" Margin="3" Grid.Row="0" VerticalAlignment="Center" Width="120"
ItemsSource="{Binding}"
SelectedIndex="{Binding Category, Source={StaticResource CategoryChangedViewModel}}">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
</Grid>
<Grid x:Name="grid2" DataContext="{StaticResource estimateInformationTableViewSource1}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="311">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Content="DESCRIPTION:" Grid.Column="0" HorizontalAlignment="Left" Margin="3" Grid.Row="0" VerticalAlignment="Center"/>
<ComboBox x:Name="dESCRIPTIONComboBox" Grid.Column="1" DisplayMemberPath="DESCRIPTION" HorizontalAlignment="Left" Height="Auto" ItemsSource="{Binding}" Margin="3,9,-53,9" Grid.Row="0" VerticalAlignment="Center" Width="180">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
</Grid>
</StackPanel>
</Page>
CurrentCodeBehind
namespace gobo.Pages
{
/// <summary>
/// Interaction logic for TESTSTUFF01.xaml
/// </summary>
public partial class TESTSTUFF01 : Page
{
private gobo.gobo2018DataSet1 gobo2018DataSet1;
public gobo.gobo2018DataSet1TableAdapters.EstimateInformationTableTableAdapter gobo2018DataSet1TableAdapter;
private CollectionViewSource estimateInformationTableViewSource;
private gobo.gobo2018EstimateInformationTableDescriptionDataSet gobo2018EstimateInformationTableDescriptionDataSet;
private gobo.gobo2018EstimateInformationTableDescriptionDataSetTableAdapters.EstimateInformationTableTableAdapter EstimateInformationTableTableAdapter;
private CollectionViewSource estimateInformationTableViewSource1;
public TESTSTUFF01()
{
InitializeComponent();
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
gobo2018DataSet1 = ((gobo.gobo2018DataSet1)(this.FindResource("gobo2018DataSet1")));
gobo2018DataSet1TableAdapter = new gobo.gobo2018DataSet1TableAdapters.EstimateInformationTableTableAdapter();
gobo2018DataSet1TableAdapter.Fill(gobo2018DataSet1.EstimateInformationTable);
estimateInformationTableViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("estimateInformationTableViewSource")));
estimateInformationTableViewSource.View.MoveCurrentToFirst();
gobo2018EstimateInformationTableDescriptionDataSet = ((gobo.gobo2018EstimateInformationTableDescriptionDataSet)(this.FindResource("gobo2018EstimateInformationTableDescriptionDataSet")));
EstimateInformationTableTableAdapter = new gobo.gobo2018EstimateInformationTableDescriptionDataSetTableAdapters.EstimateInformationTableTableAdapter();
EstimateInformationTableTableAdapter.FillByCategory(gobo2018EstimateInformationTableDescriptionDataSet.EstimateInformationTable,"ACC");
estimateInformationTableViewSource1 = ((System.Windows.Data.CollectionViewSource)(this.FindResource("estimateInformationTableViewSource1")));
estimateInformationTableViewSource1.View.MoveCurrentToFirst();
}
public void LoadDescriptionToCbo(String parameter)
{
if(gobo2018EstimateInformationTableDescriptionDataSet != null)
{
EstimateInformationTableTableAdapter.FillByCategory(gobo2018EstimateInformationTableDescriptionDataSet.EstimateInformationTable, parameter);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
LoadDescriptionToCbo("ACT");
Console.WriteLine("Button Click -> LoadDescriptionToCode");
}
}
}
I've found that this works, but I am curious if it is the best answer.
private void cATEGORYComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox cbx = (ComboBox)sender;
string s = ((DataRowView)cbx.Items.GetItemAt(cbx.SelectedIndex)).Row.ItemArray[0].ToString();
LoadDescriptionToCbo(s);
}
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
I'm using a MVVM-Pattern with a ModelView-First approach. This works fine, so far.
Now I have a UserControl (View) which should display various content depending on a Property located in my ViewModel.
First, I tried to solve the issue with DataTemplates and a DataTemplateSelector (See this tutorial) This was working very well. But I was not happy with the solution, because then I have a class (the overrided DataTemplateSelector) which is not connected to the ViewModel and can't be filled from the model.
So I tried to create a own TemplateSelector which uses a Property from the ViewModel. Unfortunately the DataTrigger is not triggering. The Binding from a CheckBox to the ViewModel is also working but not at the DataTrigger (even the designer can't find this path).
Ok, please have a look at the code:
<UserControl.Resources>
<!--Define Template which is displayed for Users-->
<DataTemplate x:Key="templateUser">
<Image
Name="logo"
Source="blanked out"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</DataTemplate>
<!--Define Template which is displayed for Administrators-->
<DataTemplate x:Key="templateAdmin">
<TextBlock Background="Yellow" Margin="3" Text="YEAH, I'm an Administrator" />
</DataTemplate>
<!--My own TemplateSelectpr-->
<DataTemplate x:Key="myTemplateSelector">
<ContentControl x:Name="DynamicContent" ContentTemplate="{StaticResource templateUser}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsAdministrator}" Value="true">
<Setter TargetName="DynamicContent" Property="ContentTemplate" Value="{StaticResource templateAdmin}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentPresenter ContentTemplate="{StaticResource myTemplateSelector}"/>
</Grid>
Of course, I can seperate the Task in two further contentcontrols, but I don't want to maintain those if same content is intersecting.
So can someone suggest anything?
Best regards, and thanks in advance!
The simpler, the better: use a single template, which includes all the controls you need to show. Then switch their visibility using a binding to your property:
<UserControl.Resources>
<DataTemplate x:Key="myTemplate">
<Grid>
<Grid Visibility="{Binding IsAdministrator, Converter={StaticResource BooleanToVisibilityConverter}}">
<!-- Content for admin -->
</Grid>
<Grid Visibility="{Binding IsAdministrator, Converter={StaticResource NotBooleanToVisibilityConverter}}">
<!-- Content for user -->
</Grid>
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentPresenter ContentTemplate="{StaticResource myTemplate}"/>
</Grid>
Answer is to long for comment
Arnaud Weil brought me on the right way:
To access the Property 'IsAdministrator' in ViewModel from the Datatemplate, I gave the UserControl a Name e.g.:
<UserControl
x:Class="blanked out"
x:Name="this"
Used the code from Arnaud with some modifications, to inherit the Binding to the ViewModel from UserControl
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<helper:NotBooleanToVisibilityConverter x:Key="NotBooleanToVisibilityConverter"/>
<DataTemplate x:Key="myTemplate">
<Grid>
<Grid Visibility="{Binding DataContext.IsAdministrator, ElementName=this, Converter={StaticResource BooleanToVisibilityConverter}}">
<!-- Content for admin -->
<TextBlock Background="Yellow" Margin="3" Text="ICH BIN ADMNIN; JUCHUUU" />
</Grid>
<Grid Visibility="{Binding DataContext.IsAdministrator, ElementName=this, Converter={StaticResource NotBooleanToVisibilityConverter}}">
<!-- Content for user -->
<Image
Name="logo"
Source="/blanked out"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</Grid>
</DataTemplate>
</UserControl.Resources>
And for the inverted BooleanToVisibilityConverter:
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace blankedout.Helper
{
[ValueConversion(typeof(bool), typeof(Visibility))]
public class NotBooleanToVisibilityConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var boolValue = (bool)value;
return !boolValue ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Thanks once again to Arnaud Weil
Regards