Source Code:
class Cons
{
public Cons()
{
BaseDir = AppDomain.CurrentDomain.BaseDirectory;
SetProperty();
}
private void SetProperty()
{
NowPlaying = "Hello";
}
public string NowPlaying{get; set; }
}
public partial class MainWindow
{
Cons Resources = new Cons()
public MainWindow()
{
txbl.DataContext = Resources;
Resources.NowPlaying = "NoHello";
}
}
And a text block <Textblock x:Name="txbl" Text="{Binding NowPlaying, Mode=TwoWay}"/>
When I set the NowPlaying Property in the MainWindow contructor, the Property changed to "NoHello", but the TextBlock Text Property still Hello even I set binding mode = TwoWay
The Image I Capture When the Problem Occured
The code you've mentioned works correctly. The textblock does show the new value "NoHello".
But, if you wish to update the the property value NowPlaying after your textblock has been loaded, please implement INotifyPropertyChanged on your class Cons and raise PropertyChanged in the setter of the property NowPlaying.
Related
The main-window is listening for plugging in/out USB-devices. If it is an usb-key/disk it collects a file-list from that device and show that list in a second window.
While debugging I can see that the NewUsbFiles observablecollection get's populated with 117 items. I see that the property UsbFile (before calling the showdialog) has 117 items, but nevertheless the listbox is empty.
Any thoughts ?
The method to populate / create that second window:
NewUsbFiles = new ObservableCollection<UsbFile>();
UpdateNewUsbFiles(driveName);
Application.Current.Dispatcher.Invoke(delegate
{
var usbFileSelector = new UsbFileSelector()
{
Owner = this,
UsbFiles = NewUsbFiles
};
usbFileSelector.ShowDialog();
});
The UsbFile-class:
public class UsbFile
{
public string UsbFileName { get; set; }
public string OnTableFileName { get; set; }
public bool Ignored { get; set; } = false;
public UsbFile(string fileName)
{
var fileInfo = new FileInfo(fileName);
UsbFileName = fileInfo.FullName;
OnTableFileName = $"{fileInfo.CreationTime:yyMMddHHmmsss}_{fileInfo.Name}";
}
}
The XAML of the second window :
<Window
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:MainWindow="clr-namespace:PartyPictures.WPF.MainWindow" x:Name="wUsbFileSelector"
x:Class="PartyPictures.WPF.UsbFileSelector"
mc:Ignorable="d"
Title="USB" HorizontalAlignment="Center" VerticalAlignment="Center" WindowStyle="ToolWindow" ScrollViewer.VerticalScrollBarVisibility="Auto" SizeToContent="WidthAndHeight">
<StackPanel x:Name="spUsbFileList">
<ListBox x:Name="ImageListbox"
DataContext="{Binding ElementName=wUsbFileSelector}"
ItemsSource="{Binding UsbFiles}"
Background="AliceBlue" ScrollViewer.HorizontalScrollBarVisibility="Disabled" MinWidth="200" MinHeight="200">
</ListBox>
</StackPanel>
</Window>
The code-behind of the second window :
public partial class UsbFileSelector : Window
{
public ObservableCollection<UsbFile> UsbFiles { get; set; } = new ObservableCollection<UsbFile>();
public UsbFileSelector()
{
InitializeComponent();
}
}
Inside the window you can see InitializeComponent method. It creates all of the stuff defined in XAML and applies all bindings. After binding has been appplied with your empty collecton (that you have created with default property value) the binding will not know about any change of that property, that was the right answer.
But implementing INotifyPropertyChanged is more about viewmodel instances, not visual.
I really suggest you use Dependency Property for windows and controls if you want to bind. There are some reasons for that:
Dependency property setter has built-in notify mechanism.
If you bind one DP to another DP, value is shared in between.
After all, it is WPF approach =)
Here is how your window will look like after change
public partial class UsbFileSelector : Window
{
public static readonly DependencyProperty UsbFilesProperty =
DependencyProperty.Register("UsbFiles", typeof(ObservableCollection<UsbFile>), typeof(UsbFileSelector));
public ObservableCollection<UsbFile> UsbFiles
{
get { return (ObservableCollection<UsbFile>) GetValue(UsbFilesProperty); }
set { SetValue(UsbFilesProperty, value); }
}
public UsbFileSelector()
{
InitializeComponent();
}
}
Also I strongly recommend you to use some WPF inspector tool while developing for the WPF, for example, snoop. You can navigate through the controls and properties while app is running and find issues much quickly you can from the code or from stackoverflow =)
In
var usbFileSelector = new UsbFileSelector()
{
Owner = this,
UsbFiles = NewUsbFiles
};
you are assigning a new value to the UsbFiles property without firing a property change notification for that property.
You could either implement INotifyPropertyChanged and fire the PropertyChanged event or make UsbFiles a dependency property.
Or you pass NewUsbFiles as constructor argument and assign it before calling InitializeComponent
public UsbFileSelector(ObservableCollection<UsbFile> usbFiles)
{
UsbFiles = usbFiles;
InitializeComponent();
}
and call it like this:
var usbFileSelector = new UsbFileSelector(NewUsbFiles)
{
Owner = this
};
Note that if you always pass a new collection, using ObservableCollection isn't actually necessary. You never add or remove elements to/from the collection, so there is no need for a change notification.
Someone posted (and deleted the comment) that I should add
DataContext = this;
To
public UsbFileSelector()
{
InitializeComponent();
DataContext = this;
}
Someone else mentioned (that comment too was deleted) that this was not necessary because of the
DataContext="{Binding ElementName=wUsbFileSelector}"
in the XAML.
BUT it turned out that removing the DataContext line from the XAML and setting it to this in code was the sollution. No idea why but that did it.
EDIT just to make clear that this is not a good solution and works only by accident, try the following:
// this works
var usbFileSelector = new UsbFileSelector();
usbFileSelector.Owner = this;
usbFileSelector.UsbFiles = NewUsbFiles;
usbFileSelector.ShowDialog();
// this does not
var usbFileSelector = new UsbFileSelector();
usbFileSelector.Owner = this;
await Task.Delay(10);
usbFileSelector.UsbFiles = NewUsbFiles;
usbFileSelector.ShowDialog();
All the answers already given are correct, the heart of your problem is the
UsbFiles = NewUsbFiles
which causes the binding to "break" - UsbFiles is no longer pointing to the collection that is bound to the Listbox.
Another possible way to solve this would be to simply leave the bound collection alone and just repopulate the contents.
var usbFileSelector = new UsbFileSelector()
{
Owner = this,
UsbFiles.Clear();
foreach (var uf in NewUsbFiles) {
UsbFiles.Add(uf);
}
};
I am using a bindable property in a custom control in order to set a property from the xaml code. However, it seems like my property always will get the default value that I've specified for the bindable property.
My xaml code:
<controls:MyView ID="4" />
My code behind:
public partial class MyView : ContentView
{
public static readonly BindableProperty IDProperty = BindableProperty.Create(
nameof(ID),
typeof(int),
typeof(MyView),
15);
public int ID
{
get
{
return (int)GetValue(IDProperty);
}
set
{
SetValue(IDProperty, value);
}
}
private MyViewViewModel viewModel;
public MyView()
{
InitializeComponent();
viewModel = new MyViewViewModel() {};
BindingContext = viewModel;
}
}
I expect that my property should get value 4 in this example, but it always get the default value 15. Should the property be set in the constructor or later?
What am I doing wrong?
Why do you embed a ViewModel inside your custom control? It is weird and even wrong. The idea behind a custom control is that you could reuse and bind it to the parent's ViewModel. Think of a simple Button control, it is reusable by simple placing it on the screen and setting the BindableProperties like Text, Command and etc. It is working because it's BindingContext by default is the same as it's parent.
In your case you sort of isolate your control from any modifications, since you set the BindingContext to a private custom ViewModel class. You have to rethink your solution.
It should be as simple as:
public partial class MyView : ContentView
{
public static readonly BindableProperty IDProperty = BindableProperty.Create(
nameof(ID),
typeof(int),
typeof(MyView),
15);
public int ID
{
get => (int)GetValue(IDProperty);
set => SetValue(IDProperty, value);
}
public MyView()
{
InitializeComponent();
}
}
I see it's too late, but i have been suffering for a while now, that - for a reason that i don't know - a ContentView(Custom view element) won't bind when you set it's BindingContext property in any way other than this:
<ContentView x:Class="mynamespace.CustomViews.MyView"
....
x:Name="this">
then on the main container element (in my case a frame) set the BindingContext
<Frame BindingContext="{x:Reference this}"
....>
Setting the BindingContext in the constructor - in MyView.xaml.cs - does not work, while this way - and other ways - work in binding a View to another class (a view model), it does not - i repeat - work in binding ContentView to its code_behind.cs file.
In you xaml , do
<controls:MyView ID="{Binding Id}" />
And then in ViewModel, Create a porperty called Id
public int Id {get; set;} = 4;
You don't need Bindable property if your are not binding , Just Create a Normal Property of type int With ID as property name.And then you can assign the ID from XAML.(Intellisense will also show the ID property)
public int ID
{
get;set;
}
I have an application which has support for multiple different languages and as such one of the requirements is that the user experience should not change regardless of language. I'm currently have a dialog box which has multiple buttons in English but is there a way to dynamically change the text fields of the buttons?
I would suggest a MarkupExtension that does all the heavy work:
public class TranlationExtension : MarkupExtension
{
public string Key { get; set; }
public TranlationExtension(string key)
{
this.Key = key;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
Binding binding = new Binding("TranslationDictionary[" + Key + "]");
binding.Source = MyTranslations;
return binding.ProvideValue(serviceProvider);
}
}
The MyTranslations must be a class that implements INotifyPropertyChanged if the Culture Changes.
Useage:
<Button Content="{local:Tranlation TheKeyOfTheTranslationThatIsUsedInTheDictionaryAswell }"/>
The Approach is described here http://www.wpftutorial.net/LocalizeMarkupExtension.html
You can bind content property of the button to property in your viewmodel, for example:
<Button Content="{Binding ButtonTxt}"/>
Then you can set ButtonTxt property in viewmodel regardles of selected language e.g.
if (languageId == Languages.English) {
ButtonTxt = buttonTextInEnglish;
}
I'm implementing MVVM for a WPF application.
The ViewModels are created as follows:
ViewModel: base class from which all ViewModels override
MainTemplateViewModel: the 'Masterpage' ViewModel which contains a ViewModel property Current that contains the ViewModel to show
CustomerOverviewViewModel: an example of a view that can be placed in the MainTemplateViewModel.Current
The CustomerGridViewModel contains a Telerik GridView. I would like to show the number of items in the title of the MainTemplateViewModel. The GridView.Items.Count property implements the INotifyPropertyChanged so I would like to bind this property to ViewModel.RowCount (because the CustomerGridViewModel doesn't know it is part of the MainTemplateViewModel it cannot be bound directly to the TextBlock). I can in turn use ViewModel.NumberOfRecords to show the amount in the title.
How can I bind the Count property to a property in my ViewModel?
Edit
I'll describe the issue in more detail:
The list of objects shown in the grid is a binding from the ViewModel:
<telerik:RadGridView x:Name="CustomerGrid" ItemsSource="{Binding CustomerViewModels}">
</telerik:RadGridView>
When filtering the Grid in memory, the Telerik Grid automatically changes the GridView.Items.Count property (this does not mean the original list count is changed!). So if I can bind this property to a property in the ViewModel class, this would solve the problem.
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
private int numberOfRecords;
public int NumberOfRecords
{
get { return numberOfRecords; }
set { numberOfRecords = value; OnPropertyChanged(); }
}
}
MainTemplateViewModel.cs
public class MainTemplateViewModel : ViewModel
{
private ViewModel current = new MainOverviewViewModel();
public ViewModel Current
{
get { return current; }
set
{
if (current != value)
{
current = value; OnPropertyChanged();
}
}
}
}
CustomerOverview.xaml.cs
public partial class CustomerOverview : UserControl
{
public CustomerOverview()
{
InitializeComponent();
this.CustomerGrid.Items.CollectionChanged += ItemsCollectionChanged;
this.CustomerGrid.Loaded += CustomerGrid_Loaded;
}
void CustomerGrid_Loaded(object sender, RoutedEventArgs e)
{
/* METHOD 1 PROBLEM: the field to bind to in the MainTemplate is out of scope and accessing a view is not MVVM */
var binding = new Binding();
binding.Path = new PropertyPath("Items.Count");
binding.Source = CustomerGrid;
((MainWindow)this.ParentOfType<MainWindow>()).NumberOfRecords.SetBinding(TextBlock.TextProperty, binding);
}
void ItemsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
/* METHOD 2 PROBLEM: codebehind code should be in viewmodel */
((CustomerOverviewViewModel)this.DataContext).NumberOfRecords = CustomerGrid.Items.Count;
}
}
Instead of loading the data in your UserControl, just declare a DependencyProperty of the relevant type in it. You can then load the data in the main view model and simply data bind to it from the UserControl. You could do something like this simple example:
In CustomerOverview.xaml.cs:
public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items",
typeof(ObservableCollection<YourDataType>), typeof(CustomerOverview),
new PropertyMetadata(null));
...
In CustomerOverview.xaml:
<ListView ItemsSource="{Binding Items, RelativeSource={RelativeSource AncestorType={
x:Type YourPrefix:CustomerOverview}}}" ... />
...
In MainWindow.xaml (or whichever relevant view):
<YourPrefix:CustomerOverview
Items="{Binding SomeCollectionPropertyInMainTemplateViewModel}" ... />
...
In MainTemplateViewModel.cs (or whichever relevant view model):
public ObservableCollection<YourDataType> SomeCollectionPropertyInMainTemplateViewModel
{
get { return someCollectionPropertyInMainTemplateViewModel; }
set
{
someCollectionPropertyInMainTemplateViewModel = value;
NotifyPropertyChanged("SomeCollectionPropertyInMainTemplateViewModel");
NotifyPropertyChanged("NumberOfRecords");
}
}
public int NumberOfRecords
{
get { return someCollectionPropertyInMainTemplateViewModel.Count; }
}
Telerik Grid has two properties
Visible Count
TelerikGrid.Items.Count
Total Count
TelerikGrid.Items.TotalItemCount
In case this helps!
If i get you right, you want your master to show details of the child.
Your master should be able to know your child by your Current property.
If you are using MVVM correctly, the data bound to your grid comes from the child-ViewModel.
In that case, you have already have the itemscount in your child-ViewModel.
After this you can say in your Master something like
<Label Content="{Binding Current.NumberOfRows}"></Label>
According to this page you could wrap your source in a QueryableCollectionView
I am really struggling to understand binding. I know there are loads of other threads with much the same title as this one, but they're all trying to do something more complex than I am, and all the answers assume a whole pile of stuff that I just don't get :(
I'm trying to display a dynamically updated message log. I've defined a Message class:
public class Message
{
public DateTime Timestamp { get; private set; }
public string Value { get; private set; }
public int Severity { get; private set; }
public Message(string value, int severity)
{
Timestamp = DateTime.Now;
Value = value;
Severity = severity;
}
}
I've defined a MessageLog class as simply:
public class MessageLog: ObservableCollection<Message>
{
public MessageLog(): base()
{ }
}
In my MainWindow constructor I have a Log property:
public MessageLog Log { get; private set; }
In the MainWindow constructor I initialise Log:
public MainWindow()
{
InitializeComponent();
DataContext = this;
Log = new Model.MessageLog();
// and so on
}
In the XAML for the main window I have:
<ListBox Name="MessagePanel" Height="100" ItemsSource="{Binding MessageLog}" IsEnabled="False"/>
Now if I add Message instances to the MessageLog I expected to see them appear in the ListBox. They don't. What have I missed?
Thanks in advance (and if you can point me somewhere that explains bindings clearly -- especially the view that XAML has of the code and where it can look for things -- then many more thanks on top. At the moment I'm using Matthew McDonald's "Pro WPF 4.5 in C#" and I'm just not getting it.)
Change your constructor:
public MainWindow()
{
InitializeComponent();
DataContext = this;
Log = new Model.MessageLog();
}
to this:
public MainWindow()
{
InitializeComponent();
Log = new Model.MessageLog(); // <- This line before setting the DataContext
DataContext = this;
}
Explanation:
Setting properties after having set the DataContext requires your class to implement INotifyPropertyChanged and raise change notifications after properties are set.
Since you're setting the DataContext before setting the property, the value of this.Log is null at the time of DataBinding, and WPF is never notified that it ever changed.
That being said, you don't usually put Data inside UI Elements (such as Window). The accepted and recommended approach to WPF is MVVM, where you usually create a ViewModel and set that as the Window's DataContext:
public class MyViewModel
{
public MessageLog Log {get;set;}
public MyViewModel()
{
Log = new MessageLog();
}
}
Window Constructor:
public MainWindow
{
DataContext = new MyViewModel();
}
Your collection property name is Log which is what you should be binding to in ItemsSource property; and if you have not done a typo in your question then you are binding wrongly to MessageLog, and change Binding as below:
<ListBox Name="MessagePanel" Height="100" ItemsSource="{Binding Log}" IsEnabled="False"/>
For more information and learning on Data Binding in WPF (4.5), see MSDN Data Binding Overview
The datacontext of the view must be the viewmodel.