I am trying to get the content of a TextBox updated using Binding in a MVVM environment. When a Button receive focus, it passes a value, and that value should be reflected in the TextBox. I seem to have the first part right, however seems to be struggling at passing the value..
I know the question about MVVM has been asked before (including by myself), but I really cannot get it, for some reasons..
So I start with my model:
public class iText : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}
I then continue with my ViewModel:
private iText _helper = new iText();
public iText Helper
{
get { return _helper; }
set
{
_helper = value;
}
}
The XAML page:
<Page.Resources>
<scan:ModelDataContext x:Key="ModelDataContext" x:Name="ModelDataContext"/>
</Page.Resources>
<TextBox Text="{Binding Helper.Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
I then try to update the Text from MainPage.cs
public sealed partial class MainPage : Page
{
public MainPageViewModel iText { get; set; }
public MainPage()
{
InitializeComponent();
iText = new MainPageViewModel();
}
private void btn_GotFocus(object sender, RoutedEventArgs e)
{
var str = "test"
iText.Helper.Text = str;
}
I could really appreciate if someone could tell me what I do wrong, and where. Thanks so much in advance.
In your MainPage constructor, try setting the datacontext to your ViewModel.
Something like...
public MainPage()
{
InitializeComponent();
iText = new MainPageViewModel();
this.dataContext = iText;
}
Related
I am a beginner at Xamarin and I am trying to pass value from one page to another using QueryProperty, but I keep getting null values.
Here is the Page where the value comes from:
<StackLayout>
<Button Text="Pass" Command="{Binding passCommand}"></Button>
</StackLayout>
The code behind:
public Page()
{
InitializeComponent();
passCommand = new Command(passFunc);
BindingContext = this;
}
public ICommand passCommand { get; }
private async void passFunc()
{
string str = "Hello";
await Shell.Current.GoToAsync($"{nameof(Page3)}?str={str}");
}
And here is the receiving page:
<StackLayout>
<Label Text="{Binding str}"/>
</StackLayout>
The code behind:
[QueryProperty(nameof(str), nameof(str))]
public partial class Page3 : ContentPage
{
public Page3()
{
InitializeComponent();
BindingContext = this;
showdisp();
}
public string str { set; get; }
public async void showdisp()
{
await App.Current.MainPage.DisplayAlert("Hello", str, "OK");
}
}
The passed value should be put in the Label and the popup display alert. When I tried to put breakpoints, str value is still null. Navigating between pages are fine.
Can someone point out if where the error is T_T
Thanks in advance.
Your property "str" needs to raise a PropertyChanged event, so that the binding updates the value:
[QueryProperty(nameof(str), nameof(str))]
public partial class Page3 : ContentPage
{
public Page3()
{
InitializeComponent();
BindingContext = this;
// attention: this won't show the passed value,
// because QueryProperty values only are set after construction
//showdisp();
}
private string _str;
public string str
{
get => _str;
set
{
if(_str == value) return;
_str = value;
// Let the bound views know that something changed, so that they get updated
OnPropertyChanged();
// optional, call showdisp() when value changed
showdisp();
}
}
public async void showdisp()
{
await App.Current.MainPage.DisplayAlert("Hello", str, "OK");
}
}
However, since the parameter only is set after construction of Page3 finished, your showdisp() method won't have the correct value. You need to call it later.
You should also consider using a ViewModel and apply MVVM.
I have the following view model:
public class ViewModel : ObservableObject
{
public string Watermark { get; set; } = "Loading...";
}
where ObservableObject is from Microsoft.Toolkit.Mvvm.ComponentModel which implements INotifyPropertyChanged and INotifyPropertyChanging.
I have the following XAML:
<xctk:WatermarkTextBox>
<xctk:WatermarkTextBox.WatermarkTemplate>
<DataTemplate>
<ContentControl Content="{Binding DataContext.Watermark, RelativeSource={RelativeSource AncestorType=Window}}"></ContentControl>
</DataTemplate>
</xctk:WatermarkTextBox.WatermarkTemplate>
</xctk:WatermarkTextBox>
And lastly the following C# code:
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
Dispatcher.Invoke(() =>
{
// Assigning property of textbox directly with .Watermark = "" does not work
// Updating view model property doesn't work either
Model.Watermark = "Done";
});
}
When the window loaded method gets called however, the watermark of the watermark textbox does not update to reflect its supposed new value, "Done".
It might be a lack of general WPF understanding but if anyone could give a pointer as to why this might fail I would greatly appreciate it.
Thanks!
You aren't using ObservableObject properly. It's not magic (unlike some solutions that use frameworks such as Fody), so you still have to do the work yourself to propagate changes. Note the example in the documentation.
public class User : ObservableObject
{
private string name;
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
}
So you'll have to follow that pattern:
public class ViewModel : ObservableObject
{
private string watermark;
public ViewModel()
{
Watermark = "Loading...";
}
public string Watermark
{
get => watermark;
set => SetProperty(ref watermark, value);
}
}
Hi !
I have a WPF application and I want to set the title of the window page without refresh the all page, because in this page I have two buttons that list a DataRow belong the Title when I press it.
void refreshStatusBar()
{
this.Title= "Holaaa";
}
WPF class:
<Height=.... Title="Prueba"...> the initial value
The problem is when I press a button (next or back) I need to set the Title of the page and never change when I call to refreshStatusBar() in the btNext or btBack method.
I tryed to binding the Title, but don´t work. Always show the same value, the initial:
Title="{Binding Path="windowTitleBar"}"
public String windowTitleBar {get; set;}
void refreshStatusBar(){
windowTitleBar="Holaaa";
}
I want the title change when I press some button. I don´t have pages inside the window page, just show one thing or another thing.
I tryed too:
Title="{Binding Path=windowTitleBar, RelativeSource={RelativeSource Mode=Self}}"
and don´t work neither.
Please, any solution to fix it?
Sorry for my english !
Thanks !
This works for me without a binding:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.Title = "Hellooo";
}
void RefreshStatusBar()
{
this.Title = "Holaaa";
}
private void button1_Click(object sender, RoutedEventArgs e)
{
RefreshStatusBar();
}
}
If you want to use a binding, set it up like you did with Title="{Binding Path=WindowTitleBar, RelativeSource={RelativeSource Mode=Self}}"
But as it is, WPF has no way of knowing when your property value changes. You can implement INotifyPropertyChanged to solve this:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string _windowTitleBar = "Hellooo";
public MainWindow()
{
this.WindowTitleBar = "Hellooo";
InitializeComponent();
}
public string WindowTitleBar
{
get { return _windowTitleBar; }
set
{
_windowTitleBar = value;
OnPropertyChanged("WindowTitleBar");
}
}
void RefreshStatusBar()
{
this.WindowTitleBar = "Holaaa";
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private void button1_Click(object sender, RoutedEventArgs e)
{
RefreshStatusBar();
}
}
Edit:
I just noticed you said "Page". I've never used Pages, but it looks like to set the title of the window containing your page, you have to set the WindowTitle property. Unfortunately it's not a DependencyProperty so you can't use a binding. You can set it directly, though:
void RefreshStatusBar()
{
this.WindowTitle = "Holaaa";
}
I have spent a couple hours trying to figure out this ONE problem. Here's what is happening:
I am trying to bind a Title to my XAML file from my ViewModel. All of the code executes (I checked using breakpoints/watch), but the binding doesn't actually work. I am very new to development and especially MVVM, so I am having a hard time figuring this out. Relevant code:
App.Xaml.Cs
private static MainPageViewModel _mainPageViewModel = null;
public static MainPageViewModel MainPageViewModel
{
get
{
if (_mainPageViewModel == null)
{
_mainPageViewModel = new MainPageViewModel();
}
return _mainPageViewModel;
}
}
MainPageModel
public class MainPageModel : BaseModel
{
private string _pageTitle;
public string PageTitle
{
get { return _pageTitle; }
set
{
if (_pageTitle != value)
{
NotifyPropertyChanging();
_pageTitle = value;
NotifyPropertyChanged();
}
}
}
MainPageViewModel
private void LoadAll()
{
var page = new MainPageModel();
page.PageTitle = "title";
MainPageViewModel
public MainPageViewModel()
{
LoadAll();
}
MainPage.Xaml.Cs
public MainPage()
{
InitializeComponent();
DataContext = App.MainPageViewModel;
}
MainPage.Xaml
<Grid x:Name="LayoutRoot">
<phone:Panorama Title="{Binding PageTitle}">
Do I need a using statement in the Xaml too? I thought I just needed to set the data context in the MainPage.Xaml.Cs file.
I'm pretty sure I've posted all of the relevant code for this. Thanks everyone!!
The problem is here, in the view model class:
private void LoadAll()
{
var page = new MainPageModel();
page.PageTitle = "title";
All you've done here is create a local object "page" -- this will not be accessible anywhere outside the local scope. I suppose what you meant to do is make "page" a member of "MainPageViewModel":
public class MainPageViewModel
{
public MainPageModel Model { get; private set; }
private void LoadAll()
{
_page = new MainPageModel();
_page.PageTitle = "title";
}
}
This way, you'll be able to bind to the "PageTitle" property -- but remember, it's a nested property, so you'll need:
<phone:Panorama Title="{Binding Model.PageTitle}">
I want to pass a value from MainWindow into my UserControl! I passed a value to my UserControl and the UserControl showed me the value in a MessageBox, but it is not showing the value in a TextBox. Here is my code:
MainWindow(Passing Value To UserControl)
try
{
GroupsItems abc = null;
if (abc == null)
{
abc = new GroupsItems();
abc.MyParent = this;
abc.passedv(e.ToString(), this);
}
}
catch (Exception ee)
{
MessageBox.Show(ee.Message);
}
UserControl
public partial class GroupsItems : UserControl
{
public MainWindow MyParent { get; set; }
string idd = "";
public GroupsItems()
{
InitializeComponent();
data();
}
public void passedv(string id, MainWindow mp)
{
idd = id.ToString();
MessageBox.Show(idd);
data();
}
public void data()
{
if (idd!="")
{
MessageBox.Show(idd);
texbox.Text = idd;
}
}
}
EDIT(using BINDING and INotifyProperty )
.....
public GroupsItems()
{
InitializeComponent();
}
public void passedv()
{
textbox1.Text = Text;
}
}
public class Groupitm : INotifyPropertyChanged
{
private string _text = "";
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Problem here is with reference.
When you create new object in code behind, new object will be created and this is not the same object which you have in xaml code. So you should use following code:
<local:GroupsItems x:Name="myGroupsItems"/>
and in code behind you don't have to create new object. You should use object that you added in XAML:
...
myGroupsItems.MyParent = this;
myGroupsItems.passedv(e.ToString(), this);
...
Here is example solution (sampleproject).
You are calling data in the constructor when idd is still "" which results in the text box still being empty. Changing the MyParent property does not change that. Only passedv does. But at that point you do not have the parent set. Just call data in passedv, too.
Try this:
public partial class GroupsItems : UserControl
{
//properties and methods
private string idd="";
public string IDD
{
get{return idd;}
set{
idd=value;
textBox1.Text=idd;
}
}
//other properties and methods
}
Usage:
In your Main form:
abc = new GroupsItems();
abc.IDD="sometext";
MainGrid1.Children.Add(abc); //Grid or any other container for your UserControl
In your Binding example, your GroupItem class looks ok, except that you need to pass in the name of the changed property:
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged("Text");
}
}
}
Now, in GroupsItems, you shouldn't be accessing the TextBox. In WPF, we manipulate the data, not the UI... but as we use Binding objects to data bind the data to the UI controls, they automatically update (if we correctly implement the INotifyPropertyChanged interface).
So first, let's add a data property into your code behind (which should also implement the INotifyPropertyChanged interface) like you did in your GroupItem class:
private GroupItem _item = new GroupItem();
public GroupItem Item
{
get { return _item; }
set
{
if (value != _item)
{
_item = value;
NotifyPropertyChanged("Item");
}
}
}
Now let's try using a Binding on a TextBox.Text property:
<TextBox Text="{Binding Item.Text}" />
See how we bind the Text property of your GroupItem class to the TextBox.Text property... now all we need to do is to change the value of the Item.Text property and watch it update in the UI:
<Button Content="Click me" Click="Button_Click" />
...
private void Button_Click(object sender, RoutedEventArgs e)
{
Item.Text = "Can you see me now?";
}
Alternatively, you could put this code into your passedv method if you are calling that elsewhere in your project. Let me know how you get on.
UPDATE >>>
In your GroupItem class, try changing the initialization to this:
private string _text = "Any text value";
Can you now see that text in the UI when you run the application? If not, then try adding/copying the whole Text property into your code behind and changing the TextBox declaration to this:
<TextBox Text="{Binding Text}" />
If you can't see the text value now, you've really got problems... you have implemented the INotifyPropertyChanged interface in your code behind haven't you?