I convert my application from WP 8.1 / W 8.1 to UWP. It includes a timer updating the value of a textbox. Here is the XAML :
Text="{Binding CurrentLocalDateTime, Mode=TwoWay, Converter={StaticResource DateTimeConverter}}"
And the data context :
private DateTime currentLocalDateTime;
public DateTime CurrentLocalDateTime { get { return currentLocalDateTime; } set { currentLocalDateTime = value; OnPropertyChanged("CurrentLocalDateTime"); } }
private void TimerExecution(object sender, object e)
{
CurrentLocalDateTime = DateTime.Now;
}
I'd like to use the new x:Bind bining way, but the control is never updated, with this code :
Text="{x:Bind CurrentLocalDateTime, Mode=TwoWay, Converter={StaticResource DateTimeConverter}}"
Data context :
public DateTime CurrentLocalDateTime { get; set; }
private void TimerExecution(object sender, object e)
{
CurrentLocalDateTime = DateTime.Now;
}
What's wrong ?
Thank you very much for your help.
Regards
When you set CurrentLocalDateTime, there's nothing to notify your UI that it happened. It works in the first case because you're implementing INotifyPropertyChanged and calling OnPropertyChanged with property name.
public DateTime CurrentLocalDateTime
{
get { return currentLocalDateTime; }
set
{
currentLocalDateTime = value;
OnPropertyChanged("CurrentLocalDateTime");
}
}
Related
I am trying to start by looking at a simple clock example.
i am wondering how to I convert a normal clock to MVVM.
Can someone help?
Thanks.
private void startClock()
{
currentTime = DateTime.Now;
DispatcherTimer clocktimer = new DispatcherTimer();
clocktimer.Interval = TimeSpan.FromSeconds(1);
clocktimer.Tick += Clocktimer_Tick;
clocktimer.Start();
}
private void Clocktimer_Tick(object sender, object e)
{
currentTime = DateTime.Now;
updateTimeDisplay(currentTime);
}
private void updateTimeDisplay(DateTime time)
{
Time.Text = time.ToString(#"HH\:mm");
Sec.Text = time.ToString(#"ss");
Date.Text = time.ToString(#"ddd dd-MM-yyyy");
}
Update
I can't post more codes as the system flagged and unable to post.
UWP C# MVVM clock example
You could make string property in the viewmode and bind it with your textblock in the xaml. When you update the this property, it will notify ui update.
For example:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel()
{
startClock();
}
private DateTime _currentTime;
private void startClock()
{
_currentTime = DateTime.Now;
DispatcherTimer clocktimer = new DispatcherTimer();
clocktimer.Interval = TimeSpan.FromSeconds(1);
clocktimer.Tick += Clocktimer_Tick;
clocktimer.Start();
}
private void Clocktimer_Tick(object sender, object e)
{
_currentTime = DateTime.Now;
updateTimeDisplay(_currentTime);
}
private void updateTimeDisplay(DateTime time)
{
Time = time.ToString(#"HH:mm:ss");
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _time;
public string Time
{
get
{
return _time;
}
set
{
_time = value;
this.OnPropertyChanged();
}
}
}
Xaml code
<Page.DataContext>
<local:ViewModel />
</Page.DataContext>
<Grid>
<TextBlock
x:Name="Time"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="25"
Text="{Binding Time}"
TextAlignment="Center" />
</Grid>
For detail mvvm design, please refer data binding in depth document.
View model will be something like this:
public class ClockViewModel : ViewModelBase
{
public ClockViewModel()
{
var _ = UpdateCurrentDateTimeAsync();
}
public DateTime? CurrentDateTime { get; private set; }
private async Task UpdateCurrentDateTimeAsync()
{
while (true)
{
CurrentDateTime = DateTime.Now;
OnPropertyChanged(nameof(CurrentDateTime));
await Task.Delay(1000);
}
}
}
Just set it as the DataContext of your view and bind some visual (e.g. TextBox) to CurrentDateTime property. ViewModelBase is a very basic INotifyPropertyChanged implementation.
So I planning to bind label from two files or more, because I place the label and the cs file in separate way. For example:
SettingServicesPhone.xaml
<Label x:Name="sipLoginStatus"
Width="106"
Height="27"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="{Binding SipLoginStatusContent}"
FontSize="13" />
For the SettingServicePhone.xaml.cs I declared public String sipLoginStatusContent;
And I use Settings.xaml and Setting.xaml.cs as a container of all functions.
I've declared public static SettingsServicesPhone setCall = new SettingsServicesPhone(); on Setting.xaml.cs. And also write get set.
public String SipLoginStatusContent
{
get { return setCall.sipLoginStatusContent; }
set
{
if (setCall.sipLoginStatusContent != value)
{
setCall.sipLoginStatusContent = value;
OnPropertyChanged("SipLoginStatusContent"); // To notify when the property is changed
}
}
}
And here the example of onclick button that I stated on Settings.xaml.cs
public void applyBtn_Click(object sender, RoutedEventArgs e)
{
SipLoginStatusContent = "Logging In";
}
It's work fine if I included them in one file. But seems like it doesn't running if I make it separate. Am I doing it wrong way? Thank you.
Set the DataContext of the window where the Label is defined to an instance of the class where the SipLoginStatusContent property is defined:
public partial class Settings : Window
{
public static SettingsServicesPhone setCall = new SettingsServicesPhone();
public Settings()
{
InitializeComponent();
DataContext = this; //<--
}
public String SipLoginStatusContent
{
get { return setCall.sipLoginStatusContent; }
set
{
if (setCall.sipLoginStatusContent != value)
{
setCall.sipLoginStatusContent = value;
OnPropertyChanged("SipLoginStatusContent"); // To notify when the property is changed
}
}
}
public void applyBtn_Click(object sender, RoutedEventArgs e)
{
SipLoginStatusContent = "Logging In";
}
}
I desire the following:
When clickee a button on the form, I want to handle the events are another class. Thus, the form contains only controls.
It's almost like a MVC pattern: Controller I have a class, and a class RegistrarTrabajador (Model). When controller detects an event of the form, passes the task to the model.
Here the Controller class and the form:
Controller:
namespace RegistroDeUsuarios
{
public class Controller
{
private MainWindow vista;
private RegistrarTrabajador modelo;
public Controller()
{
}
public Controller(MainWindow vista, RegistrarTrabajador modelo)
{
this.vista = vista;
this.modelo = modelo;
}
public void btnRegistrar_Click(Object sender, RoutedEventArgs e)
{
Trabajador trabajador = new Trabajador();
trabajador.setPrimerNombre(vista.txtPrimerNombre.Text);
trabajador.setSegundoNombre(vista.txtSegundoNombre.Text);
trabajador.setPrimerApellido(vista.txtPrimerApellido.Text);
trabajador.setSegundoApellido(vista.txtSegundoApellido.Text);
trabajador.setRangoTrabajador(vista.cboRangoTrabajador.SelectedItem.ToString());
trabajador.setFechaNacimiento(vista.txtFechaNacimiento.Text);
modelo.registrarTrabajador(trabajador);
}
public void btnNuevo_Click(Object sender, RoutedEventArgs e)
{
vista.clean();
}
public void btnSalir_Click(Object sender, RoutedEventArgs e)
{
//Application.Current.Shutdown();
}
}
}
GUI:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
cboRangoTrabajador.Items.Add("Administrador");
cboRangoTrabajador.Items.Add("Vendedor");
cboRangoTrabajador.Items.Add("Contador");
cboRangoTrabajador.Items.Add("Tecnico Mantenimiento");
cboRangoTrabajador.Items.Add("Programador");
cboRangoTrabajador.Items.Add("Analista");
cboRangoTrabajador.SelectedIndex = 0;
}
public void setControlador(Controller controlador)
{
controlador.btnRegistrar_Click(controlador,new RoutedEventArgs());
controlador.btnNuevo_Click(controlador,new RoutedEventArgs());
controlador.btnSalir_Click(controlador,new RoutedEventArgs());
}
public void clean()
{
txtPrimerNombre.Clear();
txtSegundoNombre.Clear();
txtPrimerApellido.Clear();
txtSegundoApellido.Clear();
txtFechaNacimiento.Clear();
cboRangoTrabajador.SelectedItem = "Administrador";
txtPrimerNombre.Focus();
}
}
You don't use MVC in WPF. You use Model-View-ViewModel (MVVM)
And
you don't create or manipulate UI elements in procedural code in WPF. That's what XAML is for.
Please read about DataBinding,
things like this:
trabajador.setPrimerNombre(vista.txtPrimerNombre.Text);
trabajador.setSegundoNombre(vista.txtSegundoNombre.Text);
are horrible and should NEVER be done in WPF.
Also, your code smells like crappy java. Instead of methods like setPrimerNombre() you should really use Properties. WPF has support for two way databinding to properties, so you don't need to do all this piping manually.
To make this clear, here is a small example:
XAML:
<StackPanel>
<TextBox Text="{Binding Model.LastName}"/>
<TextBox Text="{Binding Model.FirstName}"/>
<Button Content="Registrar" Click="Registrar_Click"/>
<Button Content="Clear" Click="Clear_Click"/>
</StackPanel>
Code Behind:
public class MainWindow: Window
{
public MainViewModel ViewModel { get { return DataContext as MainViewModel; } }
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
public void Registrar_Click(object sender, RoutedEventArgs e)
{
ViewModel.Registrar();
}
public void Clear_Click(object sender, RoutedEventArgs e)
{
ViewModel.Clear();
}
}
ViewModel:
public class MainViewModel: ViewModelBase //You should have some ViewModelBase implementing INotifyPropertyChanged, etc
{
private Trabajador _model;
public Trabajador Model
{
get { return _model; }
set
{
_model = value;
NotifyPropertyChange("Model");
}
}
public void Registrar()
{
DataAccessLayer.Registrar(Model);
}
public void Clear()
{
Model = new Trabajador();
}
}
Model:
public class Trabajador: ModelBase //ModelBase Should also implement INotifyPropertyChanged
{
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
NotifyPropertyChanged("LastName");
}
}
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
NotifyPropertyChanged("FirstName");
}
}
//... And so on.
}
Not sure to have fully understand what you mean but i think you want to know what it is the best way of building a WPF application in layers.
If that's right then MVVM pattern is definitly what you are looking for. Here is a great link to understand how it works and to begin to play with it!
I am facing a ListBox's ItemsSource related issue. I am implementing MVVM with WPF MVVM toolkit version 0.1.
I set one ListBox itemSource to update when a user double clicks on some other element (I handled the event in the code behind and executed the command there, since binding a command to specific events are not supported). At this point through the execution of the command a new ObservableCollection of items get generated and the ListBox's ItemsSource is intended to get updated accordingly. But it is not happening at the moment. ListBox does not update dynamically. What can be the problem? I am attaching relvent code for your reference.
XAML:
List of items which is doubled click to generate the next list:
<ListBox Height="162" HorizontalAlignment="Left" Margin="10,38,0,0" Name="tablesViewList" VerticalAlignment="Top" Width="144" Background="Transparent" BorderBrush="#20EEE2E2" BorderThickness="5" Foreground="White" ItemsSource="{Binding Path=Tables}" SelectedValue="{Binding TableNameSelected, Mode=OneWayToSource}" MouseDoubleClick="tablesViewList_MouseDoubleClick"/>
Second list of items which currently does not get updated:
<ListBox Height="153" HorizontalAlignment="Left" Margin="10,233,0,0" Name="columnList" VerticalAlignment="Top" Width="144" Background="Transparent" BorderBrush="#20EEE2E2" BorderThickness="5" Foreground="White" ItemsSource="{Binding Path=Columns, Mode=OneWay}" DisplayMemberPath="ColumnDiscriptor"></ListBox>
Code Behind:
private void tablesViewList_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
MainViewModel currentViewModel = (MainViewModel)DataContext;
MessageBox.Show("Before event command is executed");
ICommand command = currentViewModel.PopulateColumns;
command.Execute(null);
MessageBox.Show(currentViewModel.TableNameSelected);
//command.Execute();
}
View Model:
namespace QueryBuilderMVVM.ViewModels
{
//delegate void Del();
public class MainViewModel : ViewModelBase
{
private DelegateCommand exitCommand;
#region Constructor
private ColumnsModel _columns;
public TablesModel Tables { get; set; }
public ControllersModel Operators { get; set; }
public ColumnsModel Columns {
get { return _columns; }
set {
_columns = value;
OnPropertyChanged("Columns");
}
}
public string TableNameSelected{get; set;}
public MainViewModel()
{
Tables = TablesModel.Current;
Operators = ControllersModel.Current;
Columns = ColumnsModel.ListOfColumns;
}
#endregion
public ICommand ExitCommand
{
get
{
if (exitCommand == null)
{
exitCommand = new DelegateCommand(Exit);
}
return exitCommand;
}
}
private void Exit()
{
Application.Current.Shutdown();
}
//Del columnsPopulateDelegate = new MainViewModel().GetColumns;
//Method to be assigned to the delegate
//Creates an object of type ColumnsModel
private void GetColumns() {
ColumnsModel.TableNameParam = TableNameSelected;
Columns = ColumnsModel.ListOfColumns;
}
private ICommand _PopulateColumns;
public ICommand PopulateColumns
{
get {
if (_PopulateColumns == null) {
_PopulateColumns = new DelegateCommand(GetColumns); // an action of type method is passed
}
return _PopulateColumns;
}
}
}
}
Model:
public class ColumnsModel : ObservableCollection<VisualQueryObject>
{
private DataSourceMetaDataRetriever dataSourceTableMetadataObject;// base object to retrieve sql data
private static ColumnsModel listOfColumns = null;
private static object _threadLock = new Object();
private static string tableNameParam = null;
public static string TableNameParam
{
get { return ColumnsModel.tableNameParam; }
set { ColumnsModel.tableNameParam = value; }
}
public static ColumnsModel ListOfColumns
{
get
{
lock (_threadLock)
if (tableNameParam != null)
listOfColumns = new ColumnsModel(tableNameParam);
return listOfColumns;
}
}
public ColumnsModel(string tableName)
{
ColumnsModel.tableNameParam = tableName;
Clear();
try
{
dataSourceTableMetadataObject = new DataSourceMetaDataRetriever();
List<ColumnDescriptionObject> columnsInTable = new List<ColumnDescriptionObject>();
columnsInTable = dataSourceTableMetadataObject.getDataTableSchema("Provider=SQLOLEDB;Data Source=.;Integrated Security=SSPI;Initial Catalog=LogiwizUser", ColumnsModel.tableNameParam);
//List<String> listOfTables = dataSourceTableMetadataObject.getDataBaseSchema("Provider=SQLOLEDB;Data Source=.;Integrated Security=SSPI;Initial Catalog=LogiwizUser");
//List<String> listOfTables = dsm.getDataBaseSchema("G:/mytestexcel.xlsx", true);
//ObservableCollection<VisualQueryObject> columnVisualQueryObjects = new ObservableCollection<VisualQueryObject>();
foreach (ColumnDescriptionObject columnDescription in columnsInTable)
{
VisualQueryObject columnVisual = new VisualQueryObject();
columnVisual.ColumnDiscriptor = columnDescription;
columnVisual.LabelType = "column";
Add(columnVisual);
}
}
catch (QueryBuilderException ex)
{
/* Label exceptionLabel = new Label();
exceptionLabel.Foreground = Brushes.White;
exceptionLabel.Content = ex.ExceptionMessage;
grid1.Children.Add(exceptionLabel);*/
}
}
}
Any help is greatly appreciated. Thanks in advance.
The setter of property Columns should raise a PropertyChanged event.
Implement INotifyPropertyChanged to do so : MSDN INotifyPropertyChanged
I guess MVVM Toolkit provides a way of doing so easily (perhaps ViewModelBase already implement the interface ...).
EDIT : Implementing INotifyPropertyChanged is not enough, you have to raise the event created by INotifyPropertyChanged. You property should look something like this :
private ColumnsModel _columns;
public ColumnsModel Columns
{
get { return _columns; }
set
{
_columns = value;
PropertyChanged("Columns");
}
}
use an observableCollection<T> instead of a List<T>
MSDN DOC:
WPF provides the ObservableCollection class, which is a built-in implementation of a data collection that exposes the INotifyCollectionChanged interface. Note that to fully support transferring data values from source objects to targets, each object in your collection that supports bindable properties must also implement the INotifyPropertyChanged interface. For more information, see Binding Sources Overview.
say I have this control:
public partial class bloc999 : UserControl
{
bloc999Data mainBlock = new bloc999Data();
public bloc999()
{
InitializeComponent();
mainBlock.txtContents = "100";
base.DataContext = mainBlock;
}
}
in the xaml:
<TextBox Margin="74,116,106,0" Name="txtContents"
Text="{Binding Path=txtContents, UpdateSourceTrigger=PropertyChanged,Mode = TwoWay}" />
<TextBox Margin="74,145,106,132" Name="txtContents2"
Text="{Binding Path=txtContents2, UpdateSourceTrigger=PropertyChanged,Mode = TwoWay}" />
Then I have this class:
public class bloc999Data : INotifyPropertyChanged
{
string _txtContents;
string _txtContents2;
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(
this, new PropertyChangedEventArgs(propName));
}
public string txtContents2
{
get
{
return this._txtContents2;
}
set
{
if (int.Parse(value) > int.Parse(this._txtContents))
{
this._txtContents2 = "000";
}
else
this._txtContents2 = value;
NotifyPropertyChanged("txtContents2");
}
}
public string txtContents
{
get
{
return this._txtContents;
}
set
{
this._txtContents = value;
NotifyPropertyChanged("txtContents");
}
}
}
Ok now say I have A button on the form and I do this in the code:
mainBlock.txtContents2 = "7777777";
It puts 000 in the textbox, but If i just type in manually, in the textbox (txtContents2), the setter code is called but for some reason the textboxes value does not change, the instance value does change. help?
I believe it's just because the value is changing within the context of the data binding operation, so WPF just ignores it because it knows the value is changing and thinks the event is superfluous. What it doesn't know is that you've gone and changed the value from the value WPF has to something else again.
If you do the notification in a separate message then WPF will process it outside the context of the current data binding operation and will thus pick up the change:
if (int.Parse(value) > int.Parse(this._txtContents))
{
this._txtContents2 = "000";
// notify WPF of our change to the property in a separate message
Dispatcher.BeginInvoke((ThreadStart)delegate
{
NotifyPropertyChanged("txtContents2");
});
}
else
{
this._txtContents2 = value;
NotifyPropertyChanged("txtContents2");
}
This assumes your view model has access to the Dispatcher. An example of how to do so is shown in my blog post on a base ViewModel class.
I was having similar problem earlier here
In your usercontrol, update Binding and set UpdateSourceTrigger to Explicit
<TextBox Margin="74,145,106,132" x:Name="txtContents2" TextChanged="txtContents2_TextChanged"
Text="{Binding Path=txtContents2, UpdateSourceTrigger=Explicit,Mode = TwoWay}" />
then in the TextChanged event handler update the binding manually by validating the input.
move validation logic from property txtContent2's setter in bloc999Data in this event handler
private void txtContents2_TextChanged(object sender, TextChangedEventArgs e)
{
if (int.Parse(txtContents2.Text) > int.Parse(mainBlock.txtContents))
{
mainBlock.txtContents2 = "000";
txtContents2.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
}
else
{
mainBlock.txtContents2 = txtContents2.Text;
txtContents2.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
}
and it works.
Hope it helps!!