WPF CommandParameter è NULL la prima volta che viene chiamato CanExecute


86

Ho riscontrato un problema con WPF e comandi associati a un pulsante all'interno del DataTemplate di un ItemsControl. Lo scenario è abbastanza semplice. ItemsControl è associato a un elenco di oggetti e desidero essere in grado di rimuovere ogni oggetto nell'elenco facendo clic su un pulsante. Il pulsante esegue un comando e il comando si occupa della cancellazione. CommandParameter è associato all'oggetto che desidero eliminare. In questo modo so cosa ha cliccato l'utente. Un utente dovrebbe essere in grado di eliminare solo i propri "oggetti", quindi è necessario eseguire alcuni controlli nella chiamata "CanExecute" del comando per verificare che l'utente disponga delle autorizzazioni corrette.

Il problema è che il parametro passato a CanExecute è NULL la prima volta che viene chiamato, quindi non posso eseguire la logica per abilitare / disabilitare il comando. Tuttavia, se lo rendo sempre abilitato e quindi faccio clic sul pulsante per eseguire il comando, CommandParameter viene passato correttamente. Quindi ciò significa che l'associazione con CommandParameter funziona.

Il codice XAML per ItemsControl e DataTemplate ha il seguente aspetto:

<ItemsControl 
    x:Name="commentsList"
    ItemsSource="{Binding Path=SharedDataItemPM.Comments}"
    Width="Auto" Height="Auto">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <Button                             
                    Content="Delete"
                    FontSize="10"
                    Command="{Binding Path=DataContext.DeleteCommentCommand, ElementName=commentsList}" 
                    CommandParameter="{Binding}" />
            </StackPanel>                       
         </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Quindi, come puoi vedere, ho un elenco di oggetti Commenti. Voglio che il CommandParameter di DeleteCommentCommand sia associato all'oggetto Command.

Quindi immagino che la mia domanda sia: qualcuno ha mai sperimentato questo problema prima? CanExecute viene chiamato sul mio comando, ma la prima volta il parametro è sempre NULL - perché?

Aggiornamento: sono riuscito a restringere un po 'il problema. Ho aggiunto un ValueConverter di debug vuoto in modo da poter generare un messaggio quando CommandParameter è associato ai dati. Si scopre che il problema è che il metodo CanExecute viene eseguito prima che CommandParameter sia associato al pulsante. Ho provato a impostare CommandParameter prima del comando (come suggerito), ma ancora non funziona. Eventuali suggerimenti su come controllarlo.

Update2: esiste un modo per rilevare quando l'associazione è "terminata", in modo da poter forzare la rivalutazione del comando? Inoltre, è un problema avere più pulsanti (uno per ogni elemento in ItemsControl) che si legano alla stessa istanza di un oggetto Command?

Update3: ho caricato una riproduzione del bug sul mio SkyDrive: http://cid-1a08c11c407c0d8e.skydrive.live.com/self.aspx/Code%20samples/CommandParameterBinding.zip


Ho esattamente lo stesso problema, con un ListBox.
Hadi Eskandari

C'è un bug report attualmente aperto contro WPF per questo problema: github.com/dotnet/wpf/issues/316
UuDdLrLrSs

Risposte:


14

Mi sono imbattuto in un problema simile e l'ho risolto usando il mio fidato TriggerConverter.

public class TriggerConverter : IMultiValueConverter
{
    #region IMultiValueConverter Members

    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        // First value is target value.
        // All others are update triggers only.
        if (values.Length < 1) return Binding.DoNothing;
        return values[0];
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}

Questo convertitore di valori accetta un numero qualsiasi di parametri e restituisce il primo di essi come valore convertito. Quando viene utilizzato in un MultiBinding nel tuo caso, è simile al seguente.

<ItemsControl 
    x:Name="commentsList"
    ItemsSource="{Binding Path=SharedDataItemPM.Comments}"
    Width="Auto" Height="Auto">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <Button                             
                    Content="Delete"
                    FontSize="10"
                    CommandParameter="{Binding}">
                    <Button.Command>
                        <MultiBinding Converter="{StaticResource TriggerConverter}">
                            <Binding Path="DataContext.DeleteCommentCommand"
                                     ElementName="commentsList" />
                            <Binding />
                        </MultiBinding> 
                    </Button.Command>
                </Button>
            </StackPanel>                                       
         </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Dovrai aggiungere TriggerConverter come risorsa da qualche parte affinché funzioni. Ora la proprietà Command viene impostata non prima che il valore per CommandParameter sia diventato disponibile. Puoi anche eseguire il binding a RelativeSource.Self e CommandParameter invece di. per ottenere lo stesso effetto.


2
Questo ha funzionato per me. Non capisco perchè. Qualcuno può spiegare?
TJKjaer

Non funziona perché CommandParameter è associato prima del comando? Dubito che tu abbia bisogno del convertitore ...
MBoros

2
Questa non è una soluzione. Questo è un trucco? Che diavolo sta succedendo? Questo funzionava?
Giordania

Perfetto, funziona per me! La magia è nella riga <Binding />, che fa sì che il binding del comando venga aggiornato quando il modello di dati cambia (che è associato al parametro del comando)
Andreas Kahler

56

Ho riscontrato lo stesso problema durante il tentativo di associazione a un comando sul mio modello di visualizzazione.

L'ho modificato per utilizzare un'associazione di origine relativa anziché fare riferimento all'elemento in base al nome e questo ha funzionato. L'associazione dei parametri non è cambiata.

Vecchio codice:

Command="{Binding DataContext.MyCommand, ElementName=myWindow}"

Nuovo codice:

Command="{Binding DataContext.MyCommand, RelativeSource={RelativeSource AncestorType=Views:MyView}}"

Aggiornamento : ho appena riscontrato questo problema senza utilizzare ElementName, mi sto associando a un comando sul mio modello di visualizzazione e il mio contesto dati del pulsante è il mio modello di visualizzazione. In questo caso ho dovuto spostare semplicemente l'attributo CommandParameter prima dell'attributo Command nella dichiarazione Button (in XAML).

CommandParameter="{Binding Groups}"
Command="{Binding StartCommand}"

42
Spostare CommandParameter davanti al comando è la migliore risposta su questo thread.
BSick7

6
Spostare l'ordine degli attributi non ci ha aiutato. Sarei sorpreso se avesse un effetto sull'ordine di esecuzione.
Jack Ukleja

3
Non so perché funziona. Sembra che non dovrebbe, ma lo fa totalmente.
RMK

1
Ho avuto lo stesso problema: RelativeSource non ha aiutato, cambiando l'ordine degli attributi sì. Grazie per l'aggiornamento!
Grant Crofton

14
Come persona che usa religiosamente le estensioni per abbellire automaticamente XAML (dividere gli attributi su più righe, correggere il rientro, riordinare gli attributi), la proposta di cambiare l'ordine di CommandParametere Commandmi spaventa.
Guttsy

29

Ho scoperto che l'ordine in cui ho impostato Command e CommandParameter fa la differenza. L'impostazione della proprietà Command causa la chiamata immediata di CanExecute, quindi è necessario che CommandParameter sia già impostato a quel punto.

Ho scoperto che cambiare l'ordine delle proprietà in XAML può effettivamente avere un effetto, anche se non sono sicuro che risolverà il tuo problema. Vale la pena provare, però.

Sembra che tu stia suggerendo che il pulsante non viene mai abilitato, il che è sorprendente, poiché mi aspetterei che CommandParameter venga impostato subito dopo la proprietà Command nel tuo esempio. La chiamata a CommandManager.InvalidateRequerySuggested () causa l'abilitazione del pulsante?


3
Ho provato a impostare CommandParameter prima del comando - esegue ancora CanExecute, ma passa ancora NULL ... Peccato - ma grazie per il suggerimento. Inoltre, chiamando CommandManager.InvalidateRequerySuggested (); non fa alcuna differenza.
Jonas Follesø

CommandManager.InvalidateRequerySuggested () ha risolto un problema simile per me. Grazie!
MJS

13

Ho escogitato un'altra opzione per aggirare questo problema che volevo condividere. Poiché il metodo CanExecute del comando viene eseguito prima che venga impostata la proprietà CommandParameter, ho creato una classe helper con una proprietà associata che forza la chiamata di nuovo al metodo CanExecute quando l'associazione cambia.

public static class ButtonHelper
{
    public static DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached(
        "CommandParameter",
        typeof(object),
        typeof(ButtonHelper),
        new PropertyMetadata(CommandParameter_Changed));

    private static void CommandParameter_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = d as ButtonBase;
        if (target == null)
            return;

        target.CommandParameter = e.NewValue;
        var temp = target.Command;
        // Have to set it to null first or CanExecute won't be called.
        target.Command = null;
        target.Command = temp;
    }

    public static object GetCommandParameter(ButtonBase target)
    {
        return target.GetValue(CommandParameterProperty);
    }

    public static void SetCommandParameter(ButtonBase target, object value)
    {
        target.SetValue(CommandParameterProperty, value);
    }
}

E poi sul pulsante a cui vuoi associare un parametro di comando ...

<Button 
    Content="Press Me"
    Command="{Binding}" 
    helpers:ButtonHelper.CommandParameter="{Binding MyParameter}" />

Spero che questo forse aiuti qualcun altro con il problema.


Ben fatto, grazie. Non riesco a credere che M $ non abbia risolto questo problema dopo 8 anni. Terribile!
McGarnagle

8

Questo è un vecchio thread, ma poiché Google mi ha portato qui quando ho avuto questo problema, aggiungerò ciò che ha funzionato per me per un DataGridTemplateColumn con un pulsante.

Cambia l'associazione da:

CommandParameter="{Binding .}"

per

CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}"

Non sono sicuro del perché funzioni, ma per me lo ha fatto.


Ho provato entrambe le risposte con il punteggio più alto sopra, ma questa ha funzionato solo per me. Sembra che sia un problema interno del controllo stesso non il vincolante, ma ancora molte persone lo hanno capito lavorando con le risposte sopra. Grazie!
Javidan

6

Recentemente ho riscontrato lo stesso problema (per me era per le voci di menu in un menu contestuale), e anche se potrebbe non essere una soluzione adatta per ogni situazione, ho trovato un modo diverso (e molto più breve!) Di risolverlo problema:

<MenuItem Header="Open file" Command="{Binding Tag.CommandOpenFile, IsAsync=True, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding Name}" />

Ignorando la Tagsoluzione alternativa basata sul caso speciale del menu contestuale, la chiave qui è associare CommandParameterregolarmente, ma associare Commandl'addizionale IsAsync=True. Ciò ritarderà CanExecuteun po ' il binding del comando effettivo (e quindi la sua chiamata), quindi il parametro sarà già disponibile. Ciò significa, tuttavia, che per un breve momento lo stato abilitato potrebbe essere sbagliato, ma per il mio caso è stato perfettamente accettabile.


5

Potresti essere in grado di usare il mio CommandParameterBehaviorche ho pubblicato ieri sui forum di Prism . Aggiunge il comportamento mancante in cui una modifica alla CommandParametercausa Commanddeve essere richiesta nuovamente.

C'è una certa complessità qui causata dai miei tentativi di evitare la perdita di memoria causata se chiami PropertyDescriptor.AddValueChangedsenza chiamare in seguito PropertyDescriptor.RemoveValueChanged. Provo a risolverlo annullando la registrazione del gestore quando l'ekement viene scaricato.

Probabilmente dovrai rimuovere il IDelegateCommandmateriale a meno che tu non stia usando Prism (e desideri apportare le mie stesse modifiche alla libreria Prism). Nota anche che generalmente non usiamo RoutedCommands qui (usiamo Prism DelegateCommand<T>per praticamente tutto), quindi per favore non CommandManager.InvalidateRequerySuggestedritenermi responsabile se la mia chiamata a innescare una sorta di cascata di collasso con funzione d'onda quantistica che distrugge l'universo conosciuto o altro.

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;

namespace Microsoft.Practices.Composite.Wpf.Commands
{
    /// <summary>
    /// This class provides an attached property that, when set to true, will cause changes to the element's CommandParameter to 
    /// trigger the CanExecute handler to be called on the Command.
    /// </summary>
    public static class CommandParameterBehavior
    {
        /// <summary>
        /// Identifies the IsCommandRequeriedOnChange attached property
        /// </summary>
        /// <remarks>
        /// When a control has the <see cref="IsCommandRequeriedOnChangeProperty" />
        /// attached property set to true, then any change to it's 
        /// <see cref="System.Windows.Controls.Primitives.ButtonBase.CommandParameter" /> property will cause the state of
        /// the command attached to it's <see cref="System.Windows.Controls.Primitives.ButtonBase.Command" /> property to 
        /// be reevaluated.
        /// </remarks>
        public static readonly DependencyProperty IsCommandRequeriedOnChangeProperty =
            DependencyProperty.RegisterAttached("IsCommandRequeriedOnChange",
                                                typeof(bool),
                                                typeof(CommandParameterBehavior),
                                                new UIPropertyMetadata(false, new PropertyChangedCallback(OnIsCommandRequeriedOnChangeChanged)));

        /// <summary>
        /// Gets the value for the <see cref="IsCommandRequeriedOnChangeProperty"/> attached property.
        /// </summary>
        /// <param name="target">The object to adapt.</param>
        /// <returns>Whether the update on change behavior is enabled.</returns>
        public static bool GetIsCommandRequeriedOnChange(DependencyObject target)
        {
            return (bool)target.GetValue(IsCommandRequeriedOnChangeProperty);
        }

        /// <summary>
        /// Sets the <see cref="IsCommandRequeriedOnChangeProperty"/> attached property.
        /// </summary>
        /// <param name="target">The object to adapt. This is typically a <see cref="System.Windows.Controls.Primitives.ButtonBase" />, 
        /// <see cref="System.Windows.Controls.MenuItem" /> or <see cref="System.Windows.Documents.Hyperlink" /></param>
        /// <param name="value">Whether the update behaviour should be enabled.</param>
        public static void SetIsCommandRequeriedOnChange(DependencyObject target, bool value)
        {
            target.SetValue(IsCommandRequeriedOnChangeProperty, value);
        }

        private static void OnIsCommandRequeriedOnChangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (!(d is ICommandSource))
                return;

            if (!(d is FrameworkElement || d is FrameworkContentElement))
                return;

            if ((bool)e.NewValue)
            {
                HookCommandParameterChanged(d);
            }
            else
            {
                UnhookCommandParameterChanged(d);
            }

            UpdateCommandState(d);
        }

        private static PropertyDescriptor GetCommandParameterPropertyDescriptor(object source)
        {
            return TypeDescriptor.GetProperties(source.GetType())["CommandParameter"];
        }

        private static void HookCommandParameterChanged(object source)
        {
            var propertyDescriptor = GetCommandParameterPropertyDescriptor(source);
            propertyDescriptor.AddValueChanged(source, OnCommandParameterChanged);

            // N.B. Using PropertyDescriptor.AddValueChanged will cause "source" to never be garbage collected,
            // so we need to hook the Unloaded event and call RemoveValueChanged there.
            HookUnloaded(source);
        }

        private static void UnhookCommandParameterChanged(object source)
        {
            var propertyDescriptor = GetCommandParameterPropertyDescriptor(source);
            propertyDescriptor.RemoveValueChanged(source, OnCommandParameterChanged);

            UnhookUnloaded(source);
        }

        private static void HookUnloaded(object source)
        {
            var fe = source as FrameworkElement;
            if (fe != null)
            {
                fe.Unloaded += OnUnloaded;
            }

            var fce = source as FrameworkContentElement;
            if (fce != null)
            {
                fce.Unloaded += OnUnloaded;
            }
        }

        private static void UnhookUnloaded(object source)
        {
            var fe = source as FrameworkElement;
            if (fe != null)
            {
                fe.Unloaded -= OnUnloaded;
            }

            var fce = source as FrameworkContentElement;
            if (fce != null)
            {
                fce.Unloaded -= OnUnloaded;
            }
        }

        static void OnUnloaded(object sender, RoutedEventArgs e)
        {
            UnhookCommandParameterChanged(sender);
        }

        static void OnCommandParameterChanged(object sender, EventArgs ea)
        {
            UpdateCommandState(sender);
        }

        private static void UpdateCommandState(object target)
        {
            var commandSource = target as ICommandSource;

            if (commandSource == null)
                return;

            var rc = commandSource.Command as RoutedCommand;
            if (rc != null)
            {
                CommandManager.InvalidateRequerySuggested();
            }

            var dc = commandSource.Command as IDelegateCommand;
            if (dc != null)
            {
                dc.RaiseCanExecuteChanged();
            }

        }
    }
}

ho trovato la tua segnalazione di bug su connect. C'è qualche possibilità che potresti aggiornare il tuo post qui con il tuo ultimo codice di questo? o da allora hai trovato una soluzione migliore?
Markus Hütter

Una soluzione più semplice potrebbe essere osservare la proprietà CommandParameter utilizzando un'associazione invece di un descrittore di proprietà. Altrimenti un'ottima soluzione! Questo risolve effettivamente il problema di fondo invece di introdurre solo un trucco o una soluzione alternativa.
Sebastian Negraszus

1

Esiste un modo relativamente semplice per "risolvere" questo problema con DelegateCommand, sebbene richieda l'aggiornamento del sorgente DelegateCommand e la ricompilazione di Microsoft.Practices.Composite.Presentation.dll.

1) Scarica il codice sorgente di Prism 1.2 e apri CompositeApplicationLibrary_Desktop.sln. Qui c'è un progetto Composite.Presentation.Desktop che contiene l'origine DelegateCommand.

2) Sotto l'evento pubblico EventHandler CanExecuteChanged, modifica per leggere come segue:

public event EventHandler CanExecuteChanged
{
     add
     {
          WeakEventHandlerManager.AddWeakReferenceHandler( ref _canExecuteChangedHandlers, value, 2 );
          // add this line
          CommandManager.RequerySuggested += value;
     }
     remove
     {
          WeakEventHandlerManager.RemoveWeakReferenceHandler( _canExecuteChangedHandlers, value );
          // add this line
          CommandManager.RequerySuggested -= value;
     }
}

3) Sotto il vuoto virtuale protetto OnCanExecuteChanged (), modificalo come segue:

protected virtual void OnCanExecuteChanged()
{
     // add this line
     CommandManager.InvalidateRequerySuggested();
     WeakEventHandlerManager.CallWeakReferenceHandlers( this, _canExecuteChangedHandlers );
}

4) Ricompila la soluzione, quindi vai alla cartella Debug o Release in cui risiedono le DLL compilate. Copia Microsoft.Practices.Composite.Presentation.dll e .pdb (se lo desideri) nella posizione in cui fai riferimento agli assembly esterni, quindi ricompila l'applicazione per estrarre le nuove versioni.

Dopo questo, CanExecute dovrebbe essere attivato ogni volta che l'interfaccia utente esegue il rendering di elementi associati al DelegateCommand in questione.

Abbi cura di te, Joe

refereejoe su gmail


1

Dopo aver letto alcune buone risposte a domande simili, ho cambiato leggermente nel tuo esempio il DelegateCommand per farlo funzionare. Invece di usare:

public event EventHandler CanExecuteChanged;

L'ho cambiato in:

public event EventHandler CanExecuteChanged
{
    add { CommandManager.RequerySuggested += value; }
    remove { CommandManager.RequerySuggested -= value; }
}

Ho rimosso i seguenti due metodi perché ero troppo pigro per risolverli

public void RaiseCanExecuteChanged()

e

protected virtual void OnCanExecuteChanged()

E questo è tutto ... questo sembra garantire che CanExecute verrà chiamato quando il Binding cambia e dopo il metodo Execute

Non si attiverà automaticamente se il ViewModel viene modificato ma come menzionato in questo thread è possibile chiamando CommandManager.InvalidateRequerySuggested sul thread della GUI

Application.Current?.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)CommandManager.InvalidateRequerySuggested);

Ho scoperto che DispatcherPriority.Normalè troppo alto per funzionare in modo affidabile (o per niente, nel mio caso). L'uso DispatcherPriority.Loadedfunziona bene e sembra più appropriato (ovvero indica esplicitamente che il delegato non deve essere richiamato fino a quando gli elementi dell'interfaccia utente associati al modello di visualizzazione non sono stati effettivamente caricati).
Peter Duniho

0

Ehi Jonas, non sono sicuro che funzionerà in un modello di dati, ma ecco la sintassi di associazione che uso in un menu contestuale di ListView per afferrare l'elemento corrente come parametro di comando:

CommandParameter = "{Binding RelativeSource = {RelativeSource AncestorType = ContextMenu}, Path = PlacementTarget.SelectedItem, Mode = TwoWay}"


Faccio esattamente la stessa cosa nella mia visualizzazione elenco. In questo caso si tratta di un ItemsControl quindi non è presente alcuna proprietà ovvia a cui "legarsi" (nella struttura ad albero visuale). Immagino di dover trovare un modo per rilevare quando l'associazione è terminata e rivalutare CanExecute (perché CommandParameter viene associato, solo in ritardo)
Jonas Follesø


0

Alcune di queste risposte riguardano l'associazione al DataContext per ottenere il comando stesso, ma la domanda riguardava il valore null CommandParameter quando non dovrebbe esserlo. Abbiamo anche sperimentato questo. Su un presentimento, abbiamo trovato un modo molto semplice per farlo funzionare nel nostro ViewModel. Ciò è specificamente per il problema null CommandParameter segnalato dal cliente, con una riga di codice. Notare il Dispatcher.BeginInvoke ().

public DelegateCommand<objectToBePassed> CommandShowReport
    {
        get
        {
            // create the command, or pass what is already created.
            var command = _commandShowReport ?? (_commandShowReport = new DelegateCommand<object>(OnCommandShowReport, OnCanCommandShowReport));

            // For the item template, the OnCanCommand will first pass in null. This will tell the command to re-pass the command param to validate if it can execute.
            Dispatcher.BeginInvoke((Action) delegate { command.RaiseCanExecuteChanged(); }, DispatcherPriority.DataBind);

            return command;
        }
    }

-1

È un tiro lungo. per eseguire il debug puoi provare:
- controllando l'evento PreviewCanExecute.
- usa snoop / wpf mole per sbirciare dentro e vedere qual è il parametro di comando.

HTH,


Ho provato a usare Snoop, ma è davvero difficile eseguire il debug poiché è NULL solo quando viene caricato inizialmente. Se eseguo Snoop su di esso, Command e CommandParameter sono entrambi seth ... Ha a che fare con l'uso dei comandi in DataTemplate.
Jonas Follesø

-1

Il commandManager.InvalidateRequerySuggested funziona anche per me. Credo che il seguente collegamento parli di un problema simile, e M $ dev ha confermato la limitazione nella versione corrente e il commandManager.InvalidateRequerySuggested è la soluzione alternativa. http://social.expression.microsoft.com/Forums/en-US/wpf/thread/c45d2272-e8ba-4219-bb41-1e5eaed08a1f/

Ciò che è importante è la tempistica di invocare il comandoManager.InvalidateRequerySuggested. Dovrebbe essere richiamato dopo la notifica della modifica del valore rilevante.


quel link non è più valido
Peter Duniho

-2

Oltre al suggerimento di Ed Ball sull'impostazione di CommandParameter prima di Command , assicurati che il tuo metodo CanExecute abbia un parametro di tipo di oggetto .

private bool OnDeleteSelectedItemsCanExecute(object SelectedItems)  
{
    // Your goes heres
}

Spero che impedisca a qualcuno di spendere l'enorme quantità di tempo che ho fatto per capire come ricevere SelectedItems come parametro CanExecute

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.