Want to see if someone could help clear this up for me? Is there an advantage to DataBinding from the XAML Element to a value within the ViewModel(ex:1), or from the CodeBehind(ex:2) back to the Element like... HostName.Text?
<TextBlock Text="{Binding HostName}" /> --- (ex:1)
<TextBlock Name="HostName" /> --- (ex:2)
POGO
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Task;
namespace AppName.Models
{
public class Contact
{
[Key]
public int Id {get; set;}
public string Team { get; set;}
public string FirstName { get; set;}
public string LastName { get; set;}
public string Phone { get; set;}
public string Email { get; set;}
public string Role { get; set;}
public string DisplayName => $"[LastName}, {FirstName}";
}
}
The simple answer is that it depends on your application and needs. When you're building a small application, using code behind or data binding to view models doesn't make much difference. It's easy to understand the flow and when to make updates. But as your app complexity goes up and your need to test your code, then you start to use patterns that makes your code more maintainable and testable. That's where the MVVM pattern came from.
Testing code in your code behind file is harder than just testing your business logic in your ViewModel class, and ensuring it works as expected.
Your example above is kind of simplistic because it's a TextBlock that only displays text and doesn't take input. A TextBox is a better example for binding since data can change in the view model or in the UI. Binding lets you back the displayed text with a property, so changes from any direction update the model property and UI automatically.
<TextBox x:Name="Entry" Text="{Binding SelectedValue , Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
ViewModel:
public class CatalogViewModel : BindableBase
{
private string selectedValue;
public string SelectedValue
{
get { return selectedValue; }
set { SetProperty<string>(ref selectedValue, value); }
}
...
}
The alternative to that is a lot of code in the code-behind file to keep things in sync between the TextBox and data elements.
<TextBox x:Name="Entry2" TextChanged="Entry2_TextChanged" />
Code behind:
private string entryText;
public string EntryText
{
get { return entryText; }
set
{
if (value != entryText)
{
entryText = value;
Entry2.Text = entryText;
}
}
}
private void Entry2_TextChanged(object sender, TextChangedEventArgs e)
{
entryText = Entry2.Text;
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
// initialize controls
EntryText = "Default";
}
You've now coupled your business logic with the page layout and changes to either will cause a lot of changes. And testing the behavior of your code is harder to write and harder to mock.
And it just gets more complicated with multiple input controls and more complex controls, like ListViews and GridViews.
You should read up on MVVM if you're interested in the benefits of using view models and databinding to them: https://msdn.microsoft.com/en-us/magazine/dd419663.aspx.
Related
I'm doing refactoring for our app. We currently have 2 languages supported, where the logic is sitting inside TranslationService, injected through DI container (using Prism if matters) into View models.
In order to bind the translation to the text property there is tons of properties in the view model, e.g.
public string SomeText => _translationService.GetTranslation("someText");
public string AnotherText => _translationService.GetTranslation("notherText");
And the binding is happening as usual
<TextBlock Text="{Binding SomeText}" HorizontalAlignment="Center" />
Is there a way to reduce those properties? For example to bind the Text property to the GetTranslation method with a parameter?
I've seen how to use ObjectDataProvider but this doesn't really help me out, because the method parameters are hard-coded as per my understanding.
You may declare a helper class with a single indexer property like
public class Translation
{
private readonly TranslationService translationService;
public Translation(TranslationService service)
{
translationService = service;
}
public string this[string key]
{
get { return translationService.GetTranslation(key); }
}
}
which would be used as a single property in your view model:
public class ViewModel
{
public ViewModel()
{
Translation = new Translation(_translationService);
}
public Translation Translation { get; }
}
You would bind it like this:
<TextBlock Text="{Binding Translation[someText]}"/>
I've created a Textbox with placeholder text and a clear button. I've implemented it using a view model for the data context, and using a style with target type TextBox. In xaml, using it is pretty simple.
<TextBox DataContext="{Binding NameBox}" Style="{StaticResource placeholder}"/>
The way I've implemented the view model, though, smells funny to me:
public class PlaceholderTextBoxViewModel : NotifiableViewModelBase {
private string text;
public string Text {
get => text;
set {
text = value;
OnTextChange(text);
}
}
public string PlaceholderText { get; set; }
public RelayCommand ClearCommand => new RelayCommand(() => Text = "");
private event Action<string> OnTextChange;
public PlaceholderTextBoxViewModel(ref string text, string placeholderText, Action<string> changeHandler = null) {
OnTextChange = changeHandler ?? (_ => { });
Text = text;
PlaceholderText = placeholderText;
}
}
In case it doesn't smell too bad to you yet, check out how it's used
private string _name;
public string Name {
get => _name;
set {
_name = value;
System.Console.WriteLine(_name); // needed to silence auto prop error
}
}
public PlaceholderTextBoxViewModel NameBox { get; }
// in the constructor...
NameBox = new PlaceholderTextBoxViewModel(ref _name, "Exam Name", t => Name = t);
It definitely doesn't seem right that I need to pass an explicit setter (the changeHandler) to the PlaceholderTextBoxViewModel. It seems, indeed, that the ref I'm passing is never really used (and is only necessary at all -- though not as a ref -- if there is to be pre-existing text in the box).
I've never used refs before and I must be doing something wrong. I have also tried pointing everything that uses the Name property (in the final code excerpt) to the _name field but that doesn't work, the field isn't properly updated, or at least isn't "communicating" its updates (in various uses, CanExecutes are not updated, SearchPredicates are not refreshed, etc). I'm using MVVMLight, and I imagine that changing a field's value doesn't trigger OnPropertyChanged -- if the field's value is even changing at all.
How do I get the ref to work correctly? Am I doing this completely wrong?
I understand that there are other ways to implement this TextBox with its clear command, even in pure MVVM (namely, if I put the ClearCommand in the consuming VM instead of the textbox's VM itself, then the textbox doesn't need to have a VM at all). But I'd really like to know how to make sense of my attempted solution, if only for a better understanding of C# and of refs.
Problem here seems to be an architectural one. MVVM is a tiered architecture that looks like this:
Model -> View Model -> View
Model is the lowest tier, view is the highest. More importantly, each tier does not have any direct visibility into any of the tiers above it.
The problem in your example is that you've broken separation of concerns. You have a parent class creating the PlaceholderTextBoxViewModel, which implies it's in the view model. However, that same class contains a "Name" property, which is actually data that should be in your model layer. Your existing architecture is such that your view model cannot see the data layer, so you've effectively had to set up your own property change notification mechanism, using the ref and delegate, to keep your model and view model synchronized.
Throw it all out, and start again. Your model should contain POCOs, so start with something like this:
public class MyModel
{
public string Name {get; set;}
}
Then, when you create your view model, pass an instance of this class into its constructor so that it has visibility:
public class MyViewModel : NotifiableViewModelBase
{
private MyModel Model;
public MyViewModel(MyModel model) => this.Model = model;
public string Text
{
get => this.Model.Name;
set
{
this.Model.Name = value;
RaisePropertyChanged(() => this.Text);
}
}
// ... etc ....
}
I've stuck to your nomenclature of using "Name" in the model and "Text" in the view model, in practice they're usually the same, but that's up to you. Either way, you still have property change notification in your view model, and it's the view model layer updating the model layer.
Obviously there are lots of variations on this. If you don't want changes to propagate through to your model layer immediately (and there are plenty of cases where you may not want that) then give Text a backing field (_Text) and only do the synchronization at the points your want it to occur. And of course, if you want to go one step further then your model classes could instead implement interfaces, and you can use dependency injection to inject those interfaces into the view model classes instead of giving them access to the actual implementations themselves.
Above all else, keep in mind that the sole purpose of the view model is to prepare the model layer data for consumption by the view. Anything else...data, domain, business logic etc...all of that belongs in your model layer, which shouldn't have any visibility into the view model layer at all.
The ref would only make sense, if you are going to change the value.
In this case you can use the OnTextChange event.
public PlaceholderTextBoxViewModel(ref string text, string placeholderText, Action<string> changeHandler = null)
{
OnTextChange = newValue =>
{
text = newValue; // <- value back to the ref
changeHandler?.Invoke(newValue);
}
Text = text;
PlaceholderText = placeholderText;
}
BTW your solution is somehow much too complicated. Keep the ViewModel as simple and abstract as possible. In this case a simple Name property is enough.
Leave it up the the UI developers which control they use and how they will implement a clear the control logic.
Here an example
ViewModel
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
namespace WpfApp1
{
public class MainViewModel : ReactiveObject
{
[Reactive] public string Name { get; set; }
}
}
View
<Window
x:Class="WpfApp1.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:local="clr-namespace:WpfApp1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<StackPanel
Width="200"
VerticalAlignment="Center">
<Label Content="{Binding Name}" />
<DockPanel>
<Button
DockPanel.Dock="Right"
Content="x"
Width="20"
Click="NameTextBoxClearButton_Click"/>
<TextBox x:Name="NameTextBox"
Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextWrapping="Wrap" />
</DockPanel>
</StackPanel>
</Grid>
</Window>
View CodeBehind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void NameTextBoxClearButton_Click( object sender, RoutedEventArgs e )
{
NameTextBox.Text = string.Empty;
}
}
The simplest solution would be to correct your binding. You don't have to manually forward the data by trying to implement your own change notification system.
Just make sure that your data source always implements INotifyPropertyChanged and properly raises the INotifyPropertyChanged.PropertyChanged event from each property set method. Then bind directly to this properties:
class PlaceholderTextBoxViewModel : INotifyPropertyChanged
{
private string text;
public string Text
{
get => this.text;
set
{
this.text = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The binding syntax allows to reference nested properties.
In your special case, the {Binding} expression must be:
<TextBox DataContext="{Binding NameBox.Text}" />
Now the TextBox.Text value is automatically propagated to the PlaceholderTextBoxViewModel.Text property and the constructor becomes parameterless.
We are a newbie for Xamarin. We are having an issue in binding the response data from a web service to a ListView.
We debugged and we can see the the web service is successfully responding with the data but it never gets populated.
Any ideas?
It's gotta be a small thing that we are missing. We have managed to display a single entry from the data with other views (in other parts of the project) BUT not in IEnumerable<> or List<>
Here's the code:
View - RoundsPage.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:viewModels="clr-namespace:AthlosifyMobile.ViewModels"
x:Class="AthlosifyMobile.Views.RoundsPage">
<ContentPage.BindingContext>
<viewModels:RoundsViewModel />
</ContentPage.BindingContext>
<StackLayout>
<Entry Text="{Binding AccessToken}" />
<Button Command="{Binding GetRoundsCommand}" Text="Get all rounds" />
<Label Text="Rounds: "></Label>
<ListView ItemsSource="{Binding Rounds}" HasUnevenRows="true" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="Round 1:"></Label>
<Label Text="{Binding Name}"></Label>
<Label Text="{Binding DailyHandicap}"></Label>
<Label Text="{Binding PlayedUTC}"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>`
ViewModel - RoundsViewModel.cs :
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Input;
using AthlosifyMobile.Annotations;
using Xamarin.Forms;
using AthlosifyMobile.Services;
using AthlosifyMobile.Models;
namespace AthlosifyMobile.ViewModels
{
public class RoundsViewModel : INotifyPropertyChanged
{
ApiServices _apiServices = new ApiServices();
public event PropertyChangedEventHandler PropertyChanged;
private IEnumerable<Round> _rounds;
public string AccessToken { get; set; }
public IEnumerable<Round> Rounds
{
get
{
return _rounds;
}
set
{
_rounds = value;
OnPropertyChanged();
}
}
public ICommand GetRoundsCommand
{
get
{
return new Command(async() =>
{
Rounds = await _apiServices.GetRoundsAsync(AccessToken);
});
}
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Model - Course.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace AthlosifyMobile.Models
{
public class Round : EntityBase
{
public Guid RoundID { get; set; }
public Guid UserID { get; set; }
public Guid RoundCategoryID { get; set; }
public Guid CourseID { get; set; }
public string Name { get; set; }
public string Notes { get; set; }
public int DailyHandicap { get; set; }
public DateTime PlayedUTC { get; set; }
public RoundCategory RoundCategory { get; set; }
public Course Course { get; set; }
public ICollection<RoundHole> RoundHoles { get; set; }
}
public abstract class EntityBase
{
public DateTime CreatedUTC { get; set; }
public DateTime LastModifiedUTC { get; set; }
}
}
Services - apiservices.cs:
using AthlosifyMobile.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace AthlosifyMobile.Services
{
public async Task<IEnumerable<Round>> GetRoundsAsync(string accessToken)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var json = await client.GetStringAsync("http://localhost:5609/api/Rounds");
var list = JsonConvert.DeserializeObject<IEnumerable<Round>>(json);
return list;
}
}
}
You will need to diagnose whether this is an issue with connecting the View to the ViewModel or whether your Data Service isn't working correctly. Either way, there are a few things you should do to fix this!
Firstly you are using IEnumerable, instead you should be using ObservableCollection<T>. You should always be using ObservableCollection<T> for Binded list views. This is explained in the xamarin docs here (they automatically notify the view when their contents changed & update).
So you should make this change:
public ObservableCollection<Round> Rounds { get; }
Next you should verify that the bindings are correct. I would not recommend your approach of going straight to live data if you aren't familiar with xamarin. Firstly you should try adding some static objects to the view model and trying to bind them!
Disconnect your API code and call a method that creates some of your Round objects. Here is an example method (i use methods like these all the time when designing my ListViews UI).
public RoundsViewModel()
{
Rounds = CreateSampleData();
}
private ObservableCollection<Round> CreateSampleData()
{
ObservableCollection<Round> dummyData = new ObservableCollection<Round>();
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
return dummyData;
}
At this point you will either see items in your ListView, meaning you have an issue with your API code / Implementation of INotifyPropertyChanged. If you don't see anything then you likely have an issue with binding and will need to verify that your view is actually connected to the View Model.
Mvvm Helpers
Seeing some of this code makes me feel very sorry for you, you definitely should looking into using an MVVM helper such as Prism or MVVMCross. I personally use Prism which provides a ViewModelBase which all ViewModels inherit from. This means all of the INotifyPropertyChanged code is hidden away from you (less boilerplate). It also has a dependancy service which means hooking views up to view models is as simple as registering it in the app.cs.
If you are interested in prism, watch this video with Brian Lagunas to see what Prism can do for you!
Update: There are now a few helpful libraries aside from Prism that will help with the MVVM stuff. Refractored.MVVMHelpers and Xamarin.CommunityToolkit both contain an essential object: ObservableRangeCollection.
ALL code using an ObservableCollection should be replaced with ObservableRangeCollection, it is an essential object and really belongs in a microsoft maintained namespace at this point. It creates a performance benefit for updating larger collections & reduces the need for alot of boilerplate when updating the ObservableCollection
I have the error System.StackOverflowException, everytime I run my app. This error is located in the last line of my History class. Below:
public class userHistory
{
public string strTimeDate { get; set; }
public string strUrl { get; set; }
public List<userHistory> lstUserHistory { get; set; }
public userHistory(string timedate, string url)
{
lstUserHistory.Add(new userHistory(timedate, url));
}
}
My MainPage code:
public List<userHistory> lstUserHistory;
public userHistory selectedHistory;
public MainPage()
{
InitializeComponent();
listBox.DataContext = lstUserHistory;
}
private void getHistory(string url)
{
string time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
userHistory usrHistory = new userHistory(time, url);
lstUserHistory.Add(usrHistory);
listBox.DataContext = null;
listBox.DataContext = lstUserHistory;
}
private void listBox_Tap(object sender, GestureEventArgs e)
{
selectedHistory = listBox.SelectedValue as userHistory;
MessageBox.Show(selectedHistory.strUrl);
browserSearch(selectedHistory.strUrl);
}
The XAML
<Grid>
<ListBox ItemsSource="{Binding}" Foreground="RoyalBlue" Name="listBox" TabIndex="10" Tap="listBox_Tap">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="0,329,0,367" >
<TextBlock Text="{Binding strDateTime}" FontSize="15" Margin="51,1,0,1"/>
<TextBlock Text="{Binding strUrl}" FontSize="28" Margin="51,1,0,1"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I think the error might be because of what happens in getHistory. This method can be called muitply times with the same data. All I would like is for the data to be stored in a List, which can easily add new history records, or remove them. Then be displayed simply in a List box.
Thank you in advance :)
If you need any more details please comment and I will be happy to explain in further detail :)
Look at this part of userHistory:
public List<userHistory> lstUserHistory { get; set; }
public userHistory(string timedate, string url)
{
lstUserHistory.Add(new userHistory(timedate, url));
}
There are two bugs here:
The constructor calls itself (boom! stack overflow)
The constructor calls lstUserHistory.Add when lstUserHistory is definitely null
It's not clear why you've got a List<userHistory> in both your MainPage code and in userHistory. I think you need to think more carefully about the data you're modelling, and what really belongs where.
I suspect your userHistory class should actually look more like this:
public sealed class HistoryEntry
{
private readonly DateTime timestamp;
private readonly string url;
public DateTime Timestamp { get { return timestamp; } }
public string Url { get { return url; } }
public HistoryEntry(DateTime timestamp, string url)
{
this.timestamp = timestamp;
this.url = url;
}
}
Notes:
Unless you really need the class to be mutable, make it immutable
Follow .NET naming conventions
Avoid name prefixes which just specify the type
Use the appropriate data type to model what you're interested in - a date/time isn't naturally a string
Decide what the role of this class is - is it an entry, or the whole history? With my changes, it's clear that this is just an entry. If you need to model the whole history as a separate class, do that as well, rather than trying to combine the two.
I'm currently Binding my Listbox to a DTO. Following MVVM patterns, How do I interact with what was selected from the Listbox.
I want something like this [1 being the "FileName"
If (Listbox.Selecteditem[1] == "samplefilename")
{ Messagebox.Show("Files matched"}
But how exactly is that done using MVVM? Do I have to create SelectedValue bindings/properties?
public class FilesDTO : IDTO
{
public int Id { get; set; }
public string FileName { get; set; }
public string FileExtension { get; set; }
public byte[] FileArray { get; set; }
}
You just need to add a "SelectedFileDTO" to your ViewModel and in your XAML, make sure it's set to TwoWay. When it changes in the View, your SelectedFileDTO setter will be hit in your ViewModel.
<ListBox ItemsSource={Binding Files} SelectedItem={Binding SelectedFileDTO, Mode=TwoWay}/>
public FilesDTO SelectedFileDTO
{
get...
set...
}
Yes, you should bind the SelectedValue property to a DependencyProperty. Then whenever you want to use it, it is right there at your disposal. You could also give the DependencyProperty changed events to utilize.