Win8 App: Binding to a UserControl property - binding

The textbox on my main pages is bound to a string property on my usercontrol which is a popup. The textbox value is correctly displayed with the initial value of the string porperty however changes to the property do not update the displayed value. How can I make this field observe changes to the property?
**MainPage XAML:**
<TextBox x:Name="textBlockStartDate" Text="{Binding selectedDateString,Mode=OneWay}" DataContext="{Binding ElementName=MyUserControl}"/>
**MyUserControl.cs**
private DateTime selectedDate;
public string selectedDateString {get{return selectedDate.Date.ToString("dd.MM.yyyy");}}
public DatePicker(){
this.InitializeComponent();
selectedDate = DateTime.Today;
}
// Update the value when a button is pressed
private void DayButton_Clicked(object sender, RoutedEventArgs e){
Button button = (Button)sender;
selectedDate = (DateTime)button.Tag;
}

Related

How to present data with binding and DataTemplate -or- ContentControl for MAUI

How to present a string, number or a also view model with binding and DataTemplate?
I am looking for a MAUI replacement for the WPF ContentControl.
The ContentView has a Content property but this is from type View.
The ContentPresenter has a Content property but this is also from type View. <Ignorable>WTF, Why this is not named ViewPresenter when it can only present a View??? Someoteimes MAUI is weird.</Ignorable>
How to present any content with defining DataTemplates for each data type?
class PropertyViewModel {
public string Name {get;set;}
public object Value {get;set;}
}
<Page.Resources>
<DataTemplate DataType="System.String">
<Entry Text="{Binding}/>
</DataTemplate>
<DataTemplate DataType="System.Int32">
<NumberPicker Value="{Binding}/>
</DataTemplate>
.. more templates, eg. DatePicker for System.DateOnly
</Page.Resources>
<DockLayout>
<Label Text="{Binding Name}
<TemplatedContenView Content={Binding Value}/>
</DockPanel>
The TemplatedContenView or ContentControl (that does not exist in MAUI), can use different templates for different types of Value. In WPF the ContentControl uses ContentTemplate, ContentTemplateSelector or if none specified it looked into the resources to find the template.
<Ignorable>I often have the feeling with MAUI that I have to constantly reinvent things that are standard in WPF. Yes I know MAUI is not WPF, but there should still be at least similar concepts. The switch from WinForms to WPF was much easier and the differences were considerably greater.</Ignorable>
Edit1: a more detailed example
I'm a WPF developer and recently I've started MAUI project. And It looks like you have to reinvent the wheel every time when you are going to write such a simple scenario as you mentioned :(. When you do it using WPF you even don't need to thought about that, it's too easy to implement, but when you use MAUI you should break your mind to do such minor things.
I also encountered the same issue and I didn't find a simple in-box solution. But I came up with the idea to create a control with some layout inside that has attached properties from BindableLayout
TemplatedContentPresenter.xaml.cs:
public partial class TemplatedContentPresenter : ContentView
{
public TemplatedContentPresenter()
{
InitializeComponent();
}
public static readonly BindableProperty DataTemplateSelectorProperty = BindableProperty.Create(nameof(DataTemplateSelector), typeof(DataTemplateSelector), typeof(TemplatedContentPresenter), null, propertyChanged: DataTemplateSelectorChanged);
public static readonly BindableProperty DataTemplateProperty = BindableProperty.Create(nameof(DataTemplate), typeof(DataTemplate), typeof(TemplatedContentPresenter), null, propertyChanged: DataTemplateChanged);
public static readonly BindableProperty DataProperty = BindableProperty.Create(nameof(Data), typeof(object), typeof(TemplatedContentPresenter), null, propertyChanged: DataChanged);
public DataTemplateSelector DataTemplateSelector
{
get =>(DataTemplateSelector)GetValue(DataTemplateSelectorProperty);
set => SetValue(DataTemplateSelectorProperty, value);
}
public DataTemplate DataTemplate
{
get => (DataTemplate)GetValue(DataTemplateProperty);
set => SetValue(DataTemplateProperty, value);
}
public object Data
{
get => GetValue(DataProperty);
set => SetValue(DataProperty, value);
}
private static void DataTemplateSelectorChanged(BindableObject bindable, object oldValue, object newValue)
{
if(bindable is TemplatedContentPresenter contentPresenter && newValue is DataTemplateSelector dataTemplateSelector)
{
BindableLayout.SetItemTemplateSelector(contentPresenter.HostGrid, dataTemplateSelector);
}
}
private static void DataTemplateChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is TemplatedContentPresenter contentPresenter && newValue is DataTemplate dataTemplate)
{
BindableLayout.SetItemTemplate(contentPresenter.HostGrid, dataTemplate);
}
}
private static void DataChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is TemplatedContentPresenter contentPresenter)
{
BindableLayout.SetItemsSource(contentPresenter.HostGrid, new object[] { newValue });
}
}
}
TemplatedContentPresenter.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.TemplatedContentPresenter">
<Grid x:Name="HostGrid" x:FieldModifier="private" />
</ContentView>
Usage:
<Frame WidthRequest="500" HeightRequest="500">
<controls:TemplatedContentPresenter
Data="{Binding}"
DataTemplateSelector="{StaticResource CardTemplateSelector}"/>
</Frame>
UPD:
While I was writing the answer I came up with another solution with a simple converter:
SingleObjectToArray.xaml
internal class SingleObjectToArray : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new object[] { value };
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Usage:
<Frame>
<Frame.Resources>
<converters:SingleObjectToArray x:Key="SingleObjectToArrayConverter"/>
</Frame.Resources>
<Grid BindableLayout.ItemsSource="{Binding Converter={StaticResource SingleObjectToArrayConverter}}"
BindableLayout.ItemTemplateSelector="{StaticResource CardTemplateSelector}" />
</Frame>

TextBlock bind with two sources

I have a TextBlock and to show translated text I use
x:Uid="/FileResourceName/txtControldName" Name="txtControlName"
(in resource file I write: txtControldName.Text = "some text") it works fine. But I would like to hide or show it depend on codebehind object and for that I use
Visibility="{Binding Path = IsMyControlVisible}"
(in that case for text I have to assign some text directly in control like Text="some text"). If I use one of this two properties everything works fine but simultaneously these two properties do not work. Is there any way to do the same?
If I use one of this two properties everything works fine but simultaneously these two properties do not work. Is there any way to do the same?
It's not a normal behavior. There is no conflict between binding to Visibility property and setting text in resource file. Have you set DataContext for your Binding?
Please see the following code sample, it worked well.
<Grid>
<TextBlock x:Uid="txtControldName" Visibility="{Binding IsMyControlVisible}"></TextBlock>
<Button Content="test" Click="Button_Click"></Button>
</Grid>
public sealed partial class MainPage : Page,INotifyPropertyChanged
{
private Visibility _IsMyControlVisible;
public Visibility IsMyControlVisible
{
get { return _IsMyControlVisible; }
set
{
_IsMyControlVisible = value;
RaisePropertyChange("IsMyControlVisible");
}
}
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChange(String PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,new PropertyChangedEventArgs(PropertyName));
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
IsMyControlVisible = IsMyControlVisible == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed;
}
}
Please note that you need to implement the INotifyPropertyChanged interface, when the property value is change, it will notify the UI.

Send hidden field from current MVC view to the MVC view about to be loaded

When a certain link gets clicked on my _Layout.cshtml page a prompt asks for a value. I store this value in a hidden field in the same view. Now before I finish loading Recommended.cshtml I want to see if the hidden field value matches a constant value I created. Everything works, except I can't figure out how to get the hidden value into the view about to be loaded. Could I maybe use a [HttpPost] ActionResult on _Layout.cshtml? What would be best practice in this situation?
Model:
[Bind]
public class RecommendedModel
{
[StringLength(50), Required(ErrorMessage = "This field is required.")]
public string Password { get; set; }
private const string _TOKEN = "pass";
public string Token { get { return _TOKEN; } }
}
Controller:
public ActionResult Recommended(RecommendedModel _recommendedModel)
{
if (_recommendedModel.Password == _recommendedModel.Token)
{
ViewBag.Message = "Recommended.";
return View();
}
else
{
return View("Error");
}
}
View (The hidden field is actually on my shared _Layout page):
#Html.HiddenFor(m=>m.Password)
JavaScript:
var password = prompt("What is the password?", "");
document.getElementById("Password").value = password;
EDIT: All of this code works except _recommendedModel.Password is null. It should have the string the user entered into the prompt, unless I did not map it correctly.
Your password field should be inside tag. If it is outside the form tag make sure you will pass the value from that field to the contrller using javascript/jquery
In your controller, change the ActionResult method to receive the hidden field as a parameter, like this:
public ActionResult Recommended(RecommendedModel _recommendedModel, string password)
Then, set a ViewBag variable, for example, with that parameter value and put it on your "next" View.
ViewBag.password = password;
Remember your hidden field must be inside the form element in order to be passed to the controller.
edit
#using (Html.BeginForm("ControllerName", "Recommended")) {
#Html.HiddenFor(m => m.Password)
<input name="Save" type="submit" value="Save" />
}

Custom Validator Not Showing Error Message

On my Domain Model for my ASP.net MVC3 application I have built a custom validator to ensure the date of birth is inserted in a particular format.
I am saving the date of birth as a string, because my application needs to save the date of birth of long dead people, e.g. Plato, Socrates, etc., just in case you were wondering why not use DateTime to save date of birth.
Here is my custom validator code:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class ValidateDODDOB : ValidationAttribute
{
// Error Message
private const string DefaultErrorMessage = "Please type the date in the format specified.";
// Gets or sets the Regular expression.
private Regex Regex { get; set; }
// The pattern used for Date of Birth and Date of Death validation.
public string Pattern { get { return #"^(?:\d+\s)?(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)?(?:\s?\d+)(?:\sBCE)?$"; } }
// Initializes a new instance of the VerifyDODDOB class.
public ValidateDODDOB() : base(DefaultErrorMessage)
{
this.Regex = new Regex(this.Pattern);
}
// Determines whether the specified value of the object is valid.
// true if the specified value is valid; otherwise, false.
public override bool IsValid(object value)
{
// convert the value to a string
var stringValue = Convert.ToString(value);
var m = Regex.Match(stringValue);
return m.Success;
}
}
The above works in terms of validating, and stopping the Create/Edit Actions from proceeding through to the database. But no error message is being displayed when the form is returned to the View!
UPDATE IN RESPONSE TO COMMENT 01:
Sorry Olive, I should have posted the view code too. Here it is:
<div class="inputField">
#Html.LabelFor(x => x.DOB, "Date of Birth")
#Html.TextBoxFor(model => model.DOB)
#Html.ValidationMessageFor(model => model.DOB)
</div>
So Yes, I have told it to to show the Validation message as well. And as far as AJAX, it is not via AJAX. Like you said, it is after a full POST Request.
Do you mean you want the message to show in a ValidationSummary control?
If so, try setting the "excludePropertyErrors" of the ValidationSummary HtmlHelper to false:
#Html.ValidationSummary(false)
This will tell the summary control to summary display all errors (having it set to 'true', the default value, will tell the control to display model-level errors only).
I think what you are probably wanting to do is use this method
protected override ValidationResult IsValid(object value, ValidationContext context)
{
// convert the value to a string
var stringValue = Convert.ToString(value);
var m = Regex.Match(stringValue);
if(!m.Success)
{
return new ValidationResult(DefaultErrorMessage);
}
return null;
}
Then in your view make sure you have the ValidationMessageFor, and in the Controller make sure you check ModelState.IsValid and return the original View if it is not valid. That ought to do it.

WPF - Why are changes to an object for a simple data-binding not reflected back to the target in the main window?

I am currently learning WPF (C#) and have taken the following simple data binding example from a text book which I am trying to get working.
The example displays a simple window containing a name and age textbox and a Birthday button.
The name and age that are display are represented in a Person object. When the birthday button is clicked the age is incremented and a message box is displayed.
The message box displays "Happy Birthday, [Name], age [++Age]".
E.g. If the name on screen is Dangerous and the age is 28 then when you click the Birthday button the message box will state "Happy Birthday, Dangerous, age 29".
The age is incremented correctly in the Person object but this information is not reflected back to the screen via the binding. However, if you alter the name or age field on the screen then this information is correctly updated in the Person object.
The example in the book states that I should see the updated age reflected back on the screen. I’m probably missing something simple but I have spent some time trying to correct this but don’t know what is wrong? Thanks in advance for any help with this example.
The XAML to display the window:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel Name="stack">
<TextBlock>Name:</TextBlock>
<TextBox Text="{Binding Path=Name}"/>
<TextBlock>Age:</TextBlock>
<TextBox Text="{Binding Path=Age}"/>
<Button Name="birthdayButton">Birthday</Button>
</StackPanel>
</Window>
The code behind:
using System; using System.Windows; using System.Windows.Controls;
namespace WpfApplication1 {
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
Person person = new Person("Dangerous", 28);
public Window1()
{
InitializeComponent();
stack.DataContext = person;
this.birthdayButton.Click += birthdayButton_Click;
}
void birthdayButton_Click(object sender, RoutedEventArgs e)
{
++person.Age;
MessageBox.Show(
string.Format("Happy Birthday, {0}, age {1}!",
person.Name,
person.Age),
"Birthday");
}
}
public class Person
{
string name;
int age;
public Person() { }
public Person(string name, int age)
{
this.name = name;
this.age = age;
}
public string Name
{
get {return this.name;}
set {this.name = value;}
}
public int Age
{
get {return this.age;}
set {this.age = value;}
}
}
}
First of all you need <TextBox Text="{Binding Path=Age, Mode=TwoWay}"/> Your Person object may also have to implement INotifyPropertyChanged and raise the proper event when the age changes. Check here for an example of how to implement INotifyPropertyChanged.
Try setting in the binding:
<TextBox Text="{Binding UpdateSourceTrigger=PropertyChanged}"/>
Or set it to Explicit and update it manually via the Button Click event handler, becuase the default for TextBox is LostFocus, and clicking a button doesn't lose the focus from the TextBox.
For more info read: Binding.UpdateSourceTrigger property.

Resources