INotifyPropertyChanged not working as expected - wpf

I have a little trouble with INotifyPropertyChanged. I implemented it lots of times, however this case is different. I would like to create the following application:
There are multiple tab containers, which are bound to different ObservableCollections of Entities. Now I would like to show the name of the currently (last) selected Entity. To achieve this, I've created created a DependencyProperty CurrentEntity in the Window class and assign via SelectionChanged on the TabControls the current Entity.
private void SelectionChanged(Object sender, EventArgs e)
{
CurrentEntity = e.NewItems[0] as Entity;
}
The TextBlock which, in the above picture, displays VideoA looks like this:
<TextBlock Text={Binding CurrentEntity.Name}" />
Now whenever I select a new Tab, the Text in the TextBlock changes accordingly. However when I change the the name of an Entity (inside a TextBox in a Tab, which is also bound to the Entity), the text in the TextBlock doesn't change. I have to select another Tab and then change back to the Tab where I changed the Name to see the newly entered name in the TextBlock. Basically, this is my problem, I would like to see the text changing without selecting another Tab first (and yes, I de-focused from the TextBox inside the Tab after entering the new Name).
Has anyone an idea where the problem is?
My "architecture" looks (more or less ;-)) like this:

ObservableCollection just means that the event to update UI is kicked off when the collection changes, you add, remove, re-order, etc.
Altering an the property of an item in an ObservableCollection, such as a string, doesn't not actually cause a UI event.
Does the object you have inside the ObservableCollection implement INotifyPropertyChanged?

Related

Master-detail: How to fetch a control from a template inside the "detail" ContentControl?

I have a ListView (on the 'master' side) whose selection drives a ContentControl's Content property (on the 'detail' side). The ContentControl's visual tree comes from either of two DataTemplate resources that use DataType to choose which detail view to render based on what is selected in the ListView.
That part works fine.
The part I'm struggling with is that there is a particular control inside (one of) the templates that I need to obtain a reference to whenever it changes (e.g. the template selected changes or the ListView selection changes such that the instance of the control is recreated.)
In my ListView.SelectionChanged event handler, I find the ContentControl has not yet been updated with its new visual tree, so initially it's empty on the first selection, and for subsequent selections its visual tree matches the old selection instead of the new one.
I've tried delaying my code by scheduling on the Dispatcher with a priority as low as DispatcherPriority.Loaded, which works for the first selection but on subsequent selections my code still runs before the visual tree is updated.
Is there a better event I should be hooking to run whenever the ContentControl's visual tree is changed to reflect a changed data-bound value to its Content property?
Extra info: the reason I need to reach into the expanded DataTemplate is that I need to effectively set my view model's IList SelectedItems property to a DataGrid control's SelectedItems property. Since DataGrid.SelectedItems is not a dependency property, I have to do this manually in code.
The fix required a combination of techniques. For the first selection that populates the visual tree, I needed to handle ContentControl.OnApplyTemplate() which is only a virtual method rather than an event. I derived from it and exposed it as an event:
public class ContentControlWithEvents : ContentControl
{
public event EventHandler? TemplateApplied;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.TemplateApplied?.Invoke(this, EventArgs.Empty);
}
}
In the XAML I used the above class rather than ContentControl:
<local:ContentControlWithEvents
Content="{Binding SelectedAccount}"
x:Name="BankingSelectedAccountPresenter"
TemplateApplied="BankingSelectedAccountPresenter_TemplateApplied" />
Then I handle the event like this:
void BankingSelectedAccountPresenter_TemplateApplied(object sender, EventArgs e) => this.UpdateSelectedTransactions();
private void UpdateSelectedTransactions()
{
if (this.MyListView.SelectedItem?.GetType() is Type type)
{
DataTemplateKey key = new(type);
var accountTemplate = (DataTemplate?)this.FindResource(key);
Assumes.NotNull(accountTemplate);
if (VisualTreeHelper.GetChildrenCount(this.BankingSelectedAccountPresenter) > 0)
{
ContentPresenter? presenter = VisualTreeHelper.GetChild(this.BankingSelectedAccountPresenter, 0) as ContentPresenter;
Assumes.NotNull(presenter);
presenter.ApplyTemplate();
var transactionDataGrid = (DataGrid?)accountTemplate.FindName("TransactionDataGrid", presenter);
this.ViewModel.Document.SelectedTransactions = transactionDataGrid?.SelectedItems;
}
}
}
Note the GetChildrenCount check that avoids an exception thrown from GetChild later if there are no children yet. We'll need that for later.
The TemplateApplied event is raised only once -- when the ContentControl is first given its ContentPresenter child. We still the UpdateSelectedTransactions method to run when the ListView in the 'master' part of the view changes selection:
void BankingPanelAccountList_SelectionChanged(object sender, SelectionChangedEventArgs e) => this.UpdateSelectedTransactions();
On initial startup, SelectionChanged is raised first, and we skip this one with the GetChildrenCount check. Then TemplateApplied is raised and we use the current selection to find the right template and search for the control we need. Later when the selection changes, the first event is raised again and re-triggers our logic.
The last trick is we must call ContentPresenter.ApplyTemplate() to force the template selection to be updated before we search for the child control. Without that, this code may still run before the template is updated based on the type of item selected in the ListView.

Setting DataContext in Catel with WPF

So I've started looking at the Catel MVVM framework and it looks like it will solve a couple of problems I have encountered, but I have one really silly issue. I think I'm just missing something small.
I took one of my smaller WPF projects to switch over the Catel as a way for me to learn it. I have a simple 'Player Registration' form, with fields like name and surname. I recreated my original view model by using the vm codesnippet and all is good, all the properties and attributes I've set up as I've read in the documentation.
I then changed the UserControl I used for 'Player Registration' (PlayerRegistrationView) to a catel:UserControl. I placed PlayerRegistrationView on a standard WPF Window (nothing else, just a xmlns for the View and the view as the only content on the window, no attributes)
But here is my problem:
I have a MainWindow with a button on to open the Window for the player registration. The on-click event simply is this:
private void ButtonPlayerClick(object sender, RoutedEventArgs e)
{
var playerRegistration = new PlayerRegistrationDialog
{
Owner = this,
DataContext = new PlayerRegistrationViewModel(),
};
playerRegistration.Show();
}
Running my program and then clicking on the button results in an NotSupportedException on my PlayerRegistrationView: The view model of the view could not be resolved. Use either the GetViewModelType() method or IViewModelLocator
I tried making the ViewModel a static resource on the window and setting the datacontext there, but it produces the same error.
I am at a loss. What have I missed?
Thanks
The whole point of Catel is that it automatically wires up all the views and view models. The "complex" thing that you are trying to achieve is that you have a view which is placed on a window. You want the window to have the same data context as the view in order to do some stuff in the window as well.
In Catel, it is possible to place any view with datacontext management on a DataWindow (window in Catel). Then it will work like this:
DataWindow
|=> View
If the DataWindow and the View share the same view model type, then they share the same view model. For example:
PlayerRegistrationWindow => derives from catel:DataWindow
PlayerRegistrationView => derives from catel:UserControl
Since both start with PlayerRegistration, they will both be resolved to PlayerRegistrationViewModel automatically.
To show the window, the only thing you have to do is this:
var viewModel = new PlayerRegistrationViewModel();
var uiVisualizerService = ServiceLocator.Default.ResolveType<IUIVisualizerService>();
uiVisualizerService.Show(viewModel);
All will work automatically and you don't have to worry about setting any datacontext yourself.

Own property of UserControl in WPF

I have a user control with 2 grids on it. Now I want to be able to retrieve the grid that has the focus and expose it to my view model. How can I do this in WPF?
I want to fill a property in my view model with the name of the Grid that has focus. It seems not to be easy.
Can anyone help?
Thx!
You really should reconsider your design if you are exposing UI elements or specific parts to your viewmodel. Usually your viewmodel should not know of any specific ui element. What exactly do you want to do with the name of the ui element? You could listen to a GotFocus event on your two grids
like
<Grid x:Name="Grid1" GotFocus="OnGridGotFocus"/>
<Grid x:Name="Grid2" GotFocus="OnGridGotFocus"/>
and add this method to your UserControl, in this method you could retrieve it via
private static void OnGridGotFocus(object aSender, RoutedEventArgs aE)
{
string name = (string)(aSender as DependencyObject).GetValue(NameProperty);
}
the name could now be written into a DependencyProperty which you bind to your view model. But again, i still think you should not do this.
If you explain what exactly you are trying to achieve, maybe we can help you better.

want to create a listbox with a textblock and a button itemtemplate

alt text http://www.freeimagehosting.net/uploads/abbcae9cd3.png
I want to create a listbox like the one shown in the image,
i just dont know how will i get the button(the red close button) click event for each item in the listbox
Can anyone point me to a good tutorial on this
Thanks
Create a button as usual, with a Click handler:
<Button Click="MyClickHandler">...</Button>
Now, in your click handler, you have the sender argument, which will contain the button that was clicked. You want the item that the button belongs to, though. First, you need to the ListViewItem that contains the button. There's several implementations of how to find a visual ancestor around, here's the first result I found. Add that extension method to a static class somewhere. So now your click handler looks like this:
private void MyClickHandler(object sender, RoutedEventArgs e)
{
ListViewItem parent = ((DependencyObject)sender).TryFindParent<ListViewItem>();
}
Now you need to retrieve the item that goes with this ListViewItem. This, thankfully, is much simpler (though I have no idea what it is, since your post doesn't say):
object ItemRelatedToButton = TheListBoxInQuestion.ItemContainerGenerator.ItemFromContainer(parent);
Replace object with the type that your ListBox actually contains, and the names with ones that are actually relevant. At this point, you can now interact with the item that the button is "attached" to.
You may also want to make sure parent isn't null, just in case something weird happened, and in theory you should make sure ItemRelatedToButton isn't null either.
If you don't want to write code behind, like MVVM would suggest, you have two choices:
1. if the control in your datatemplate supports ICommand, send the DataContext as the CommandParameter. Then the parameter will be passed into you command execute method.
2. If the control doesn't have an ICommand property, create an attached property, in the PropertyChangedCallback call back, your can hook a command to your clicked event. Then again, in Xaml you can pass the Datacontext as the CommandParameter.
Sorry for not elaborating further, but if you search for MVVM, Command and Attached, you should find tons of detail informations.

In WPF, is it possible to have a combo box control, that looks like a RadioButton?

I have a whole bunch of code that is dependent on the ComboBox type, and I want to be able to use it on a new UI, but the UI that I want to use it for needs to look like a RadioButton for usability reasons. Is it possible to have a ComboBox control look like a set of RadioButtons?
My suggestion would be to use an ItemsControl with a DataTemplate that would render RadioButtons. To the ItemsControl you'd bind the same thing you're binding to the ComboBox.
One caveat is that you need to set the GroupName of the radio buttons to something that would be the same to the group, so they can be mutually exclusive. Otherwise, if you don't do anything, you'll be able to select more than one RadioButton simultaneously.
You could build a new UserControl that has many of the same methods that the ComboBox class does, but adapt it so that it creates multiple radio boxes instead.
Your question is kinda vague though.
IE create an Items collection on your user control, and when something is added, draw a radio box and resize your control, instead of what a combo box does and just adds a string to the list.
Then all you have to do is find and replace all your references to ComboBox with RadioIFiedComboBox.
Heres some comparison:
ComboBox cb = new ComboBox();
cb.Items.Add("blah");
or
RadioIFiedComboBox cb = new RadioIFiedComboBox();
cb.Items.Add("blah");
and
public class RadioIFiedComboBox : UserControl {
public ObservableCollection<object> Items = new ObservableCollection<object>();
public RadioIFiedComboBox() {
Items.CollectionChanged += new NotifyCollectionChangedEventHandler(YourCollectionChanged);
}
private void YourCollectionChanged(){
//do something here to redraw your controls
}
}
The code above is just an example, you'd have to create all the methods you use in the ComboBox class and create similar functionality.

Resources