Question

I need Attached Property that sets focus to UIElement from ViewModel. I've created Attached property and in PropertyChangedCallback I set focus to UIElement.

private static void VoySetFocusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
  if (o is UIElement)
  {
    if ((bool)e.NewValue)
    {
      (o as UIElement).Focus();
      (o as UIElement).SetValue(VoySetFocusProperty, false);
    }
  }
}

but I want it to work like trigger in shotgun. I set true to Test in ViewModel ... it invokes PropertyChangedCallback in MyAttachedProperties class,sets focus to UIElement

((o as UIElement).Focus();

and value of Test in ViewModel returns to false

((o as UIElement).SetValue(VoySetFocusProperty, false);)

Everything seems to work fine but SetValue doesn't change my value in ViewModel back.

Full code:

View:

<Window x:Class="WpfAttachedProperty.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfAttachedProperty"
    Title="MainWindow" Height="127" Width="316">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto"/>
        <RowDefinition Height="auto"/>
    </Grid.RowDefinitions>
    <TextBox local:MyAttachedProperties.VoySetFocus="{Binding Path=Test,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" Text="Focus Me"/>
    <Button Grid.Row="1" Content="Click to Focus" HorizontalAlignment="Left" Margin="10" VerticalAlignment="Top" Width="75" Command="{Binding MyCommand}" />
</Grid>

Code Behind:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfAttachedProperty
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow:Window
{
  public MainWindow()
  {
    InitializeComponent();
    DataContext = new ViewModel();
  }
}

/// <summary>
/// Command Class
/// </summary>
public class DelegateCommand:ICommand
{
  private readonly Action _action;

  public DelegateCommand(Action action)
  {
    _action = action;
  }

  public void Execute(object parameter)
  {
    _action();
  }

  public bool CanExecute(object parameter)
  {
    return true;
  }

  public event EventHandler CanExecuteChanged
  {
    add
    {
    }
    remove
    {
    }
  }
}

/// <summary>
/// ViewModelClass
/// </summary>
public class ViewModel:INotifyPropertyChanged
{
  private bool _test = false;
  public bool Test
  {
    get
    {
      return _test;
    }
    set
    {
      _test = value;
      this.NotifyPropertyChanged("Test");
    }
  }
  public ICommand MyCommand
  {
    get
    {
      return new DelegateCommand(SetTestToTrue);
    }
  }
  private void SetTestToTrue()
  {
    this.Test = true;
  }

  #region INotifyPropertyChanged
  public void NotifyPropertyChanged(String PropertyName)
  {
    if (this.PropertyChanged != null)
    {
      this.PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
    }
  }
  public event PropertyChangedEventHandler PropertyChanged;
  #endregion
}


public class MyAttachedProperties
{
  public static Object GetVoySetFocus(DependencyObject obj)
  {
    return (Object)obj.GetValue(VoySetFocusProperty);
  }

public static void SetVoySetFocus(DependencyObject obj, Object value)
{
  obj.SetValue(VoySetFocusProperty, value);
}

public static readonly DependencyProperty VoySetFocusProperty =
    DependencyProperty.RegisterAttached("VoySetFocus", typeof(bool), typeof(MyAttachedProperties), new UIPropertyMetadata(false, new PropertyChangedCallback(VoySetFocusChanged), new CoerceValueCallback(CoerceVoySetFocus)));

private static object CoerceVoySetFocus(DependencyObject d, object baseValue)
{
  return (bool)baseValue;
}

private static void VoySetFocusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
  if (o is UIElement)
  {
    if ((bool)e.NewValue)
    {
      (o as UIElement).Focus();
      // Doesn't set Test to false in ViewModel
      (o as UIElement).SetValue(VoySetFocusProperty, false);
    }
  }
}
}
}

Greetings Marko.

Was it helpful?

Solution

Issue is with the line:

((o as UIElement).SetValue(VoySetFocusProperty, false);)

You should instead use SetCurrentValue to set the DP.

((o as UIElement).SetCurrentValue(VoySetFocusProperty, false);)

Explanation:

Setting the value directly of any DependencyProperty breaks the binding it with source property.

However, SetCurrentValue doesn't break the binding and push back the value back to the source property. Explanation from MSDN for SetCurrentValue:

This method is used by a component that programmatically sets the value of one of its own properties without disabling an application's declared use of the property. The SetCurrentValue method changes the effective value of the property, but existing triggers, data bindings, and styles will continue to work.


Moreover, I think setting it in PropertyChanged callback won't propagate that back to Viewmodel if do the operation synchronously since it reaches to callback from Viewmodel property setter only.

So, what we can do is do this operation asynchronously by enqueuing it on dispatcher using BeginInvoke so that it gets propagate to viewmodel Test property.

(o as UIElement).Focus();
Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate
{
    // Doesn't set Test to false in ViewModel
    (o as UIElement).SetCurrentValue(VoySetFocusProperty, false);
});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top