Get binding object from Tap event - c#

I'm trying to access the object that I bound a DataTemplate to, specifically I only need one int value. I've linked the main Grid from within the DataTemplate to an event handler via Tap:
<DataTemplate x:Name="joinTemplate">
<Grid Tag="{Binding index}" DataContext="{Binding}" Tap="select_Click" ...>
...
</Grid>
</DataTemplate>
My handler looks like:
private void select_Click(object sender, System.Windows.Input.GestureEventArgs e)
The problem is that I still can't access sender.DataContext or sender.Tag. However, when I run it in the debugger and look at it through Watch, I can get to both the DataContext and Tag by simply expanding "base" twice. That should mean that the object that I'm being given inherits those objects and is somehow the child of the original Grid, however, I thought that the sender was always the Grid you bound the handler to? To get the actual element that I tapped I'd have to use, for this example, e.OriginalSource, right?

Just cast sender to the appropriate type to access the DataContext property:
((FrameworkElement)sender).DataContext
Then, the same way, you'll have to bind the value to whichever type you binded to the grid. For instance, if you binded an object of type Model:
var model = (Model)((FrameworkElement)sender).DataContext

Related

How to detect when a templated UserControl is removed from UI (not just hidden in UI)

I have an ItemsControl with its ItemsSource bound to an ObservableCollection<T>, using my own UserControl as the ItemTemplate:
<ItemsControl ItemsSource="{Binding Path=MyObservableColletion, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<MyControls:MyUserControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I've got it hooked up so that the UI is updated as items are added/removed to/from the collection.
When an item is removed from the collection, I want to detect this in the code-behind of the representative templated MyUserControl. How can I do this?
I did notice that the Unloaded event fires in this case, but it's not adequate for my purpose because Unloaded also fires when the UI containing my ItemsControl is simply hidden/collapsed (e.g. when it's in a TabControl and the tab is switched away).
Is there another event for this purpose, or a way of detecting in the body of an Unloaded handler that my control is definitely removed and not just hidden?
since your your "MyObservableColletion" should be of type ObservableCollection you should be able to subscribe to it's "CollectionChanged" event. Its EventHandler will trigger on adding and removing. In the event args you will find The oldItems array that will contain the collection that was removed from the collection.
public void CollectionChangedEventHandler(object sender, NotifyCollectionChangedEventArgs args)
{
var deletedItem = args.OldItems.FirstOrDefault();
}
in WPF you do not deal with controls in code. never ever. You play with the data that is represented by controls.
We need to check if any property is changed for the dis-connected UserControl. One such property is DataContext, as after UserControl is not in the ItemsControl, so it's DataContext will be reset. And for dis-connected controls, it is set to {DisconnectedItem}.
Add this code in your UserControl.
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (e.Property.Name == "DataContext" && e.NewValue.ToString() == "{DisconnectedItem}")
{
System.Diagnostics.Debug.WriteLine(this + " : I am removed !");
}
base.OnPropertyChanged(e);
}

ComboBox.SourceUpdated event is not fired

I have two ComboBoxes on my view. Both of them are bound to two different ObservableCollections in the ViewModel, and when the selected item in ComboBox1 is changed, ComboBox2 gets updated with a different collection. The binding works just fine, however, I want the second ComboBox to always select the first item in its collection. Initially, it works, however, when the source and items in ComboBox2 get updated, the selection index gets changed to -1 (i.e. the first item is no longer selected).
To fix this, I added a SourceUpdated event to ComboBox2 and method that the event calls changes the index back to 0. The problem is that the method is never called (I put a breakpoint at the very top of the method and it doesn't get hit). Here's my XAML code:
<Grid>
<StackPanel DataContext="{StaticResource mainModel}" Orientation="Vertical">
<ComboBox ItemsSource="{Binding Path=FieldList}" DisplayMemberPath="FieldName"
IsSynchronizedWithCurrentItem="True"/>
<ComboBox Name="cmbSelector" Margin="0,10,0,0"
ItemsSource="{Binding Path=CurrentSelectorList, NotifyOnSourceUpdated=True}"
SourceUpdated="cmbSelector_SourceUpdated">
</ComboBox>
</StackPanel>
</Grid>
And in the code-behind:
// This never gets called
private void cmbSelector_SourceUpdated(object sender, DataTransferEventArgs e)
{
if (cmbSelector.HasItems)
{
cmbSelector.SelectedIndex = 0;
}
}
Any help is appreciated.
After working on it for an hour I finally figured it out. The answer is based on this question: Listen to changes of dependency property.
So basically you can define a "Property Changed" event for any DependencyProperty on an object. This can be extremely helpful when you need to extend or add additional events to a control without having to create a new type. The basic procedure is like this:
DependencyPropertyDescriptor descriptor =
DependencyPropertyDescriptor.FromProperty(ComboBox.ItemsSourceProperty, typeof(ComboBox));
descriptor.AddValueChanged(myComboBox, (sender, e) =>
{
myComboBox.SelectedIndex = 0;
});
What this does is that it creates a DependencyPropertyDescriptor object for the ComboBox.ItemsSource property, and then you can use that descriptor to register an event for any control of that type. In this case, every time the ItemsSource property of myComboBox is changed, the SelectedIndex property is set back to 0 (which means the first item in the list is selected.)

Replace current binding source object with another object

I'm totally new to C# and WPF and I'm trying to do my best with the data binding. I have a MyClass which implements INotifyPropertyChanged; so everytime I change a property value, this is updated in my UI. Then I have bound the DataContext of a stackpanel to an object of MyClass. Like this:
<StackPanel Name="stackPanel1" DataContext="{Binding}">
<TextBlock Name="textBlock1" Text="{Binding Path=Title, Mode=OneWay}" />
</StackPanel>
In code behind I do this:
item = new MyClass();
stackPanel1.DataContext = item;
and the binding is working fine. If I replace my current binding source object with another one, I have to manually set this by typing again the datacontext binding:
item = new MyClass();
stackPanel1.DataContext = item;
item1 = new MyClass();
.
. //manipulate item1
.
if (item1 is ok)
item=item1;
stackPanel1.DataContext = item;
Is there a better way to replace my source object and have all the associated bindings updated?
When you say stackPanel1.DataContext = item;, you are setting the property, not binding it.
When you set the property, you are setting it equal to an instance of the object. When you bind it, you are telling it it will be getting its value from some other location, so look in that location anytime it needs to get the value.
Providing your class that contains the bound properties implements INotifyPropertyChanged, then the UI will be alerted anytime a bound property changes, which causes the binding to get reevaluated.
For example, if you had set the DataContext initially with
MyWindow.DataContext = this;
where this was your Window, and your Window had a propety of type MyClass called Item, then you could bind the DataContext using the following
<StackPanel DataContext="{Binding Item}" ...>
and anytime you updated the property Item, your StackPanel's DataContext would also update (providing you implement INotifyPropertyChanged).
If you're interested, I like to blog about beginner concepts in WPF, and you may be interested in checking out my article What is this "DataContext" you speak of?, which is a very simple explanation of what the DataContext is and how it's used.
To summarize, WPF has two layers: the UI layer and the Data Layer. The DataContext is the data layer, and when you write {Binding SomeProperty}, you are actually binding to the data layer. Typically you set the data layer (DataContext) once in your code behind, and then use Bindings in your XAML to make your UI layer display information from the data layer.
(You may also be interested in checking out my Simple MVVM Example, which contains a very simple working code sample, and illustrates some examples of how INotifyPropertyChanged is implemented and how the UI layers and Data layers can be completely separate)
You may add a CurrentItem property in your MainWindow (or UserControl or whatever it is) and also implement INotifyPropertyChange for that property. Then set
DataContext = this;
in the MainWindow's constructor and bind like this:
Text="{Binding Path=CurrentItem.Title}"
Now whenever you set
var item = new MyClass();
...
CurrentItem = item;
the binding will be updated.
DataContext="{Binding}"
and
stackPanel1.DataContext = item;
Both do basically the same thing. The difference being that one is done in XAML and the other is in code. While the first example would allow binding to be updated given a binding parent the second one must be updated every time you want to change what the stackpanel is attached to. IMHO you should create a common binding parent to bind against. This would allow you to change the child bindings without having to set the context everytime.
<StackPanel Name="parentPanel">
<StackPanel Name="stackPanel1" DataContext="{Binding Path=Child}">
<TextBlock Name="textBlock1" Text="{Binding Path=Title, Mode=OneWay}" />
</StackPanel>
</StackPanel>
parent = new ParentClass();
parent.Child= new MyClass();
parentPanel.DataContext = parent ;
Now if notify property changed was created on ParentClass correctly you can changing the binding for the child stack panel
parent.Child= new NewClass();

Loading XAML at runtime using the MVVM pattern in WPF

This is a question that extends from the originally posted here:
Link to loading-xaml through runtime
I'm working on a WPF MVVM application that loads XAML content dynamically from an external source, very similar as the answer in the post above.
Here is what I got so far:
My View declares an instance of the ViewModel as a resource and creates an instance of that ViewModel
In my ViewModel constructor I'm loading a XamlString property coming from an external source (file or db..)
In my view I have a button that user clicks after ViewModel finishes loading and in the click-event code-behind I'm deserializing the dynamically loaded XAML and add it to my grid.
My question is, how can I eliminate code-behind and automate the logic so the View can render the new xaml section dynamically right after the ViewModel is done getting the XAML content and initializing the string property?
Should I use some kind of Messaging Bus so the ViewModel notifies once the property has been set so the View can add the new content?
What troubles me is the fact that ViewModels do have a reference to Views and should not be in charge of generating UI elements.
Thanks in advance!
Edit:
Just to clarify: in my particular case I am not trying to bind a Business Object or Collection (Model) to a UI element (e.g. Grid) which obviously could be accomplished through templates and binding. My ViewModel is retrieving a whole XAML Form from an external source and setting it as a string property available to the View. My question is: Who should be in charge of deserializing this XAML string property into a UI element and add it programmatically to the my grid once my Xaml string property in the VM is set?
This sounds to me more of like a View responsibility, not ViewModel. But the pattern as i understand it enforces to replace any code-behind logic with V-VM bindings.
I have a working solution now and I'd like to share it. Unfortunately I did not get rid of code-behind completely but it works as I expect it to. Here is how it works(simplified):
I have my simplified ViewModel:
public class MyViewModel : ViewModelBase
{
//This property implements INPC and triggers notification on Set
public string XamlViewData {get;set;}
public ViewModel()
{
GetXamlFormData();
}
//Gets the XAML Form from an external source (e.g. Database, File System)
public void GetXamlFormData()
{
//Set the Xaml String property
XamlViewData = //Logic to get XAML string from external source
}
}
Now my View:
<UserControl.Resources>
<ViewModel:MyViewModel x:Key="Model"></ViewModel:MyViewModel>
</UserControl.Resources>
<Grid DataContext="{StaticResource Model}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel>
<!-- This is the Grid used as a Place Holder to populate the dynamic content!-->
<Grid x:Name="content" Grid.Row="1" Margin="2"/>
<!-- Then create a Hidden TextBlock bound to my XamlString property. Right after binding happens I will trigger an event handled in the code-behind -->
<TextBlock Name="tb_XamlString" Text="{Binding Path=XamlViewData, Mode=TwoWay, UpdateSourceTrigger=LostFocus, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Visibility="Hidden" Loaded="tb_XamlString_Loaded" />
</StackPanel>
</Grid>
Basically I created a hidden TextBlock bound to my XAML String property in the ViewModel and I hooked its Loaded event to an event handler in the code behind of the View:
private void tb_XamlString_Loaded(object sender, RoutedEventArgs routedEventArgs)
{
//First get the ViewModel from DataContext
MyViewModel vm = content.DataContext as MyViewModel;
FrameworkElement rootObject = XamlReader.Parse(vm.XamlViewData) as FrameworkElement;
//Add the XAML portion to the Grid content to render the XAML form dynamically!
content.Children.Add(rootObject);
}
This may not be the most elegant but gets the job done. Like some people say, in MVVM there are some cases like this where little code-behind code is needed. It doesn't hurt and also part of this solution still uses the V-VM Binding principles when using the VM to retrieve and populate the XamlString property and exposing it to the View. If we would like to Unit Test the XAML parsing and loading functionality we could delegate it to a separate class.
I hope someone finds this useful!
I'm having trouble understanding what you're saying, so my answer will be based on my interpretation. You should consider posting a sample (simplified) of what you're trying to do.
1) I think you're misunderstanding what MVVM does. MVVM is mostly a binding-based pattern. Your view model should be exposing properties containing business objects and your view should just be binding to those properties. If I am misunderstanding you, and that's what you are doing, then your problem is that your view needs to be aware of when the properties get updated (after you deserialize your xaml, etc). There are two ways to do this: INotifyPropertyChanged interface on your viewmodel, or make your view model inherit from DependencyObject, and make the properties dependency properties. I won't go into details here, because this is a large subject that you should research on Google before making a decision.
2) Generally speaking, you shouldn't use click events inside your view if you're using MVVM. Instead, create properties on the view model of type ICommand (and create ICommand implementations to match, or use an implementation of DelegateCommand (google it) which will allow you to use delegates to implement the interface. The idea is, your view binds to the property and executes the handler directly inside the viewmodel.
3) If you want to push information from the viewmodel to the view, then you should create an event on the viewmodel and subscribe to it in the view, but this is a last resort, only to be used in cases like displaying a new window, etc. Generally, you should be using binding.
4) To be more specific about what you're doing, you should be binding your Grid's ItemsSource property to some property on the view model. Note, the property on the view model should be of type ObservableCollection<T> if you want to be able to add items and get instant updates.
Hope this helps.

Editable WPF ListBox

I have a ObservableCollection that's bound to a ListBox in WPF. I want the ListBox to be editable, and for the editing changes to be saved to the collection. Since WPF doesnt provide an editable listbox, I've tried creating my own by changing the ListBox.ItemTemplate.
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="EditableText" Text="{TemplateBinding Content}"/>
</DataTemplate>
</ListBox.ItemTemplate>
Changing the ItemTemplate gives me editable boxes, but any changes to the textboxes dont get saved to the ObservableCollection. Is there a way to have an editable ListBox with two way binding?
You cannot do it this way.
To achieve that kind of trick, you would need your items to be "holder classes" that expose a property you can bind your textbox to.
To understand it, imagine the following pseudo sequence of calls:
class ListBox
{
Bind(Items)
{
foreach(var item in Items)
{
DataTemplate Template = LoadTemplateForItem(item.GetType()); // this is where your template get loaded
Template.Bind(item); //this is where your template gets bound
}
}
}
Your template (the DataTemplate with the listbox) is loaded and the item (which I assume is a string in your case) gets passed in.
At this point, it only knows the string, and cannot influence anything upwards. A two-way binding cannot influence the collection because the template does not know in which context it is being used, so it cannot reach back to the original collection and modify its contents.
For that matter, this is the same thing for the TextBox. If it is not given a conainer and a property name, it has nowhere to "store back" the changes.
This basically the same as passing a string into a function call. The function cannot change which string was passed in (ignoring tricks such as by-reference argument passing).
To get back to your case, you need to build a collection of objects which expose a property containing the value that needs to be edited:
public class MyDataItem
{
string Data { get; set;}
}
Then you can bind your ListBox to a collection of those items and modifiy your datatemplate:
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="EditableText" Text="{Binding Data, Mode=TwoWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>
Bind to a model property -- i.e. a property of the data object -- rather than to a view property such as Content. For example:
// model class
public class Widget : INotifyPropertyChanged
{
public string Description { ... }
}
<!-- view -->
<DataTemplate>
<TextBox Text="{Binding Description}" />
</DataTemplate>
Note this will not work if your ItemsSource is ObservableCollection (because there's no property to bind to).

Categories