Bind ViewModel to Item from LongListSelector in DataTemplate - c#

I want to display a list of objects in a LongListSelector, and format them with a DataTemplate. To properly use MVVM I'd like to have a ViewModel in this DataTemplate.
Creating of this ViewModel is no problem, but how do I pass the Item to the ViewModel?
I'm using this code:
<Controls:LongListSelector
ItemsSource="{Binding MyItems}" Margin="0" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch" >
<Controls:LongListSelector.DataContext>
<viewmodel:MyListOfItemsViewModel />
</Controls:LongListSelector.DataContext>
<Controls:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="CurTemplate">
<Grid Margin="10" >
<Grid.DataContext>
<viewmodel:MyViewModel MyItem="{Binding Path=DataContext,ElementName=CurTemplate}" />
</Grid.DataContext>
But alas, the only thing that is set for MyItem is null, and this is never updated to the real value. I found out that later in the process (after the initial setting of MyItem CurTemplate does have a valid DataContext, but this is not sent to my ViewModel. Am I missing something here?
For completeness the code for MyViewModel:
public static DependencyProperty MyItemProperty = DependencyProperty.Register("MyItem", typeof(object), typeof(MyViewModel), new PropertyMetadata("asd", ItemChanged));
private static void ItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
System.Diagnostics.Debugger.Break(); // to set when something is set
// called once, NewValue is null
}
public object MyItem
{
get
{
return (object)GetValue(MyItemProperty);
}
set
{
SetValue(MyItemProperty, value);
RaisePropChangeEvent("MyItem");
}
}
I did a lot of searching and fiddling around, but I'm pretty sure this is just a minor thing that is missing here. I would be very glad if you could help me out here...
EDIT: Solved
I solved my problem by using {Binding Path=Content,RelativeSource={RelativeSource Mode=TemplatedParent}} as binding for the viewmodel. I have no idea why this works with Content, but not with DataContext...
Thanks for your help, robertftw, your linked post brought me to the right track!

Seems like the problem I had a few days ago:
Binding does not update usercontrol property correctly MVVM
public static DependencyProperty MyItemProperty = DependencyProperty.Register("MyItem", typeof(object), typeof(MyViewModel), new PropertyMetadata(string.Empty, ItemChanged));
private static void ItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var obj = d as MyViewModel;
obj.RaisePropChangeEvent("MyItem");
}
The problem I had is that the set of MyItem isn't actually called.

What are you trying to do?
Do you just want to interact to a selection change and have the selected item in the LongListSelector being pushed to the ViewModel?
If so... I'm using an extension for this scenario. The only thing with such an extension is that setting the current item from the ViewModel is not ported back to the view ( but didn't need that ).
The ViewModel change is
public RelayCommand<MyItemType> ViewModelCommand
The XAML change is
<phone:LongListSelector extensions:LongListSelectorExtension.Command="{Binding ViewModelCommand}" />
The extension
public static class LongListSelectorExtension
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command",
typeof(ICommand), typeof(LongListSelectorExtension),
new PropertyMetadata(null, OnCommandChanged));
public static ICommand GetCommand(LongListSelector selector)
{
return (ICommand)selector.GetValue(CommandProperty);
}
public static void SetCommand(LongListSelector selector, ICommand value)
{
selector.SetValue(CommandProperty, value);
}
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = d as LongListSelector;
if (selector == null)
{
throw new ArgumentException(
"You must set the Command attached property on an element that derives from LongListSelector.");
}
var oldCommand = e.OldValue as ICommand;
if (oldCommand != null)
{
selector.SelectionChanged -= OnSelectionChanged;
}
var newCommand = e.NewValue as ICommand;
if (newCommand != null)
{
selector.SelectionChanged += OnSelectionChanged;
}
}
private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selector = sender as LongListSelector;
var command = GetCommand(selector);
if (command != null && selector.SelectedItem != null)
{
command.Execute(selector.SelectedItem);
}
selector.SelectedItem = null;
}
}

Related

How can I get the binding object of TextProperty through an attached Property

I have a class called TxtBox with an attached Property:
public class TxtBox
{
public static readonly DependencyProperty TypeProperty = DependencyProperty.RegisterAttached(
"Type", typeof (Enums.FieldType), typeof (TextBox), new PropertyMetadata(default(Enums.FieldType),OnTypeChanged));
public static void SetType(DependencyObject element, Enums.FieldType value)
{
element.SetValue(TypeProperty, value);
}
public static Enums.FieldType GetType(DependencyObject element)
{
return (Enums.FieldType) element.GetValue(TypeProperty);
}
private static void OnTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var src = (TextBox) d; //(FrameworkElement)d;
var binding = BindingOperations.GetBinding(src, TextBox.TextProperty);
if (binding != null) //Binding here is always null ?????????
{
binding.Converter = new NumberConverter();
binding.ConverterParameter = e.NewValue;
}
}
}
At MainWindow.xaml :
<Grid Margin="10">
<TextBox Text="{Binding RequestNo}" att:TxtBox.Type="Number" />
<\Grid>
I need to assign the Converter and ConverterParameter for the TextProperty once I have set the type for the textbox control through the attached property (Type). When the OnTypeChanged method fires, I can't get the Binding, as it is always null !!!
Thanks in advance :)
Your attached property is being set before the Binding is applied to the Text property of the Text box. You can work around this by attempting to update the Binding when the value of Text changes:
private static void OnTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var src = (TextBox)d;
var dpd = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof(TextBox));
dpd.AddValueChanged(src, UpdateBindingHandler);
UpdateBinding(src);
}
protected static void UpdateBindingHandler(object sender, EventArgs e)
{
UpdateBinding((TextBox)sender);
}
private static void UpdateBinding(TextBox tbox)
{
var binding = BindingOperations.GetBinding(tbox, TextBox.TextProperty);
if (binding != null)
{
binding.Converter = new NumberConverter();
binding.ConverterParameter = GetType(tbox);
var dpd = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof(TextBox));
// Don't do this every time the value changes, only the first time
// it changes after TxtBox.Type has changed.
dpd.RemoveValueChanged(tbox, UpdateBindingHandler);
}
}
When you do that, you'll find that your whole design is flawed: You can't alter a Binding once it's been used. It throws an exception.
You might be able to get away with creating a new binding, clone the properties of the old one, and put a converter on it. Bindings have a lot of properties, though, and if there's already a converter, you'll need to replace it with a chain converter that preserves that one while adding yours.
I'm not sure this feature is going to work out.
Finally,I have got the solution, I changed the design as Mr Peter Duniho advised me to write a markup extension to take the place of the {Binding}
public class TextBoxTypeExtension:MarkupExtension
{
private readonly Binding _binding;
public TextBoxTypeExtension(Binding binding,Enums.FieldType type)
{
_binding = binding;
_binding.Converter = new NumberConverter();
_binding.ConverterParameter = type;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return _binding.ProvideValue(serviceProvider);
}
}
At MainWindow.xaml :
<TextBox MaxLength="10" Grid.Row="1" Grid.Column="1"
Text="{extension:TextBoxType {Binding Request.RequestNo},Number}"/>
Reference:
MarkupExtension that uses a DataBinding value

Get selected values from dxe:ComboBoxEdit

I am using a DevExpress ComboboxEdit object to get multiple selection from the user. My problem is that I am not sure what type of object will come back once a selection has been done.
I have read this one, and came up with the code below, but I am not sure what I am missing. (I also don't know exactly what a DependencyProperty is, but would like to avoid too many objects)
<Window x:Class = "Demo.MainWindow"
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:local = "clr-namespace:Demo"
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
xmlns:dxl="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol"
xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<StackPanel>
<dxe:ComboBoxEdit ItemsSource="{Binding Path=MyList}"
IsTextEditable="False"
EditValue="{Binding Path=MySelectedList, Mode=TwoWay}"
Name="abc">
<dxe:ComboBoxEdit.StyleSettings>
<dxe:CheckedComboBoxStyleSettings/>
</dxe:ComboBoxEdit.StyleSettings>
</dxe:ComboBoxEdit>
<Button Click="showSelected" Content="Show selected items" />
</StackPanel>
</Window>
MainWindow.xaml.cs
using System.Collections.Generic;
using System.Windows;
using System.Text;
namespace Demo
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private System.Collections.Generic.IList<string> _myList;
private System.Collections.Generic.IList<string> _mySelectedList; // This has probably the wrong type.
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public IList<string> MyList
{
get
{
return _myList;
}
set
{
_myList = value;
this.RaisePropertyChanged("MyList");
}
}
public IList<string> MySelectedList
{
get
{
return _mySelectedList;
}
set
{
_mySelectedList = value;
this.RaisePropertyChanged("MySelectedList");
}
}
private void showSelected(object sender, RoutedEventArgs e)
{
StringBuilder sb = new StringBuilder();
foreach(string s in this.MySelectedList)
{
sb.Append(s);
}
System.Windows.MessageBox.Show(sb.ToString());
// This MessageBox show show whatever is checked.
}
public MainWindow()
{
MySelectedList = new System.Collections.Generic.List<string>();
MyList = new System.Collections.Generic.List<string>();
MyList.Add("a");
MyList.Add("b");
MyList.Add("c");
MyList.Add("d");
DataContext = this;
}
}
}
When I run it and click the combobox, then a red X appears and says that The type System.Collection.Generic.List´1[System.Object] could not be converted. And the MessageBox is always empty.
You do not have INotifyPropertyChanged implemented on your MainWindow, but that may not be the only issue. I would read up on Dependency Properties and Data Binding before you really try to tinker with WPF. If you do not understand those concepts everything will be difficult and confusing.
EDIT
They are using a DependencyProperty (As you mentioned) it seems. But anyway, this is how you would implement one
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register("SelectedItems", typeof(IList), typeof(MainWindow), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedItemsChanged)));
private static void OnSelectedItemsChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
MainWindow mainWindow = o as MainWindow;
if (mainWindow != null)
mainWindow.OnSelectedItemsChanged((IList)e.OldValue, (IList)e.NewValue);
}
protected virtual void OnSelectedItemsChanged(IList oldValue, IList newValue)
{
// Add your property changed side-effects. Descendants can override as well.
}
public IList SelectedItems
{
// IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
get
{
return (IList)GetValue(SelectedItemsProperty);
}
set
{
SetValue(SelectedItemsProperty, value);
}
}
Notice it needs to be of type IList and you will need to cast to type string
Also, remove the Mode=TwoWay as it is not needed in your binding.
<dxe:ComboBoxEdit ItemsSource="{Binding MyList}" EditValue="{Binding SelectedItems}" >
<dxe:ComboBoxEdit.StyleSettings>
<dxe:CheckedComboBoxStyleSettings/>
</dxe:ComboBoxEdit.StyleSettings>
</dxe:ComboBoxEdit>
You also do not need INotifyPropertyChanged that was my mistake. I thought you were doing traditional binding.
EditValue property contains a list of objects, so your code in VM should look like this:
private List<object> _mySelectedList;
public List<object> MySelectedList
{
get
{
return _mySelectedList;
}
set
{
_mySelectedList = value;
this.RaisePropertyChanged("MySelectedList");
}
}
Or you can write EditValue converter, example you will find here.

How to Bind to CaretIndex aka curser position of an Textbox

Hi I'm trying to bind to the TextBox.CaretIndex property which isn't a DependencyProperty, so I created a Behavior, but it doesn't work as expected.
Expectation (when focused)
default = 0
if I change the value in my view it should change the value in my viewmodel
if I change the value in my viewmodel it should change the value in my view
Current behavior
viewmodel value gets called ones when the window opens
Code-behind
public class TextBoxBehavior : DependencyObject
{
public static readonly DependencyProperty CursorPositionProperty =
DependencyProperty.Register(
"CursorPosition",
typeof(int),
typeof(TextBoxBehavior),
new FrameworkPropertyMetadata(
default(int),
new PropertyChangedCallback(CursorPositionChanged)));
public static void SetCursorPosition(DependencyObject dependencyObject, int i)
{
// breakpoint get never called
dependencyObject.SetValue(CursorPositionProperty, i);
}
public static int GetCursorPosition(DependencyObject dependencyObject)
{
// breakpoint get never called
return (int)dependencyObject.GetValue(CursorPositionProperty);
}
private static void CursorPositionChanged(
DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
// breakpoint get never called
//var textBox = dependencyObject as TextBox;
//if (textBox == null) return;
}
}
XAML
<TextBox Text="{Binding TextTemplate,UpdateSourceTrigger=PropertyChanged}"
local:TextBoxBehavior.CursorPosition="{Binding CursorPosition}"/>
Further Information
I think there is something really wrong here because I need to derive it from DependencyObject which was never needed before, because CursorPositionProperty is already a DependencyProperty, so this should be enough. I also think I need to use some events in my Behavior to set my CursorPositionProperty correctly, but I don't know which.
After fighting with my Behavior i can present you a 99% working solution
Behavior
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfMVVMTextBoxCursorPosition
{
public class TextBoxCursorPositionBehavior : DependencyObject
{
public static void SetCursorPosition(DependencyObject dependencyObject, int i)
{
dependencyObject.SetValue(CursorPositionProperty, i);
}
public static int GetCursorPosition(DependencyObject dependencyObject)
{
return (int)dependencyObject.GetValue(CursorPositionProperty);
}
public static readonly DependencyProperty CursorPositionProperty =
DependencyProperty.Register("CursorPosition"
, typeof(int)
, typeof(TextBoxCursorPositionBehavior)
, new FrameworkPropertyMetadata(default(int))
{
BindsTwoWayByDefault = true
,DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
}
);
public static readonly DependencyProperty TrackCaretIndexProperty =
DependencyProperty.RegisterAttached(
"TrackCaretIndex",
typeof(bool),
typeof(TextBoxCursorPositionBehavior),
new UIPropertyMetadata(false
, OnTrackCaretIndex));
public static void SetTrackCaretIndex(DependencyObject dependencyObject, bool i)
{
dependencyObject.SetValue(TrackCaretIndexProperty, i);
}
public static bool GetTrackCaretIndex(DependencyObject dependencyObject)
{
return (bool)dependencyObject.GetValue(TrackCaretIndexProperty);
}
private static void OnTrackCaretIndex(DependencyObject dependency, DependencyPropertyChangedEventArgs e)
{
var textbox = dependency as TextBox;
if (textbox == null)
return;
bool oldValue = (bool)e.OldValue;
bool newValue = (bool)e.NewValue;
if (!oldValue && newValue) // If changed from false to true
{
textbox.SelectionChanged += OnSelectionChanged;
}
else if (oldValue && !newValue) // If changed from true to false
{
textbox.SelectionChanged -= OnSelectionChanged;
}
}
private static void OnSelectionChanged(object sender, RoutedEventArgs e)
{
var textbox = sender as TextBox;
if (textbox != null)
SetCursorPosition(textbox, textbox.CaretIndex); // dies line does nothing
}
}
}
XAML
<TextBox Height="50" VerticalAlignment="Top"
Name="TestTextBox"
Text="{Binding MyText}"
vm:TextBoxCursorPositionBehavior.TrackCaretIndex="True"
vm:TextBoxCursorPositionBehavior.CursorPosition="{Binding CursorPosition,Mode=TwoWay}"/>
<TextBlock Height="50" Text="{Binding CursorPosition}"/>
there is just on thing i don't know why it doesn't work => BindsTwoWayByDefault = true. it has no effect on the binding as far as i can tell you because of this i need to set the binding mode explicit in XAML
I encountered a similar problem, and the easiest solution for me was to inherit from TextBox and add a DependencyProperty. So it looks like this:
namespace UI.Controls
{
public class MyTextBox : TextBox
{
public static readonly DependencyProperty CaretPositionProperty =
DependencyProperty.Register("CaretPosition", typeof(int), typeof(MyTextBox),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnCaretPositionChanged));
public int CaretPosition
{
get { return (int)GetValue(CaretPositionProperty); }
set { SetValue(CaretPositionProperty, value); }
}
public MyTextBox()
{
SelectionChanged += (s, e) => CaretPosition = CaretIndex;
}
private static void OnCaretPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as MyTextBox).CaretIndex = (int)e.NewValue;
}
}
}
... and in my XAML:
xmlns:controls="clr-namespace:IU.Controls"
...
<controls:MyTextBox CaretPosition="{Binding CaretPosition}"/>
... and CaretPosition property in the View Model of course. If you're not going to bind your View Model to other text-editing controls, this may be sufficient, if yes - you'll probably need another solution.
The solution from WiiMaxx has the following problems for me:
The caret index in the text box is not changed when the view model property is changed from the code.
This was also mentioned by Tejas Vaishnav in his comment to the solution.
BindsTwoWayByDefault = true does not work.
He stated that it is strange that he needs to inherit from DependencyObject.
The TrackCaretIndex property is only used for initialisation and it felt kind of unnecessary.
Here is my solution which solves those problems:
Behavior
public static class TextBoxAssist
{
// This strange default value is on purpose it makes the initialization problem very unlikely.
// If the default value matches the default value of the property in the ViewModel,
// the propertyChangedCallback of the FrameworkPropertyMetadata is initially not called
// and if the property in the ViewModel is not changed it will never be called.
private const int CaretIndexPropertyDefault = -485609317;
public static void SetCaretIndex(DependencyObject dependencyObject, int i)
{
dependencyObject.SetValue(CaretIndexProperty, i);
}
public static int GetCaretIndex(DependencyObject dependencyObject)
{
return (int)dependencyObject.GetValue(CaretIndexProperty);
}
public static readonly DependencyProperty CaretIndexProperty =
DependencyProperty.RegisterAttached(
"CaretIndex",
typeof(int),
typeof(TextBoxAssist),
new FrameworkPropertyMetadata(
CaretIndexPropertyDefault,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
CaretIndexChanged));
private static void CaretIndexChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
{
if (dependencyObject is not TextBox textBox || eventArgs.OldValue is not int oldValue || eventArgs.NewValue is not int newValue)
{
return;
}
if (oldValue == CaretIndexPropertyDefault && newValue != CaretIndexPropertyDefault)
{
textBox.SelectionChanged += SelectionChangedForCaretIndex;
}
else if (oldValue != CaretIndexPropertyDefault && newValue == CaretIndexPropertyDefault)
{
textBox.SelectionChanged -= SelectionChangedForCaretIndex;
}
if (newValue != textBox.CaretIndex)
{
textBox.CaretIndex = newValue;
}
}
private static void SelectionChangedForCaretIndex(object sender, RoutedEventArgs eventArgs)
{
if (sender is TextBox textBox)
{
SetCaretIndex(textBox, textBox.CaretIndex);
}
}
}
XAML
<TextBox Height="50" VerticalAlignment="Top"
Name="TestTextBox"
Text="{Binding MyText}"
viewModels:TextBoxAssist.CaretIndex="{Binding CaretIndex}"/>
Some clarifications for the differences:
View model property changes work now because the caret index on the TextBox is set at the end of CaretIndexChanged.
The BindsTwoWayByDefault was fixed by using the according FrameworkPropertyMetadata constructor parameter.
Inheriting from DependencyObject was only necessary because DependencyProperty.Register was used instead of DependencyProperty.RegisterAttached.
Without the TrackCaretIndex property I had the problem that the propertyChangedCallback for the FrameworkPropertyMetadata was never called to properly initialize things. The problem occurs only when the default value for the FrameworkPropertyMetadata match the value of the view model property right from the start and the view model property is not changed. That's why I used this random default value.
As you said, the TextBox.CaretIndex Property is not a DependencyProperty, so you cannot data bind to it. Even with your own DependencyProperty, it won't work... how would you expect to be notified when TextBox.CaretIndex Property changes?

WPF ListView scroll from view model

I have observable collection called (Users) in view model that binded with ListViewControl (lstUsers) in view and what I need is to scroll to current logged in user in List View .
I see in most of examples that used scroll from code behind as following e.g. :
lstUsers.ScrollIntoView(lstUsers[5]);
but what I need is to handle it from view model .
Please advice !
One way of doing this would be to use something like an ICollectionView which has a current item. You can then set IsSynchronizedWithCurrentItem to true to link the current item in the view model to the selected item in the ListView.
Finally handle the event SelectionChanged in the code behind the view to change the scroll position so that it always displays the selected item.
For me the benefit of this method is that the viewmodel is kept unaware of anything about the view which is one of the aims of MVVM. The code behind the view is the perfect place for any code concerning the view only.
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView x:Name="View"
SelectionChanged="Selector_OnSelectionChanged" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Items}"/>
<Button Grid.Row="1" Command="{Binding ChangeSelectionCommand}">Set</Button>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
View.ScrollIntoView(View.SelectedItem);
}
}
public class ViewModel
{
private readonly CollectionViewSource _source = new CollectionViewSource();
public ICollectionView Items
{
get { return _source.View; }
}
public ICommand ChangeSelectionCommand { get; set; }
public ViewModel()
{
SetUp();
ChangeSelectionCommand = new Command(ChangeSelection);
}
private void SetUp()
{
var list = new List<string>();
for (int i = 0; i < 100; i++)
{
list.Add(i.ToString(CultureInfo.InvariantCulture));
}
_source.Source = list;
}
private void ChangeSelection()
{
var random = new Random(DateTime.Now.Millisecond);
var n = random.Next(100);
Items.MoveCurrentToPosition(n);
}
}
public class Command : ICommand
{
private readonly Action _action;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_action();
}
public event EventHandler CanExecuteChanged;
public Command(Action action)
{
_action = action;
}
}
let me share my solution with you
Create your own ListView descendant with dependency property TargetListItem
public class ScrollableListView : ListView
{
/// <summary>
/// Set this property to make ListView scroll to it
/// </summary>
public object TargetListItem
{
get { return (object)GetValue(TargetListItemProperty); }
set { SetValue(TargetListItemProperty, value); }
}
public static readonly DependencyProperty TargetListItemProperty = DependencyProperty.Register(
nameof(TargetListItem), typeof(object), typeof(ScrollableListView), new PropertyMetadata(null, TargetListItemPropertyChangedCallback));
static void TargetListItemPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var owner = (ScrollableListView)d;
owner.ScrollToItem(e.NewValue);
}
public void ScrollToItem(object value)
{
if (value != null && Items != null && Items.Contains(value))
{
ScrollIntoView(value);
}
}
}
create property in ViewModel
object currentListItem;
public object СurrentListItem
{
get => сurrentListItem;
set
{
if (сurrentListItem != value)
{
сurrentListItem = value;
OnPropertyChanged(nameof(СurrentListItem));
}
}
}
bind it
<controls:ScrollableListView ... TargetListItem="{Binding CurrentListItem}"/>
Now you can set CurrentListItem in ViewModel when needed. And the corresponding visual element will become visible in the ListView immediately.
Also maybe you just can use attached property on ListView instead of creating ScrollableListView. But i'm not sure.
Yep, there's always times in MVVM when you need to get at the control. There's various ways of doing this, but here's an easy-ish way of doing it without deriving from the control or messing with routed commands or other such toys what you have in WPF.
In summary:
Create an attached property on your view model.
Set the attached property in XAML to pass the list box back to the view model.
Call .ScrollIntoView on demand.
Note, this is a rough and ready example, make sure your DataContext is set before showing the window.
Code/View Model:
public class ViewModel
{
private ListBox _listBox;
private void ReceiveListBox(ListBox listBox)
{
_listBox = listBox;
}
public static readonly DependencyProperty ListBoxHookProperty = DependencyProperty.RegisterAttached(
"ListBoxHook", typeof (ListBox), typeof (ViewModel), new PropertyMetadata(default(ListBox), ListBoxHookPropertyChangedCallback));
private static void ListBoxHookPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var listBox = (ListBox) dependencyObject;
var viewModel = (ViewModel) listBox.DataContext;
viewModel.ReceiveListBox(listBox);
}
public static void SetListBoxHook(DependencyObject element, ListBox value)
{
element.SetValue(ListBoxHookProperty, value);
}
public static ListBox GetListBoxHook(DependencyObject element)
{
return (ListBox) element.GetValue(ListBoxHookProperty);
}
}
OK, so that will let us get the ListBox passed back to the view; you can do with it as you wish.
Now, just set the property in XAML:
<ListBox wpfApplication1:ViewModel.ListBoxHook="{Binding RelativeSource={RelativeSource Self}}" />
Good to go!

Windows 8 XAML Databinding on updating on text changed

I have a windows 8 XAML/C# application using the MVVM pattern.
All my textboxes on the form have their text properties bound to properties on my MVVM class.
So, one of my textboxes looks like this:
<TextBox x:Name="textAddressLine1" Text="{Binding AddressLine1, Mode=TwoWay}"/>
And that property on the MVVM class looks like this:
private string addressLine1;
public string AddressLine1
{
get { return addressLine1; }
set
{
if (addressLine1 == value)
{
return;
}
addressLine1 = value;
RaisePropertyChanged("AddressLine1");
}
}
As I type into my textbox the MVVM class isn't updated. It only gets updated once the focus moves to a different control.
How can I update the MVVM class property when the text changes on my textbox?
Thanks in advance
Use explicit binding for
textAddressLine1
<TextBox x:Name="textAddressLine1" Text="{Binding AddressLine1,UpdateSourceTrigger=Explicit, Mode=TwoWay}" TextChanged="textAddressLine1_Changed"/>
private void textAddressLine1_Changed(object sender, RoutedEventArgs e)
{
BindingExpression be = textAddressLine1.GetBindingExpression(TextBox.TextProperty);
be.UpdateTarget();
}
I didn't test the code but should work.
EDIT: I see it UpdateSourceTrigger is not exist for environtment
You can create a your viewmodel as instance and give it as datacontext by the way you can easily perform your viewmodel from your code-behind. For this type cases it saves the day!
public MyClassViewModel ViewModel {get;set}
ctor()
{
this.ViewModel=new MyClassViewModel();
this.DataContext=this.ViewModel;
InitializeComponets();
}
private void textAddressLine1_Changed(object sender, RoutedEventArgs e)
{
this.ViewModel.AddressLine1=textAddressLine1.Text;
}
I think you will not need two way by this way. OneWay is ok. Because you explicitly change VM.
Note:I coded on SO, didn't test again.Hope helps!
I had the same issue, I found here: https://stackoverflow.com/a/11676076/4551080
<TextBox Text="{Binding Path=EmailAddress, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
So make sure to set UpdateSourceTrigger=PropertyChanged Default Value is LostFocus
Use this workaround:
public class ExtendedTextBox : TextBox
{
public static readonly DependencyProperty CustomActionProperty =
DependencyProperty.Register(
"CustomAction",
typeof(Action<string>),
typeof(ExtendedTextBox),
new PropertyMetadata(null, OnPropertyChanged));
public Action<string> CustomAction
{
get
{
return (Action<string>)GetValue(CustomActionProperty);
}
set
{
SetValue(CustomActionProperty, value);
}
}
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(e.NewValue != null)
(d as ExtendedTextBox).TextChanged += ExtendedTextBox_TextChanged;
else
(d as ExtendedTextBox).TextChanged -= ExtendedTextBox_TextChanged;
}
async static void ExtendedTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
await CoreWindow.GetForCurrentThread().Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => (sender as ExtendedTextBox).CustomAction((sender as ExtendedTextBox).Text));
}
}
IN your model:
public Action<string> UpdateBindedViewModelProperty
{
get { return new Action<string>((value) => NewLabelName = value); }
}
and view:
<plmrfc:extendedtextbox customaction="{Binding UpdateBindedViewModelProperty, Mode=OneTime}" text="{Binding Path=NewLabelName, Mode=TwoWay}" width="200" x:name="Label_TextBox"></plmrfc:extendedtextbox>
There is also another way, which does not involve subclassing TextBox. Maybe you like this one more:
using System.Reflection;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Flexman
{
public class TextBoxUpdateSourceBehaviour
{
private static PropertyInfo _boundProperty;
public static readonly DependencyProperty BindingSourceProperty =
DependencyProperty.RegisterAttached(
"BindingSource",
typeof(string),
typeof(TextBoxUpdateSourceBehaviour),
new PropertyMetadata(default(string), OnBindingChanged));
public static void SetBindingSource(TextBox element, string value)
{
element.SetValue(BindingSourceProperty, value);
}
public static string GetBindingSource(TextBox element)
{
return (string)element.GetValue(BindingSourceProperty);
}
private static void OnBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var txt = d as TextBox;
if (txt == null)
return;
txt.Loaded += OnLoaded;
txt.TextChanged += OnTextChanged;
}
static void OnLoaded(object sender, RoutedEventArgs e)
{
var txt = sender as TextBox;
if (txt == null)
return;
// Reflect the datacontext of the textbox to find the field to bind to.
var dataContextType = txt.DataContext.GetType();
_boundProperty = dataContextType.GetRuntimeProperty(GetBindingSource(txt));
// If you want the behaviour to handle your binding as well, uncomment the following.
//var binding = new Binding();
//binding.Mode = BindingMode.TwoWay;
//binding.Path = new PropertyPath(GetBindingSource(txt));
//binding.Source = txt.DataContext;
//BindingOperations.SetBinding(txt, TextBox.TextProperty, binding);
}
static void OnTextChanged(object sender, TextChangedEventArgs e)
{
var txt = sender as TextBox;
if (txt == null)
return;
if (_boundProperty.GetValue(txt.DataContext).Equals(txt.Text)) return;
_boundProperty.SetValue(txt.DataContext, txt.Text);
}
}
}
and view
<TextBox Text="{Binding Username}" Flexman:TextBoxUpdateSourceBehaviour.BindingSource="Username" />
This is the prettiest solution I know of. Others will be "non-generic" hacks.
Good luck. I do agree that it's major downgrade from silverlight/WPF but hey, there are a lot of more horrible things in WinRT that are missing in WPF :)

Categories

Resources