WPF DataBinding of a label - c#

im now trying for hours to get the databinding run.
But whatever im trying nothing works.
After a few thousend examples and retries (feels like a ffew thousand) i decided to make a new thread for my problem.
I have a window where you can select a woker.
On these window there are a UserControl which shows the details of the selected worker.
It would be nice to have all labels / textboxes / comboboxes filled automatically in case the selected worker changed.
For that the UserControl has a Property "ShownWorker" which contains the selected worker.
Worker Class:
public class Worker : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string id;
public string ID
{
get
{
return id;
}
set
{
id = value;
OnPropertyChanged("ID");
}
}
public Worker()
{
}
}
UserControl:
private Worker shownWorker;
public Worker ShownWorker
{
get
{
return shownWorker;
}
set
{
shownWorker = value;
}
}
public WorkerDetails()
{
InitializeComponent();
this.DataContext = shownWorker;
}
Label on the UserControl:
<Label Height="28" Margin="129,6,6,0" Name="labelWorkerID" VerticalAlignment="Top" Content="{Binding ID, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}"></Label>
And im setting the ShownWorker like that:
private void dataGridAvaibleWorker_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (dataGridAvaibleWorker.SelectedItem is Worker)
{
var selectedWorker= (Worker)dataGridAvaibleWorker.SelectedItem;
WorkerDetails.ShownWorker = selectedWorker;
}
}
But nothing happens. Whats wrong?
I dont get it.

Aside from the property change notification, you have another issue:
These two properties:
ShownWorker
DataContext
Are pointing to the same reference when the form opens - e.g.
Worker someWorker = new Worker();
ShownWorker = someWorker;
DataContext = ShownWorker;
Changing ShownWorker here does not affect DataContext
ShownWorker = new Worker();
DataContext at this point still references the original worker - when you do this assignment DataContext = ShownWorker, DataContext references the worker you instantiated in the first of the three lines, it does not refer to the instance that ShownWorker is pointing to
To try and explain it a bit better:
// I'll stick some 'tags' on to shown how the instances will be referenced
Worker someWorker = new Worker(); // someWorker = "Instance1"
ShownWorker = someWorker; // ShownWorker points to "Instance1"
DataContext = ShownWorker; // DataContext points to "Instance1"
ShownWorker = new Worker(); // ShownWorker now points to "Instance2"
// DataContext still points to "Instance1"
You need to set DataContext not ShownWorker and raise a property changed event for DataContext
A better way to do this is to use an MVVM approach e.g.
public class WorkerViewModel : INotifyPropertyChanged
{
// Put standard INPC implementation here
public event PropertyChanged.... etc
private Worker shownWorker;
public Worker ShownWorker
{
get
{
return shownWorker;
}
set
{
if(value == shownWorker) return; // Don't do anything if the value didn't change
shownWorker = value;
OnPropertyChanged("ShownWorker");
}
}
public WorkerViewModel()
{
ShownWorker = // Get/create the worker etc
}
}
Now you have a VM you can set the DataContext to:
public WorkerViewModel WorkerViewModel { get; private set; }
public WorkerDetails()
{
InitializeComponent();
WorkerViewModel = new WorkerViewModel();
this.DataContext = WorkerViewModel;
}
And updates can be done to the VM instead:
WorkerViewModel.ShownWorker = someWorker;
Make sure you set your bindings in the XAML
<UserControl>
<SomeControl DataContext="{Binding ShownWorker}" />
</UserControl>
Instead of rolling your own MVVM stuff - I'd suggest looking at some popular MVVM frameworks as they can make working with WPF/Silverlight a breeze.
My personal fave is Caliburn Micro but there are plenty out there (MVVMLight, Prism, etc)

Related

WPF UserControl: Invoke usercontrol's public method from viewModel

I have a MVVM WPF application in C#, NET 3.5 and Visual Studio 2008.
From the app main xaml I import a user control.
This user control has some public methods, there are two I am interested in.
One method to start an animation and another to stop it.
From my view's constructor in code-behind (xaml.cs), I call the user control public method to start the animation to show it to user while I am loading some data into my gridview within listview. The method to load the data is called form my view model.
So now, when the loading task is finished, I need to call the another user control public method to stop animation but I do not know how to do this from my view model.
Any ideas? I cannot touch the user control as this is not mine.
Below some piece of code.
XAML:
xmlns:controlProgress="clr-namespace:Common.XAML.Controls.Progress;assembly=Common.XAML"
<controlProgress:Progress x:Name="Progress"
Grid.ZIndex="3"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="150"
CustomText="Loading...">
Code-behind (xaml.cs):
public MyView(ViewModelSession vm)
: base(vm)
{
InitializeComponent();
Progress.StartAnimation();
}
View Model:
public MyViewModel(Session session)
: base(session)
{
this.LoadDataIntoGridView();
}
You can use the INotifyPropertyChanged Interface e.g. create an ViewModelBase
public class ViewModelBase
: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then you use this for your ViewModel and add a Property IsLoading
public class MyViewModel : ViewModelBase
{
private bool _isLoading;
public bool IsLoading
{
get { return _isLoading; }
set
{
if(_isLoading == value) return;
_isLoading = value;
OnPropertyChanged();
}
}
Then in your View Codebehind use the PropertyChanged event of the ViewModel to Start/Stop Animation.
Then you can set the bool in your ViewModel to start stop closing animation
in your view
UPDATE
public class MyView
{
private readonly MyViewModel _viewModel;
public MyView(MyViewModel viewModel)
: base(viewModel)
{
InitializeComponent();
_viewModel = viewModel;
_viewModel.PropertyChanged +=OnPropertyChanged;
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(MyViewModel.IsLoading))
{
if (_viewModel.IsLoading)
{
Progress.StartAnimation();
}
else
{
Progress.StopAnimation();
}
}
}
}
You could put a boolean property in your view model to track if the loading has been completed, after that the property will be set to true.
public class MyViewModel
{
public bool IsLoadComplete { get; set; }
public MyViewModel()
{
this.LoadDataIntoGridView();
}
}
Then in your codebehind you can start a Task to track changes in that property of the DataContext:
public MyView(MyViewModel vm)
{
InitializeComponent();
Progress.StartAnimation();
Task.Run(() =>
{
var dataContext = DataContext as MyViewModel;
while (true)
{
if (dataContext.IsLoadComplete)
break;
Task.Delay(100);
}
Dispatcher.BeginInvoke(new Action(() => { Progress.StopAnimation(); }));
});
}
You have to use Dispatcher.BeginInvoke to queue the call in the UI thread. Of course this is not a ready-to-production solution. You may provide Datacontext until View has been constructed in which case you must refactor, also you may keep track of the task you have just started and may be support cancellation with a CancellationToken. This is only a sample

UWP MVVM binding to TextBox and passing back value

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;
}

Binding to the same property on multiple windows doesn't work

I ran into a quite confusing problem when developing a multi-window wpf application.
There are two windows, MainWindow and SecondWindow. The code of both is pretty simple:
MainWindow:
<Button Content="Change Property to 5" Click="ChangeProperty" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" />
SecondWindow:
<Label Content="{Binding InstanceOfMyClass.value, NotifyOnSourceUpdated=True}"></Label>
The code behind the second Window is untouched, the code behind the first window is the following:
public partial class MainWindow : Window
{
SecondWindow w;
ViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new ViewModel() { InstanceOfMyClass = new MyClass() { value = 3 } };
w = new SecondWindow() { DataContext = vm };
w.Show();
}
private void ChangeProperty(object sender, RoutedEventArgs e)
{
vm.InstanceOfMyClass.value = 7;
}
}
And the view model class which implements INotifyPropertyChanged:
class ViewModel : INotifyPropertyChanged
{
private MyClass _instance;
public MyClass InstanceOfMyClass
{
get
{
return _instance;
}
set
{
_instance = value;
OnPropertyChanged("InstanceOfMyClass");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
class MyClass
{
public int value { get; set; }
}
I expected the text block to change its text to 5 when I click the button.
The number "3" is correctly loaded on startup. The window also refreshes when I create a new instance of MyClass and set it as InstanceOfMyClass in my ViewModel.
But when I hit the button - or, even stranger, when I temporarily store InstanceOfMyClass, set it to null and reassign it with the saved variable - nothing happens.
Any idea why?
Thanks in advance!
Implement INotifyPropertyChanged in MyClass and try again. In ChangeProperty you change the value property, that doesn't notify the view about the change.
Or you can also try rewriting your ChangeProperty to the following:
vm.InstanceOfMyClass = new MyClass() { value = 7 };
Both of these approaches should fix the problem as far as I can see.

Set the SelectionChanged event of a ComboBox while binding its SelectedItem and ItemsSource in XAML

I'm trying to set up a ComboBox with its options binded from a list of strings, its default selected value binded from a setting, and with an event handler for its selection changed.
I want to configure it all using XAML like so:
<ComboBox Name="RoutesComboBox"
ItemsSource="{Binding Routes}"
SelectedItem="{Binding DefaultRoute}"
SelectionChanged="RouteFilter_SelectionChanged" />
But when I do that on startup it throws the error:
An unhandled exception of type
'System.Reflection.TargetInvocationException' occurred in
PresentationFramework.dll
If I only do some of it in XAML, then either set the SelectionChanged event or the ItemsSource programatically in C# like below it works fine. But I have a lot of these ComboBoxes so I would rather do it straight in the XAML.
<ComboBox Name="RoutesComboBox"
ItemsSource="{Binding Routes}"
SelectedItem="{Binding DefaultRoute}" />
With this C#:
public IEnumerable<string> Routes
{
get { return LubricationDatabase.GetRoutes(); }
}
public string DefaultRoute
{
get { return MySettings.Default.DefaultRoute; }
set { } /* side question: without this, it throws a parse exception. Any idea why? */
}
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
RoutesComboBox.SelectionChanged += RouteFilter_SelectionChanged;
}
I've also tried the solution found here:
private string _defaultRoute;
public string DefaultRoute
{
get { return MySettings.Default.DefaultRoute; }
set
{
if (_defaultRoute != value)
{
_defaultRoute = value;
// this fires before `SelectedValue` has been
// updated, and the handler function uses that,
// so I manually set it here.
RoutesComboBox.SelectedValue = value;
SelectionChangedHandler();
}
}
}
Which is okay, but is pretty bulky and probably more work than is worth it when I can just programatically assign the SelectionChanged event.
Again if possible I'd like to do it all using XAML because I have a lot of these ComboBoxes and initializing them all like this in the C# will look awful.
Any ideas?
Why are you binding with SelectedItem when you're not going to update the item when a user changes their selection? Not sure what your event handler is doing, but I have a working solution just the way you wanted it.
In short, you need to keep track of the DefaultRoute using a backing field. Also, you need to notify the UI when the selected item changes in your view model; which by the way is something you don't seem to be doing, MVVM. You should only be hooking into the selection changed event if you plan on updating the view in some way. All other changes should be handled in your view models DefaultRoute setter
XAML
<ComboBox Name="RoutesComboBox"
ItemsSource="{Binding Routes}"
SelectedItem="{Binding DefaultRoute}"
SelectionChanged="RouteFilter_SelectionChanged" />
Code
public partial class MainWindow : Window, INotifyPropertyChanged
{
public IEnumerable<string> Routes
{
get
{
return new string[] { "a", "b", "c", "d" };
}
}
public string DefaultRoute
{
get
{
return _defaultRoute;
}
set
{
_defaultRoute = value;
// Handle saving/storing setting here, when selection has changed
//MySettings.Default.DefaultRoute = value;
NotifyPropertyChanged();
}
}
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
DefaultRoute = MySettings.Default.DefaultRoute;
}
private string _defaultRoute;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void RouteFilter_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
public static class MySettings
{
public static class Default
{
public static string DefaultRoute = "a";
}
}

How can I keep the data in my textbox (WP)?

I am working on an APP that can take number from user and send message to that number.
The number is saved in global variable, the number is changeable by the user. I want the phone number to appear in the textbox every time the user opens the app, so he/she can view the number and update it if they require.
What I've tried:
phonenumber.Text = (App.Current as App).phoneglobal;
I added it after InitializeComponent();, but that didn't work.
Since you are using WPF, I recommend using the MVVM pattern. In your case, you would have:
public partial class MainWindow : Window
{
private MainWindowViewModel viewModel = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = viewModel;
}
}
public class MainWindowViewModel : System.ComponentModel.INotifyPropertyChanged
{
private App currentApp = (Application.Current as App);
public MainWindowViewModel()
{
}
public string PhoneNumber
{
get
{
return currentApp.phoneglobal;
}
set
{
currentApp.phoneglobal = value;
OnPropertyChanged(new PropertyChangedEventArgs("PhoneNumber"));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
}
Then, in your xaml, simply bind to the ViewModel's PhoneNumber property.
<Window x:Class="YourNamespace.MainWindow">
<TextBox x:Name="phonenumber" Text="{Binding PhoneNumber}" />
</Window>
Then you should never need to set phonenumber.Text from the code-behind. If you need to set the phone number programmatically, set viewModel.PhoneNumber and the text box will automatically update.
Note that if you set currentApp.phoneglobal directly (without using viewModel.PhoneNumber), then the text box will NOT automatically update.
If this doesn't help, post your xaml code as well as any references in code to the phonenumber text box.

Categories