web-dev-qa-db-fra.com

Comment connecter l'événement et la commande TextChanged de TextBox afin d'utiliser le modèle MVVM dans Silverlight

Récemment, j'ai réalisé que le motif MVVM était très utile pour l'application Silverlight et j'ai étudié comment l'adopter dans mon projet.

BTW, comment connecter l'événement textChanged de la zone de texte avec Command? Il existe une propriété de commande pour Button, mais Textbox n'a pas de propriété commapd. Si les contrôles ne possèdent pas de propriété de commande, comment combiner l'événement ICommand et Control?

J'ai suivi le code xaml

<UserControl.Resources>
        <vm:CustomerViewModel x:Key="customerVM"/>    
    </UserControl.Resources>

    <Grid x:Name="LayoutRoot" 
          Background="White" 
          DataContext="{Binding Path=Customers, Source={StaticResource customerVM}, Mode=TwoWay}" >

        <StackPanel>
            <StackPanel Orientation="Horizontal"
                        Width="300"
                        HorizontalAlignment="Center">
                <TextBox x:Name="tbName" 
                         Width="50" 
                         Margin="10"/>
                <Button Width="30" 
                        Margin="10" 
                        Content="Find"
                        Command="{Binding Path=GetCustomersByNameCommand, Source={StaticResource customerVM}}"
                        CommandParameter="{Binding Path=Text, ElementName=tbName}"/>
            </StackPanel>
            <sdk:DataGrid ItemsSource="{Binding Path=DataContext, ElementName=LayoutRoot}"
                          AutoGenerateColumns="True"
                          Width="300"
                          Height="300"/>
        </StackPanel>
    </Grid>

Ce que j'essaie de faire, c'est que si l'utilisateur entre du texte dans la zone de texte, les données seront affichées dans la grille de données au lieu d'utiliser un clic de souris. Je sais qu'il existe un contrôle de boîte à complétion automatique intégré. Cependant, je souhaite sait comment appeler la propriété Command dans la classe ViewModel des contrôles qui ne possèdent pas la propriété Command telle que textbox.

Merci 

15
Ray

Pourquoi ne pas simplement lier la propriété Text à une propriété de votre modèle de vue? De cette façon, vous êtes averti de la modification et vous obtenez également la nouvelle valeur:

public string MyData
{
    get { return this.myData; }
    set
    {
        if (this.myData != value)
        {
            this.myData = value;
            this.OnPropertyChanged(() => this.MyData);
        }
    }
}

XAML:

<TextBox Text="{Binding MyData}"/>
22
Kent Boogaart

Voici le moyen le plus simple. Liez votre zone de texte à la propriété du modèle de vue, comme décrit ci-dessus. Ensuite, ajoutez simplement un événement code-behind (oui, je parle de code-behind avec MVVM, ce n'est pas la fin du monde) dans la zone de texte. Ajoutez un événement TextChanged, puis actualisez simplement la liaison. 

Au total, vous aurez quelque chose comme ceci pour un modèle de vue: 

public class MyViewModel 
{
    private string _myText;

    public string MyText 
    {
        get { return _myText; }
        set 
        {
            _myText = value;
            RaisePropertyChanged("MyText"); // this needs to be implemented
            // now do whatever grid refresh/etc
        }
    }
}

Dans votre XAML, vous aurez ceci:

<TextBox Text="{Binding MyText,Mode=TwoWay}" TextChanged="TextBox_TextChanged"/>

Enfin, dans le code derrière, faites simplement ceci:

public void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
   var binding = ((TextBox)sender).GetBindingExpression(TextBox.TextProperty);
   binding.UpdateSource();
}

Votre propriété sera mise à jour à tout moment si le texte est modifié. }

20
Jeremy Likness

Voici la façon de faire MvvmLight! Le mérite revient à GalaSoft Laurent Bugnion. 

<sdk:DataGrid Name="dataGrid1" Grid.Row="1"
    ItemsSource="{Binding Path=CollectionView}"
    IsEnabled="{Binding Path=CanLoad}"
    IsReadOnly="True">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <cmd:EventToCommand
                Command="{Binding SelectionChangedCommand}"
                CommandParameter="{Binding SelectedItems, ElementName=dataGrid1}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</sdk:DataGrid>

Source: http://blog.galasoft.ch/archive/2010/05/19/handling-datagrid.selecteditems-in-an-mvvm-friendly-manner.aspx

9
Doguhan Uluca

Dans la section de définition, nous ajoutons:

xmlns:i="http://schemas.Microsoft.com/expression/2010/interactivity"

Si vous utilisez la variable TextBox, ajoutez la référence à l'événement que nous voulons détecter:

<TextBox Text="{Binding TextPrintersFilter}">
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="TextChanged">          
      <i:InvokeCommandAction Command="{Binding FilterTextChangedCommand}"/>
    </i:EventTrigger>
  </i:Interaction.Triggers>
</TextBox>

Dans le ViewModel, ajoutez le code pour Commad:

public ICommand FilterTextChangedCommand
{
  get
  {
    if (this._filterTextChangedCommand == null)
    {
      this._filterTextChangedCommand =
        new RelayCommand(param => this.OnRequestFilterTextChanged());
    }
    return this._filterTextChangedCommand;
  }
}

private void OnRequestFilterTextChanged()
{
  // Add code
}

N'oubliez pas d'exécuter le texte contraignant:

private string _textPrintersFilter;
public string TextPrintersFilter
{
  get { return _textPrintersFilter; }
  set
  {
    _textPrintersFilter = value;
    this.RaisePropertyChange(nameof(TextPrintersFilter));
  }
}
4
Adel

Simplement utiliser 

<TextBox Text="{Binding MyText,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
4
Vivek Maskara

Par souci de conversation, supposons que vous ayez besoin de relier un événement arbitraire à une commande plutôt que de lier directement une propriété sur le ViewModel (en raison d'un manque de prise en charge du contrôle ou de la structure, d'un défaut, etc.). peut être fait dans le codebehind. Contrairement à certaines idées fausses, MVVM n’empêche pas codeback. Il est juste important de se rappeler que la logique dans le code ci-dessous ne devrait pas toucher plusieurs couches, mais plutôt se rapporter directement à l'interface utilisateur et à la technologie d'interface utilisateur utilisée. (Notez toutefois que le fait de placer 95% de votre travail dans le fichier de balisage peut rendre peu utile la présence de certaines fonctionnalités dans le codebehind, de sorte qu'un commentaire ou deux dans le balisage à propos de cette implémentation unique de codebehind peut faciliter les choses. sur la route pour vous-même ou pour les autres.)

Il y a généralement deux parties pour lier une commande dans codebehind. Tout d'abord, vous devez répondre à l'événement. Deuxièmement, vous souhaiterez peut-être établir un lien avec la propriété CanExecute de la commande.

// Execute the command from the codebehind
private void HandleTheEvent(Object sender, EventArgs e)
{
    var viewModel = DataContext as ViewModel;
    if (viewModel != null)
    {
        var command = viewModel.SomeCommand;
        command.Execute(null);
    }
}

// Listen for the command's CanExecuteChanged event
// Remember to call this (and unhook events as well) whenever the ViewModel instance changes
private void ListenToCommandEvent()
{
    var viewModel = DataContext as ViewModel;
    if (viewModel != null)
    {
        var command = viewModel.SomeCommand;
        command.CanExecuteChanged += (o, e) => EnableOrDisableControl(command.CanExecute(null));
    }
}
3
avidgator

Je l'ai résolu en liant une propriété sur mon modèle de vue et en définissant UpdateSourceTrigger de la liaison à PropertyChanged. La propriété prend en charge INotifyPropertyChanged.

Dans mon modèle de vue, je souscris ensuite à l'événement PropertyChanged pour la propriété. Lorsque cela déclenche, j'effectue les tâches que je dois effectuer (dans mon cas, mettre à jour une collection) et à la fin, j'appelle PropertyChanged sur la propriété que mes autres éléments de la vue écoutent.

0
Mike Cheel

Vous devez utiliser un comportement pour exécuter la commande:

public class CommandBehavior : TriggerAction<FrameworkElement>
{
    public static readonly DependencyProperty CommandBindingProperty = DependencyProperty.Register(
        "CommandBinding",
        typeof(string),
        typeof(CommandBehavior),
        null);

    public string CommandBinding
    {
        get { return (string)GetValue(CommandBindingProperty); }
        set { SetValue(CommandBindingProperty, value); }
    }

    private ICommand _action;

    protected override void OnAttached()
    {
        DataContextChangedHandler.Bind(AssociatedObject, _ProcessCommand);
    }

    private void _ProcessCommand(FrameworkElement obj)
    {
        if (AssociatedObject != null)
        {

            var dataContext = AssociatedObject.DataContext;

            if (dataContext != null)
            {
                var property = dataContext.GetType().GetProperty(CommandBinding);
                if (property != null)
                {
                    var value = property.GetValue(dataContext, null);
                    if (value != null && value is ICommand)
                    {
                        _action = value as ICommand;
                        if (AssociatedObject is Control)
                        {
                            var associatedControl = AssociatedObject as Control;
                            associatedControl.IsEnabled = _action.CanExecute(null);
                            _action.CanExecuteChanged +=
                                (o, e) => associatedControl.IsEnabled = _action.CanExecute(null);
                        }

                    }
                }
            }
        }
    }

    protected override void Invoke(object parameter)
    {
        if (_action != null && _action.CanExecute(parameter))
        {
            _action.Execute(parameter);
        }
    }
}

public static class DataContextChangedHandler
{
    private const string INTERNAL_CONTEXT = "InternalDataContext";
    private const string CONTEXT_CHANGED = "DataContextChanged";

    public static readonly DependencyProperty InternalDataContextProperty =
        DependencyProperty.Register(INTERNAL_CONTEXT,
                                    typeof(Object),
                                    typeof(FrameworkElement),
                                    new PropertyMetadata(_DataContextChanged));

    public static readonly DependencyProperty DataContextChangedProperty =
        DependencyProperty.Register(CONTEXT_CHANGED,
                                    typeof(Action<FrameworkElement>),
                                    typeof(FrameworkElement),
                                    null);


    private static void _DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var control = (FrameworkElement)sender;
        var handler = (Action<FrameworkElement>)control.GetValue(DataContextChangedProperty);
        if (handler != null)
        {
            handler(control);
        }
    }

    public static void Bind(FrameworkElement control, Action<FrameworkElement> dataContextChanged)
    {
        control.SetBinding(InternalDataContextProperty, new Binding());
        control.SetValue(DataContextChangedProperty, dataContextChanged);
    }
}

Maintenant vous pouvez "Lier" votre commande en xaml:

        <TextBox Text="{Binding SearchText, Mode=TwoWay}" >
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="TextChanged">
                    <utils:CommandBehavior CommandBinding="SearchCommand" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </TextBox>

Si vous avez besoin, vous pouvez étendre ce comportement avec des propriétés supplémentaires, par exemple si vous avez besoin de l'expéditeur ou du DataContext d'un autre élément. 

Cordialement, Tamás

(J'ai trouvé ceci sur un blog, mais je ne me souviens plus de son adresse)

0
abtamas

Jeremy a répondu. Cependant, si vous voulez réduire le code derrière, faites quelque chose comme ça. Dans votre modèle de vue:

public class MyViewModel 
{
    private string _myText;

    public string MyText 
    {
        get { return _myText; }
        set 
        {
            _myText = value;
            RaisePropertyChanged("MyText"); // this needs to be implemented
            // now do whatever grid refresh/etc
        }
    }
    public void TextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        var binding = ((TextBox)sender).GetBindingExpression(TextBox.TextProperty);
        binding.UpdateSource();
    }
}

Puis dans le code derrière:

public void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
    YourViewModel.TextBox_TextChanged(sender, e);
}

Je sais que c'est du code en double, mais si c'est ce que vous voulez, alors le voici.

0
Marc2001