So, I've got a UserControl which displays basic information about a customer extracted from a file object.
The control looks like this:
<UserControl x:Class="Ns.Gui.pnlDebtor"
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"
d:DesignHeight="230" d:DesignWidth="460" xmlns:my="clr-namespace:Ns.Gui">
<Grid>
<GroupBox Header="Debtor" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="groupBox1" VerticalAlignment="Stretch">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="28" />
<RowDefinition Height="56" />
<RowDefinition Height="28" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80*" />
<ColumnDefinition Width="380*" />
</Grid.ColumnDefinitions>
<Label Content="Name:" Height="28" HorizontalAlignment="Left" Name="lblName" VerticalAlignment="Top" />
<Label Content="Address:" Grid.Row="1" Height="28" HorizontalAlignment="Left" Name="lblAddress" VerticalAlignment="Top" />
<Label Content="Customer Nr.:" Grid.Row="2" Height="28" HorizontalAlignment="Left" Name="lblCustomerNr" VerticalAlignment="Top" />
<TextBox Grid.Column="1" Height="23" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="tbName" VerticalAlignment="Top" IsEnabled="False" IsReadOnly="False" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:pnlDebtor, AncestorLevel=1}, Path=DebtorName, Mode=OneWay}" />
<TextBox Grid.Column="1" Grid.Row="1" Height="46" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="tbAddress" VerticalAlignment="Top" IsReadOnly="True" IsEnabled="False" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:pnlDebtor, AncestorLevel=1}, Path=Adresse, Mode=OneWay}" />
<TextBox Grid.Column="1" Grid.Row="2" Height="23" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="tbCustomerNr" VerticalAlignment="Top" IsEnabled="False" IsReadOnly="True" />
<Button Content="Debtor Details" Grid.ColumnSpan="2" Grid.Row="3" Height="23" HorizontalAlignment="Left" Margin="0,0,0,0" Name="btnDetails" VerticalAlignment="Top" />
</Grid>
</GroupBox>
</Grid>
</UserControl>
My Name and Address Textboxes are bound to DebtorName and DebtorAddress properties of the pnlDebtor UserControl.
Codebehind is like so:
public partial class pnlDebtor : UserControl
{
private MyFile file = null;
public MyFile File
{
get
{
return file;
}
set
{
file = value;
tbCustomerNr.Text = file.CustomerNo;
}
}
private Contact debtor = null;
public Contact Debtor
{
get
{
if (debtor == null)
{
if (File != null)
{
debtor = AbstractDataObject.GetObject4ID<Contact>(File.DebtorID);
}
}
return debtor;
}
}
private Address debtorAddress = null;
public string Address
{
get
{
string result = string.Empty;
if (debtorAddress == null)
{
if (Debtor != null)
{
List<Address> lsAddresses = AbstractDataObject.GetObject4NonIdProperty<Address>("ContactID", Debtor.ID);
if (lsAddresses.Any())
{
debtorAddress = lsAddresses[0];
result += lsAddresses[0].Street + "\r\n"
+ lsAddresses[0].PostalCode + " " + lsAddresses[0].City;
}
}
}
else
{
result += debtorAddress.Street + "\r\n"
+ debtorAddress.PostalCode + " " + debtorAddress.City;
}
return result;
}
}
private string strDebtorName = string.Empty;
public string DebtorName
{
get
{
if (strDebtorName== string.Empty)
{
if (Debtor != null)
{
strDebtorName = Debtor.Name1;
if (Debtor.FirstName != null)
strSchuldnerName += ", " + Debtor.FirstName;
}
}
return strDebtorName;
}
}
public pnlDebtor()
{
InitializeComponent();
}
}
As you can see, unlike the Name and Address Textboxes, my CustomerNr Textbox is populated in the codebehind. When I pass in my file object, I extract the customer number and assign that value to the Text property of the appropriate Textbox. Both methods work, but the first method (Binding) seems to be prefered for WPF. Why?
To me, the disadvantages are:
1) Logic and presentation aren't separated. If I send my xaml to someone in design, there's a chance they could screw up my binding.
2) If I'm debugging and set a breakpoint, the Text property of my bound Textboxes is always an empty string. I can't see what's going on.
So, what are the advantages of using binding? Why is it the preferred method? Use small words. This is my first WPF project. :)
I think the missing link here is MVVM. Your bindings to properties in codebehind mean that that particular UserControl is still tightly coupled to that particular class. I wouldn't have said that it's any better than the version without bindings. The codebehind class won't compile unless the XAML page is bundled with it, because there's a reference to tbCustomerNr.Text.
Using MVVM and bindings, the ViewModel is completely isolated from the View. I can, if I like, delete the Views entirely from my program and the ViewModels will still compile without any complaints. This means that the ViewModel logic can be reused easily, and logic and UI development tasks can be separated cleanly.
Related
I have Viewmodel-A/view-A, Viewmodel-B/view-B. A user selects an item in view-B which triggers a method that calls a method in Viewmodel-A and passes it an int. The method in Viewmodel-A runs a query, parses the data, and supplies it to the properties that are bound to various fields in view-A. I have confirmed through various breakpoints that:
Viewmodel-B's method gets triggered and passes the appropriate int to Viewmodel-A
Viewmodel-A's does get initiated and the int gets passed
The query and parsing happens correctly in Viewmodel-A
the properties are correctly getting updated within Viewmodel-A
The bindings for each of the fields and properties are correct
I have no binding errors, no compile errors, etc.
This is the Viewmodel-B (AppointmentsViewVM) method (EditSelection_Clicked) that triggers to Viewmodel-A (AppointmentsEditVM)
public async void EditSelection_Clicked(object obj)
{
if(Messaging.AskQuestion("Do you want to edit this appointment?"))
{
SelectedVM = AppointmentsViewModel.ApptTabItems[1];
AppointmentsViewModel.ApptTabItems[1].IsSelected = true;
ApptSelectedID = ApptIDSelected.ApptID;
new AppointmentsEditVM().RetrieveEditInfo(ApptSelectedID);
}
}
This is the method (RetrieveEditInfo) within Viewmodel-A (AppointmentsEditVM) - excuse my mess, I've been having a hard time figuring out whats going on
public async void RetrieveEditInfo(int _ApptID)
{
Console.WriteLine("ApptSelected: " + _ApptID + " " + _ApptID.GetType());
AppointmentsView = await AppointmentData.RetrieveApptByID(_ApptID);
foreach(var item in AppointmentsView)
{
DogsComboSelected = item.DogsID;
Console.WriteLine("Dogs "+DogsComboSelected+ " "+DogsComboSelected.GetType());
VetComboSelected = item.ApptVetID;
Console.WriteLine("Vets "+VetComboSelected+ " "+VetComboSelected.GetType());
CalendarSelected = item.ApptDateParsed;
Console.WriteLine("Calendar " + CalendarSelected + " " + CalendarSelected.GetType());
TimePickerSelected = item.ApptTimeParsed;
Console.WriteLine("Time " + TimePickerSelected + " " + TimePickerSelected.GetType());
ProcedureTextBox = item.ApptServices;
OnPropertyChanged("ProcedureTextBox");
Console.WriteLine("Procedure " + ProcedureTextBox + " " + ProcedureTextBox.GetType());
}
}
This is view-A (AppointmentsEditView) where the fields should be updating
<UserControl x:Class="MBR2.Views.Appointments.AppointmentsEditView"
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:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
mc:Ignorable="d"
xmlns:views="clr-namespace:MBR2.Views.Appointments"
xmlns:appointments="clr-namespace:MBR2.ViewModels.Appointments" d:DataContext="{d:DesignInstance Type=appointments:AppointmentsEditVM}"
d:DesignHeight="650" d:DesignWidth="800"
x:Name="ApptEditView">
<UserControl.Resources>
<DataTemplate DataType="{x:Type appointments:AppointmentsEditVM}">
<views:AppointmentsEditView />
</DataTemplate>
</UserControl.Resources>
<Grid>
<Label x:Name="DogLabel"
Content="Dog: "
HorizontalAlignment="Left"
Height="30"
Margin="25, 30, 0, 0"
VerticalAlignment="Top"
Width="80"/>
<ComboBox x:Name="DogsComboBox"
ItemsSource="{Binding DogsComboItemsSource}"
SelectedValue="{Binding DogsComboSelected,
Mode=TwoWay}"
SelectedValuePath="Key"
DisplayMemberPath="Value"
HorizontalAlignment="Left"
Height="40"
Margin="120,30,0,0"
VerticalAlignment="Top"
Width="190"/>
<Label x:Name="VetLabel"
Content="Vet:"
HorizontalAlignment="Left"
Height="30"
Margin="25,97,0,0"
VerticalAlignment="Top"
Width="80"/>
<ComboBox x:Name="VetComboBox"
ItemsSource="{Binding VetComboItemsSource}"
SelectedValue="{Binding VetComboSelected,
Mode=TwoWay,
IsAsync=True}"
SelectedValuePath="Key"
DisplayMemberPath="Value"
HorizontalAlignment="Left"
Height="40"
Margin="120,99,0,0"
VerticalAlignment="Top"
Width="190"/>
<Label x:Name="ProcedureLabel"
Content="Procedure:"
HorizontalAlignment="Left"
Height="30" Margin="25,180,0,0"
VerticalAlignment="Top"
Width="80"/>
<TextBox x:Name="ProcedureTextBox"
Text="{Binding ProcedureTextBox,
Mode=TwoWay}"
HorizontalAlignment="Left"
Height="140"
Margin="120,180,0,0"
TextWrapping="Wrap"
VerticalAlignment="Top"
Width="280"/>
<Label x:Name="DateLabel" Content="Date:" HorizontalAlignment="Left" Height="30" Margin="25,370,0,0" VerticalAlignment="Top" Width="80"/>
<Calendar x:Name="DateCalendar"
SelectedDate="{Binding CalendarSelected,
Mode=TwoWay}"
HorizontalAlignment="Left" Height="180" Margin="65,349,0,0" VerticalAlignment="Top" Width="280"/>
<Label x:Name="TimeLabel" Content="Time:" HorizontalAlignment="Left" Height="30" Margin="345,370,0,0" VerticalAlignment="Top" Width="80"/>
<xctk:TimePicker x:Name="TimePickerSelected"
Value="{Binding TimePickerSelected,
Mode=TwoWay}"
StartTime="06:00"
TimeInterval="00:30"
Margin="400,361,210,159" />
<Button x:Name="AddButton" Content="Add Appointment"
Command="{Binding EditAppointment_Click}"
HorizontalAlignment="Left"
Height="35"
Margin="485,505,0,0"
VerticalAlignment="Top"
Width="120"/>
<Button x:Name="CancelButton"
Content="Cancel"
Command="{Binding Cancel_Click}"
HorizontalAlignment="Left"
Height="35"
Margin="630,507,0,0"
VerticalAlignment="Top"
Width="125"/>
</Grid>
</UserControl>
There are two lists (DogsComboItemsSource & VetComboItemsSource) that generate on compilation, I did not include that code.
EDIT
I ripped out my ViewModelBase (which housed the OnPropertyChanged() method) and re-wrote it barebones in the AppointmentsEditVM. I added a Console.WriteLine(propertyName) line to see what else is going on. I set a breakpoint at the DogsID...NotifyPropertyChanged("DogsID") line. If I select the combobox in View-A, I see the right stuff in the console and the breakpoint hits. if I try and go with View-B's function, the breakpoint gets hit but the propertyName doesn't show up in Console.
I'm very new to WPF and currently learning the concepts of data binding.
my simplified XAML code. besides my problem (below) it works fine - quick and dirty placing of objects via GUI, will be cleaned up once works:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
</Grid>
<Grid Grid.Row="1">
<GroupBox Header="Change Type:" Height="95" Width="100" VerticalAlignment="Top" Margin="270,4,422,0" >
<StackPanel>
<RadioButton x:Name="RbtAdd" HorizontalAlignment="Left" Margin="5" VerticalAlignment="Top" GroupName="modGroup" Checked="ModeRadio_Checked">
<WrapPanel>
<TextBlock Text="Add" Foreground="Green"/>
</WrapPanel>
</RadioButton>
<RadioButton x:Name="RbtPull" HorizontalAlignment="Left" Margin="5" VerticalAlignment="Top" GroupName="modGroup" Checked="ModeRadio_Checked">
<WrapPanel>
<TextBlock Text="Pull" Foreground="Blue"/>
</WrapPanel>
</RadioButton>
<RadioButton x:Name="RbtModify" HorizontalAlignment="Left" Margin="5" VerticalAlignment="Top" GroupName="modGroup" Checked="ModeRadio_Checked">
<WrapPanel>
<TextBlock Text="Modify" Foreground="DarkGray"/>
</WrapPanel>
</RadioButton>
</StackPanel>
</GroupBox>
<TextBlock x:Name="txtCurStock" HorizontalAlignment="Left" Margin="330,181,0,0" TextWrapping="Wrap" Text="{Binding Path=CurrentStock}" VerticalAlignment="Top" FontSize="20" FontWeight="Bold" TextAlignment="Center"/>
<Label Content="Current stock:" HorizontalAlignment="Left" Margin="289,156,0,0" VerticalAlignment="Top"/>
<Label x:Name ="lblOperation" Content="Stock to Pull:" HorizontalAlignment="Left" Margin="507,156,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="txtEntry" HorizontalAlignment="Left" Height="32" Margin="489,181,0,0" TextWrapping="Wrap" TextAlignment="Center" Text="{Binding Path=ModEntry}" VerticalAlignment="Top" Width="120" FontSize="20" FontWeight="Bold" TextChanged="TxtEntry_TextChanged"/>
<Label Content="New Stock" HorizontalAlignment="Right" Margin="714,156,0,0" VerticalAlignment="Top" Width="68"/>
<TextBlock Text="{Binding Path=NewStock}" HorizontalAlignment="Right" Margin="0,186,10,0" TextAlignment="Center" TextWrapping="Wrap" VerticalAlignment="Top" FontSize="20" FontWeight="Bold" Width="68"/>
<TextBox x:Name="txtComment" HorizontalAlignment="Left" Height="86" Margin="289,233,0,0" TextWrapping="Wrap" Text="{Binding Path=ModEntry}" VerticalAlignment="Top" Width="493"/>
<Label Content="Comment:" HorizontalAlignment="Left" Margin="289,207,0,0" VerticalAlignment="Top"/>
<TextBlock x:Name ="txtModindicator" HorizontalAlignment="Left" Margin="433,181,0,0" TextWrapping="Wrap" Text="-" FontSize="20" FontWeight="Bold" VerticalAlignment="Top"/>
<TextBlock x:Name ="txtResindicator" HorizontalAlignment="Left" Margin="663,182,0,0" TextWrapping="Wrap" Text="=" FontSize="20" FontWeight="Bold" VerticalAlignment="Top"/>
</Grid>
</Grid>
now the shortened c# code:
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace SomeWPF
{
/// <summary>
/// Interaction logic for ModifyWindow.xaml
/// </summary>
public partial class MainWindow : INotifyPropertyChanged
{
public enum Mymode
{
add,
pull,
modify
}
public Mymode mode;
public MainWindow()
{
DataContext = this;
InitializeComponent();
CurrentStock = 5;
RbtPull.IsChecked = true;
ModEntry = 1;
}
private void ModeRadio_Checked(object sender, RoutedEventArgs e)
{
if (sender != null)
{
if (sender.Equals(RbtAdd))
{
mode = Mymode.add;
txtModindicator.Text = "+";
txtComment.Text = "Add";
lblOperation.Content = "Stock to Add:";
}
else if (sender.Equals(RbtPull))
{
mode = Mymode.pull;
txtModindicator.Text = "-";
txtComment.Text = "Pull";
lblOperation.Content = "Stock to Pull:";
}
else
{
mode = Mymode.modify;
txtModindicator.Text = "~";
lblOperation.Content = "Corrected Quantity:";
txtComment.Text = "Mod";
}
TxtEntry_TextChanged(sender, null);
}
}
private void TxtEntry_TextChanged(object sender, TextChangedEventArgs e)
{
if (mode == Mymode.add)
{
NewStock = CurrentStock + ModEntry;
}
else if (mode == Mymode.pull)
{
NewStock = CurrentStock - ModEntry;
}
else
{
NewStock = ModEntry;
}
}
#region Binding Stuff
private int _newstock;
public int NewStock
{
get
{
return _newstock;
}
set
{
if (_newstock != value)
{
_newstock = value;
OnPropertyChanged();
}
}
}
private int _modentry;
public int ModEntry
{
get
{
return _modentry;
}
set
{
if (_modentry != value)
{
_modentry = value;
OnPropertyChanged();
}
}
}
private int _currentstock;
public int CurrentStock
{
get
{
return _currentstock;
}
set
{
if (_currentstock != value)
{
_currentstock = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
So this window is a popup in a little program for an inventory storage for the users to enter movements of the inventory.
everything loads fine so far and I now wanted to do the quite simple calculation part. with "old" winforms c# you'd just take the values and update the text property of the result "manually" but of course we (I) want to learn new stuff and do stuff with data binding.
The code also does the calculation, but the trigger is somehow not what I want.
let's say current stock is 5
when window loads, the mode is set to Pull (RbtPull) and the user entry (Binding to ModEntry) is set to 1 via code. The NewStock therefore should be 4 which displays correctly. (yey)
Also the comment field (for debugging for now) displays the ModEntry value 1.
so far so good.
Now I enter 3 in the Stock to Pull field, but nothing happens. (I want it to react "realtime"). The new Stock is still displayed as 4, the comment is still displayed as 1.
When I leave the field (and click into the comment field) - the property change is detected and the Comment Field shows also 3 (=ModEntry) - so it's not "realtime" but only triggers when the field is losing focus, but that would be also acceptable.
The real problem is: The new Stock stays 4 and does not calculate.
Now when I enter the Stock to Pull field again and change the value to let's say 5, the New Stock field updates to 2 (so to the value I entered before 5-3=2)
Overwriting the field with again 5 will change the new Stock to 0.
So it's always "one step behind".
From what I have found i have an idea, that I need some kind of Binding Converter instead of my method of calculating things, but I can't really find anything suitable and am not familiar enough yet with data binding. Have tried out some things already directly in the binding variable code but none worked. If anyone could hint me in the right direction I'd be very thankful. (don't need a silver plate solution but just an idea what way to search (e.g. if the sort of binding I use makes sense at all or if there's something I have to add etc.).
Thanks a lot!
PS: of course if someone is motivated to give a silver plate solution I'd also be grateful. :) - and sorry for the bad english, no native speaker.
#nosale 's second link (see comments) provided the answer to the Problem.
Setting both XAML fields txtEntry and the Result field to UpdateSourceTrigger=PropertyChanged solved the issue.
so the correct block Looks like this now without changes to the c# code:
<Label Content="Current stock:" HorizontalAlignment="Left" Margin="289,156,0,0" VerticalAlignment="Top"/>
<Label x:Name ="lblOperation" Content="Stock to Pull:" HorizontalAlignment="Left" Margin="507,156,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="txtEntry" HorizontalAlignment="Left" Height="32" Margin="489,181,0,0" TextWrapping="Wrap" TextAlignment="Center" Text="{Binding Path=ModEntry, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120" FontSize="20" FontWeight="Bold" TextChanged="TxtEntry_TextChanged"/>
<Label Content="New Stock" HorizontalAlignment="Right" Margin="714,156,0,0" VerticalAlignment="Top" Width="68"/>
<TextBlock Text="{Binding Path=NewStock, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Right" Margin="0,186,10,0" TextAlignment="Center" TextWrapping="Wrap" VerticalAlignment="Top" FontSize="20" FontWeight="Bold" Width="68"/>
<TextBox x:Name="txtComment" HorizontalAlignment="Left" Height="86" Margin="289,233,0,0" TextWrapping="Wrap" Text="{Binding Path=ModEntry}" VerticalAlignment="Top" Width="493"/>
<Label Content="Comment:" HorizontalAlignment="Left" Margin="289,207,0,0" VerticalAlignment="Top"/>
Reason is, that textboxes have a Default UpdateSourceTrigger=LostFocus and not PropertyChanged to prevent updates with user having entered typos.
something new learned: WPF is cool and automatically handles non plausible values like null or strings and marks the field red! :)
thanks again for the links!
I am trying to make a RichTextBox with a FlowDocument that I can insert text at the caret position. I can add text to the end of the document. I think I am missing something in my setup to that allows my VM to access the Flowocument or I am setting it up wrong. If I create a FlowDocument in my VM and try to set my RichTextBox to it I get an error that my MyEditor (RichTextBox) does not exist. I can add text to the RichTextBox using what I call the AddItemBtn from a ListBox so at least that much works.
My question is "How should I set my RichTextBox/FlowDocument up?
XAML code
<Window x:Class="Scripter.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Scripter.ViewModels"
xmlns:wpftoolkit="clr-namespace:Xceed.Wpf.Toolkit;assembly=Xceed.Wpf.Toolkit"
Title="MainWindow" Height="350" Width="725">
<Grid HorizontalAlignment="Stretch">
<Grid HorizontalAlignment="Stretch" Height="72" Margin="10,14,0,0" VerticalAlignment="Top" Width="auto">
<WrapPanel HorizontalAlignment="Left" Height="50" Margin="10,0,0,0" VerticalAlignment="Top">
</WrapPanel>
<Button x:Name="OpenFilesBtn" Content="Open" HorizontalAlignment="Left" Margin="15,10,0,0" VerticalAlignment="Top" Width="75" Command="{Binding OpenFileBtn}"/>
<Button x:Name="SavefilesBtn" Content="Save" HorizontalAlignment="Left" Margin="104,10,0,0" VerticalAlignment="Top" Width="75" Command="{Binding SaveFileBtn}"/>
<TextBlock x:Name="OpenFile" Text="{Binding OpenFile,Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Margin="15,37,0,0" VerticalAlignment="Top" Width="353"/>
<ComboBox x:Name="TipsBtn" SelectedIndex="0" ItemsSource="{Binding Path=Tabs, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Path=SelectedOption}" HorizontalAlignment="Left" Margin="538,10,0,0" VerticalAlignment="Top" Width="120"/>
<Button x:Name="AddItemBtn" Content="Add Item" HorizontalAlignment="Left" Margin="417,10,0,0" VerticalAlignment="Top" Width="100" Command="{Binding AddItemBtn}" CommandParameter="{Binding ElementName=AddItemList,Path=SelectedItem}"/>
</Grid>
<Grid Margin="10,100,10,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<RichTextBox Grid.Column="0" x:Name="MyEditor" SelectionChanged="MyEditor_SelectionChanged" ScrollViewer.VerticalScrollBarVisibility="Auto" Margin="0" Height="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" IsDocumentEnabled="True" AcceptsTab="True" AcceptsReturn="True" >
<RichTextBox.Resources>
<Style TargetType="{x:Type Paragraph}">
<Setter Property="Margin" Value="0" ></Setter>
<Setter Property="FontSize" Value="15"></Setter>
</Style>
</RichTextBox.Resources>
<FlowDocument >
<Paragraph >
<Run Text="{Binding TestText}" ></Run>
</Paragraph>
</FlowDocument>
</RichTextBox>
<ListBox x:Name="AddItemList" Grid.Column="1" Width="Auto" Height="Auto" ItemsSource="{Binding Path=OptionsToChoose}" SelectedItem="ItemSelected">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="TextSelected" Text="{Binding Description}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
VM code that has the issue
public ScripterViewModel()
{
ScripterModel scripterModel = new ScripterModel();
ObservableCollection<string> tabsChoice = new ObservableCollection<string>();
tabsChoice.Add("Tabs");
tabsChoice.Add("Buttons");
Tabs = tabsChoice;
this.OpenFileBtn = new DelegateCommand(chooseFile, canChooseFile).ObservesProperty(() => OpenFile);
this.SaveFileBtn = new DelegateCommand(saveFile, canSaveFile).ObservesProperty(() => SaveFile);
this.AddItemBtn = new DelegateCommand<Tabbed>(addItem);
FlowDocument flowDoc = new FlowDocument();
Paragraph p = new Paragraph(new Run("new paragraph"));
flowDoc.Blocks.Add(new Paragraph(new Run("Paragraph 1")));
flowDoc.Blocks.Add(p);
//MyEditor = flowDoc;
}
public void MyEditor_SelectionChanged(object sender, RoutedEventArgs e)
{
// TextRange tempRange = new TextRange(MyEditor.Document.ContentStart, MyEditor.Selection.Start);
MessageBox.Show("Selection Changed");
}
private string _testText;
public string TestText
{
get
{
return _testText;
}
set
{
string _temp;
_temp = _testText + value;
SetProperty(ref _testText, value);
}
}
Hey I am new to WPF and MVVM but I'll give my best to help you. So don't blame me if I'm wrong.
1. Set Window.DataContext
First of all you have to tell your View where it can get the data from.
This can be done by adding this code to your View.xaml:
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
But make sure your namespace variable (here "local") points to your ViewModels.
xmlns:local="clr-namespace:Client.ViewModel"
This for ex. points to my ViewModel folder.
2. Define a OnPropertyChanged method
Your View won't know if you have modified a variable. So you need a method to notify your View about the changes.
First of all implement the interface INotifyPropertyChanged to your ViewModel.
public class MainViewModel : ViewModelBase, INotifyPropertyChanged
Now add this code:
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
3. Use OnPropertyChanged
So now you have a method to tell your View that a variable has changed but how do you use it?
To explain this to you I'll use your FlowDocument flowDoc.
So let's begin by defining your FlowDocument setting up :
private FlowDocument _flowDoc;
Now lets write a getter & setter for flowDoc:
public FlowDocument FlowDoc
{
get
{
return _flowDoc;
}
set
{
_flowDoc = value;
}
}
Now it's time to use our OnPropertyChanged method which we created in 2.
In the setter section you want to add the following Code:
OnPropertyChanged("variable");
Your result should now look like this:
public FlowDocument FlowDoc
{
get
{
return _flowDoc;
}
set
{
_flowDoc = value;
OnPropertyChanged("FlowDoc");
}
}
Important: remember to apply this to all your variables!
4. Use MVVM pattern right
In MVVM you have a Model a View and a ViewModel.
The Model is for your data so if possible don't store data in your ViewModel instead use a data class for ex.
You may have a look at this and/or this.
As I said in the beginning I'm new to all of this but i hope it helps
you. Feel free to ask.
Im quite new to silverlight and windows 7 phone development. And I'm not sure what I missed but apparantly I missed something because it's not working as intended.
My goal, is to show a list of creatures, with only their name and hitpoints. But the whole Text={Binding}-stuff apparently doesn't work. So I wonder if any of you guys could help me with this.
When i say it dosen't work, its because the data is in the creature list, but not in the page/textblocks - it shows the right amount of creatures, but just not the data.
XAML
<phone:PhoneApplicationPage
x:Class="RPG_Assistent.Pages.DamageTrackerPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
shell:SystemTray.IsVisible="True">
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<!--<Button Content="Damage" Height="72" HorizontalAlignment="Left" Margin="0,618,0,0" Name="btnDamage" VerticalAlignment="Top" Width="160" Click="btnDamage_Click" />
<TextBox Height="72" HorizontalAlignment="Left" Margin="158,618,0,0" Name="txtDamage" Text="" VerticalAlignment="Top" Width="286" KeyUp="NumericOnlyTextBox_KeyUp"></TextBox>-->
<ListBox ItemsSource="{Binding creatureList}" Height="500" HorizontalAlignment="Center" Margin="6,6,0,0" Name="listBox1" VerticalAlignment="Top" Width="400">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Width="400" Height="120" >
<Button.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="80" Width="200">
<StackPanel Orientation="Vertical" Height="40">
<TextBlock Width="100" FontSize="22" Text="Name:" Height="40"/>
<TextBlock Width="100" Text="{Binding Name}" Height="40"/>
</StackPanel>
<StackPanel Orientation="Vertical" Height="40">
<TextBlock Width="100" FontSize="22" Text="Hitpoints:" Height="40"/>
<TextBlock Width="100" Text="{Binding HitPoints}" Height="40"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</Button.ContentTemplate>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
<!--Sample code showing usage of ApplicationBar-->
<!--<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
<shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Button 1"/>
<shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Button 2"/>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text="MenuItem 1"/>
<shell:ApplicationBarMenuItem Text="MenuItem 2"/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>-->
CS - called when page is done loading stuff in. ( called after InitializeComponent(); on my DamageTracker Page )
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
namespace RPG_Assistent.Pages
{
public partial class DamageTrackerPage : PhoneApplicationPage
{
List<Models.Creature> creatureList { get; set; }
public DamageTrackerPage()
{
InitializeComponent();
creatureList = new List<Models.Creature>();
#region ApplicationTitle Setup
ApplicationTitle.Text = Constants.AppName;
ApplicationTitle.TextAlignment = Constants.AppName_TextAlignment;
ApplicationTitle.FontSize = Constants.AppName_FontSize;
ApplicationTitle.FontWeight = Constants.AppName_FontWeight;
#endregion
//SetInputScope(txtDamage);
LoadCreatures();
DataContext = this;
}
public void LoadCreatures()
{
string name;
for (int i = 0; i < 10; i++)
{
name = "Monster " + i + 1;
creatureList.Add(new Models.Creature(name));
}
}
public void btnDamage_Click(object sender, RoutedEventArgs e)
{
}
#region textbox control - makes numeric only
private void SetInputScope(TextBox textBoxControl)
{
InputScopeNameValue digitsInputNameValue = InputScopeNameValue.TelephoneNumber;
textBoxControl.InputScope = new InputScope()
{
Names = {
new InputScopeName()
{
NameValue = digitsInputNameValue
}
}
};
}
private void MaskNumericInput(TextBox textBoxControl)
{
string[] invalidCharacters = { "*", "#", ",", "(", ")", "x", "-", "+", " ", "#", "." };
for (int i = 0; i < invalidCharacters.Length; i++)
{
textBoxControl.SelectionStart = textBoxControl.Text.Length;
}
}
private void NumericOnlyTextBox_KeyUp(object sender, KeyEventArgs e)
{
MaskNumericInput((TextBox)sender);
}
#endregion
}
}
CS - Creature class, is placed in "Models" folder - because i thought i would be clever
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;
namespace RPG_Assistent.Models
{
public class Creature
{
public string Name { get; set; }
public int HitPoints { get; set; }
public string Type { get; set; }
public Creature(string name)
{
this.Name = name;
this.HitPoints = 0;
this.Type = "Images/mob.jpg";
}
public void Damage(int damage)
{
HitPoints += damage;
}
public void Bloodied()
{
switch (this.Type)
{
case "Images/mob.jpg":
this.Type = "Images/mobhurt.jpg";
break;
case "Images/mobhurt.jpg":
this.Type = "Images/mob.jpg";
break;
}
}
}
}
Since you are binding to a list of Creatures, you do not need to put Creature.Name. You should be able to change it to Text={Binding Name} and Text={Binding Hitpoints}
It looks like it should be Text={Binding Name} or Text={Binding HitPoints}
EDIT: but, Text={Binding Path=Name} or Text={Binding Path=HitPoints} would work too.
EDIT 2: Sorry, I didn't notice your comment. I don't have VS in the computer, so I can't try it myself, but try setting the DataType on the DataTemplate to Creature.
Update your binding to the following. I´ve dropped Creature, from the binding path. Then it should work
<StackPanel Orientation="Vertical" Height="40">
<TextBlock Width="100" FontSize="22" Text="Name:" Height="40"/>
<TextBlock Width="100" Text="{Binding Path=Name}" Height="40"/>
</StackPanel>
<StackPanel Orientation="Vertical" Height="40">
<TextBlock Width="100" FontSize="22" Text="Hitpoints:" Height="40"/>
<TextBlock Width="100" Text="{Binding Path=HitPoints}" Height="40"/>
</StackPanel>
You always bind to the DataContext with direct bindings, and when setting the ItemsSource to a list, the DataContext becomes each item in the list for each row it will represent. So your thinking here is completely correct!
However: ContentControl act the same. When you set the Content of a ContentControl you basicly override the DataContext for the Content. The DataContext is thus set as your StackPanel, and it will render itself as your StackPanel, but you will also try to Bind to your StackPanel, and not to your Creature object anymore.
So you might want to do this:
Move your content StackPanel to a DataTemplate, set this DataTemplate as ContentTemplate on your Button and set the Content of the Button to a Binding of your Creature object, like so:
<Button Width="400" Height="120" Content="{Binding}">
<Button.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="80" Width="200">
<StackPanel Orientation="Vertical" Height="40">
<TextBlock Width="100" FontSize="22" Text="Name:" Height="40"/>
<TextBlock Width="100" Text="{Binding Path=Name}" Height="40"/>
</StackPanel>
<StackPanel Orientation="Vertical" Height="40">
<TextBlock Width="100" FontSize="22" Text="Hitpoints:" Height="40"/>
<TextBlock Width="100" Text="{Binding Path=HitPoints}" Height="40"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</Button.ContentTemplate>
</Button>
My preferred way of handling these occasions I set up a collection for the view. It would look something like this
public class CreatureList : ObservableCollection<Creature>
{
// at least implement the constructor
}
After that you can use the new collection class in your window XAML definition.
<ResourceDictionary>
<local:CreatureList x:Key="creatures" />
</ResourceDictionary>
The definition of the local namespace has to be set to the assembly namespace where the class CreatureList would be found. After that you can use the defined list in your listbox definition.
<ListBox Name="creatureListBox" ItemsSource="{Binding Source={StaticResource creatures}}">
<!-- Template definition for each entry -->
</ListBox>
To use these objects in your window class, you have to set up some attributes and associate them to the specified entry.
public partial class DamageTrackerPage : PhoneApplicationPage
{
private readonly CreatureList creatureList;
}
In the constructor of the class you bind the attribute to the specified XAML definition.
public DamageTrackerPage() {
InitializeComponent();
creatureList = FindResource("creatures") as CreatureList;
}
Now when you add entries to the list or remove entries from it the changes will be updated to your window automatically.
This is at least the way I'm doing it in WPF, but I'm sure for WinPhone apps that should be the same.
Basically, I'm trying to take information entered by the user on one page and print it out to another page via a "printer friendly" version, or report, of something. I have a MainPage.xaml that is, as the name suggests, my main page, but in a window there is the subpage AdCalculator.xaml where the user enters the information and PrintEstimate.xaml that is navigated to via a button on AdCalculator.
I would like to be able to transfer the information entered in the textboxes from AdCalculator and print it out via text blocks in PrintEstimate. So in order to do that I have the following code:
Views.AdCalculator AdCalc = new Views.AdCalculator();
string PrintCompanyName = AdCalc.CompanyName;
string PrintContactName = AdCalc.txt_CustomerName.Text;
string PrintBillingAddress1 = AdCalc.txt_BillingAddress.Text;
string PrintBillingAddress2 = AdCalc.txt_BillingAddressLine2.Text;
string PrintPhoneNumber = AdCalc.txt_PhoneNumber.Text;
string PrintNumOfAds = AdCalc.txt_NumofAds.Text;
string PrintRateOfPlay = AdCalc.Cmb_Rate.SelectedValue.ToString();
string PrintNumOfMonths = AdCalc.txt_NumofMonths.Text;
string PrintTotalDue = AdCalc.txt_InvoiceSummary_TotalDue.Text;
PrintEstimate PrintEstimatePage = new PrintEstimate();
PrintEstimatePage.txt_CompanyName.Text = PrintCompanyName;
PrintEstimatePage.txt_CustomerName.Text = PrintContactName;
PrintEstimatePage.txt_BillingAddress.Text = PrintBillingAddress1;
PrintEstimatePage.txt_BillingAddressLine2.Text = PrintBillingAddress2;
PrintEstimatePage.txt_PhoneNumber.Text = PrintPhoneNumber;
PrintEstimatePage.txt_InvoiceSummary_NumofAds.Text = PrintNumOfAds;
PrintEstimatePage.txt_InvoiceSummary_RateofPlay.Text = PrintRateOfPlay;
PrintEstimatePage.txt_InvoiceSummary_NumOfMonths.Text = PrintNumOfMonths;
PrintEstimatePage.txt_EstimateTotal.Text = PrintTotalDue;
Only problem is, when I instantiate the new AdCalculator page, it clears the values, so nothing is actually retained as far as user-input goes. Following a lead from a colleague, I believe all I need to do is change the line
Views.AdCalculator AdCalc = new Views.AdCalculator();
to
Views.AdCalculator AdCalc = (AdCalculator)Application.OpenForms["AdCalculator"];
except the "Apllication.OpenForms" doesn't register. I know there are a lot of differences in the way C# code-behind is laid out for silverlight applications, so I didn't know if there was an equivalent that anyone knew about to "Application.OpenForms" that would help solve my issue or if there was any other way to go about getting my task done.
If I understand your question correctly you simply want to get some user input and display it.
I suggest you start by defining a class that will represent the data you are inputting,
for example:
public class Customer
{
public string ContectName { get; set; }
public string BillingAddress1 { get; set; }
public string BillingAddress2 { get; set; }
public string PhoneNumber { get; set; }
public int NumOfAds { get; set; }
public double RateOfPlay { get; set; }
public int NumOfMonths { get; set; }
public double TotalDue { get; set; }
}
On the page where the user inputs data you then create an instance
of this class, either by manually creating an instance and setting
its properties when the user submits (similar to what you are doing in your code)
or use databinding to your advantage (which is what I prefer).
Let's say for example you are inputting data on your MainPage
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = new Customer();
}
Now you can bind the controls. Let's say you're using a grid:
<Grid x:Name="LayoutRoot" Background="White">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<sdk:Label Content="Billing Address 1:" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="0" Height="23" HorizontalAlignment="Left" Margin="3" Name="billingAddress1TextBox" Text="{Binding Path=BillingAddress1, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
<sdk:Label Content="Billing Address 2:" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="3" Name="billingAddress2TextBox" Text="{Binding Path=BillingAddress2, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
<sdk:Label Content="Contect Name:" Grid.Column="0" Grid.Row="2" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="3" Name="contectNameTextBox" Text="{Binding Path=ContectName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
<sdk:Label Content="Num Of Ads:" Grid.Column="0" Grid.Row="3" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="3" Height="23" HorizontalAlignment="Left" Margin="3" Name="numOfAdsTextBox" Text="{Binding Path=NumOfAds, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
<sdk:Label Content="Num Of Months:" Grid.Column="0" Grid.Row="4" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="4" Height="23" HorizontalAlignment="Left" Margin="3" Name="numOfMonthsTextBox" Text="{Binding Path=NumOfMonths, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
<sdk:Label Content="Phone Number:" Grid.Column="0" Grid.Row="5" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="5" Height="23" HorizontalAlignment="Left" Margin="3" Name="phoneNumberTextBox" Text="{Binding Path=PhoneNumber, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
<sdk:Label Content="Rate Of Play:" Grid.Column="0" Grid.Row="6" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="6" Height="23" HorizontalAlignment="Left" Margin="3" Name="rateOfPlayTextBox" Text="{Binding Path=RateOfPlay, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
<sdk:Label Content="Total Due:" Grid.Column="0" Grid.Row="7" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="7" Height="23" HorizontalAlignment="Left" Margin="3" Name="totalDueTextBox" Text="{Binding Path=TotalDue, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
</Grid>
</Grid>
When the user clicks the submit button, you can use something like this:
private void Button_Click(object sender, RoutedEventArgs e)
{
var currentCustomer = this.DataContext as Customer;
var previewWindow = new PrintPreviewWindow(currentCustomer);
previewWindow.Show();
}
For this to work you'll need to have a Silverlight ChildWindow like this:
public partial class PrintPreviewWindow : ChildWindow
{
public PrintPreviewWindow(Customer customer)
{
InitializeComponent();
this.DataContext = customer;
}
private void OKButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = false;
}
}
So your MainPage creates a new instance of the PrintPreviewChildWindow (could be a page as well if you prefer) and passes along the customer instance. The ChildWindow can then do whatever it wants with it. When the ChildWindow closes, you'll probably want to empty the input page, you can do this by simply setting the data context again:
this.DataContext = new Customer();
I'm guessing this is what you are looking for.
Try to get into the whole data binding stuff, it will save you lots and lots of lines of code. And let us know if this answers your question or if you have more :-)