简体   繁体   中英

WPF UserControl custom paste command across multiple child controls with MVVM

Consider the following UserControl:

<UserControl ...>
    <UserControl.Resources>
        <inf:DecimalToPercentageConverter x:Key="DecimalToPercentageConverter"/>
    </UserControl.Resources>
    <Expander Margin="0,5,5,5" Style="{DynamicResource ExpanderStyle}" IsExpanded="True" IsTabStop="False">
        <Expander.Header>
            <TextBlock FontSize="15" Foreground="White" Text="{x:Static resources:Strings.GeneralOptions}"/>
        </Expander.Header>

        <ItemsControl ItemsSource="{Binding DaysViewSource}" IsTabStop="False" Name="ItemsControl">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border Background="White" Padding="5" BorderThickness="2">
                        <HeaderedContentControl IsTabStop="False" Width="240">
                            <HeaderedContentControl.Header>
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock Text="{Binding Date, StringFormat=d}" FontSize="14" Margin="3"/>
                                    <TextBlock Text="{Binding Date, StringFormat=(ddd)}" FontSize="14" Margin="3"/>
                                </StackPanel>
                            </HeaderedContentControl.Header>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>
                                <Grid.RowDefinitions>
                                    <RowDefinition/>
                                    <RowDefinition/>
                                    <RowDefinition/>
                                </Grid.RowDefinitions>

                                <StackPanel Orientation="Horizontal" Grid.ColumnSpan="2">
                                    <Image Margin="3" Width="16" Height="16" Source="{DynamicResource InformationIcon}" />
                                    <TextBlock Margin="1,3,3,3" Text="{Binding HoursPerAgentText}"/>
                                </StackPanel>

                                <TextBlock Grid.Row="1" Text="{x:Static resources:Strings.Utilization}" Margin="3" VerticalAlignment="Center"/>
                                <TextBox Grid.Row="1" Grid.Column="1" 
                                         Text="{Binding Utilization, ValidatesOnDataErrors=true, StringFormat='P2', Converter={StaticResource DecimalToPercentageConverter}}" 
                                         Margin="3" VerticalAlignment="Center" />

                                <TextBlock Grid.Row="2"  Text="{x:Static resources:Strings.Shrinkage}"  Margin="3" VerticalAlignment="Center"/>
                                <TextBox Grid.Row="2" Grid.Column="1" 
                                         Text="{Binding Shrinkage, ValidatesOnDataErrors=true, StringFormat='P2', Converter={StaticResource DecimalToPercentageConverter}}"  
                                         Margin="3" VerticalAlignment="Center"/>
                            </Grid>
                        </HeaderedContentControl>
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Expander>
</UserControl>

It renders like this:

UserControl呈现

My task is to implement paste functionality across all days and both fields. Users will copy the values from an Excel sheet and paste directly into one of the textboxes. I have the parsing logic down but I'm stumped on how to handle the pasting across all controls.

So far I tried with a CommandBinding with the Paste command but this seems to only give me an event in the view code behind, Ideally I'd like to be able to handle the logic in my view model. I know there are patterns that allow a command binding to be linked to a view model with an attached property ( http://codingcontext.wordpress.com/2008/12/10/commandbindings-in-mvvm/ ) and in this case, I get the event in the viewmodel but then all I get is the sender as a textbox element (I don't want that in my viewmodel).

Has anyone come across the same problem or has ideas on how to solve this elegantly?

I have a Utility project that hosts many Manager classes, one of which is the ClipboardManager . All of my Manager classes follow the Singleton pattern (meaning that there can only be one of each) and are made available from the BaseViewModel class. They are all interfaced, so that I can implement mock managers for testing purposes, but that's another story.

The ClipboardManager has some standard methods, but essentially for specific tasks, I need to add the following methods:

/// <summary>
/// Copies the SecurityPermissions object specified by the securityPermissions input parameter into the computer clipboard in the SecurityPermissions format.
/// </summary>
/// <param name="securityPermissions">The SecurityPermissions object to copy into the computer clipboard.</param>
public void SetClipboardSecurityPermissions(SecurityPermissions securityPermissions)
{
    try { SetClipboardSecurityPermissionsData(securityPermissions); }
    catch (COMException)
    {
        Thread.Sleep(0);
        try { SetClipboardSecurityPermissionsData(securityPermissions); }
        catch (COMException)
        {
            MessageBox.Show("The clipboard was inaccessible, please try again later");
        }
    }
}

private void SetClipboardSecurityPermissionsData(SecurityPermissions userSecurityPermissions)
{
    List<SecurityPermission> securityPermissions = new List<SecurityPermission>(userSecurityPermissions);
    List<SerializableSecurityPermission> serializableSecurityPermissions = new List<SerializableSecurityPermission>();
    foreach (SecurityPermission securityPermission in securityPermissions) serializableSecurityPermissions.Add(new SerializableSecurityPermission(securityPermission));
    DataObject securityPermissionsDataObject = new DataObject();
    securityPermissionsDataObject.SetData("SecurityPermissions", serializableSecurityPermissions, false);
    Clipboard.SetDataObject(securityPermissionsDataObject);
}

/// <summary>
/// Retrieves a SecurityPermissions object from the computer clipboard if any data exists in the SecurityPermissions format.
/// </summary>
public SecurityPermissions GetClipboardSecurityPermissions()
{
    if (HasClipboardGotDataFormat("SecurityPermissions"))
    {
        try { return PasteClipboardSecurityPermissions(); }
        catch (COMException)
        {
            Thread.Sleep(0);
            try { return PasteClipboardSecurityPermissions(); }
            catch (COMException)
            {
                MessageBox.Show("The clipboard was inaccessible, please try again later");
            }
        }
    }
    return new SecurityPermissions();
}

private SecurityPermissions PasteClipboardSecurityPermissions()
{
    object data = Clipboard.GetData("SecurityPermissions");
    if (data != null)
    {
        List<SecurityPermission> securityPermissions = new List<SecurityPermission>();
        List<SerializableSecurityPermission> serializableSecurityPermissions = (List<SerializableSecurityPermission>)data;
        foreach(SerializableSecurityPermission serializableSecurityPermission in serializableSecurityPermissions) securityPermissions.Add(new SecurityPermission(serializableSecurityPermission));
        return new SecurityPermissions(securityPermissions);
    }
    return new SecurityPermissions();
}

This uses one of the standard methods:

/// <summary>
/// Returns true if any data currently exists in the computer clipboard in the data format specified by the dataFormat input parameter.
/// </summary>
public bool HasClipboardGotDataFormat(string dataFormat)
{
    return Clipboard.ContainsData(dataFormat);
}

The public methods are the ones that are interfaced and so the ones that I have access to from my view models. So this is one way to handle clipboard activity from the view model. Now once you have your data in the view model, how you distribute it amongst your various controls will very much depend on how you have set them up.

If you have one common parent view model, then I would perform your clipboard activity in there and then distributing the data to the child controls should be easy.

UPDATE >>>

One easy way to handle this is to provide a 'paste' Button in your UI. You can connect that to an ICommand of some sort in your parent view model... I use a custom ActionCommand (similar to RelayCommand ):

/// <summary>
/// Pastes the SecurityPermissions collection from the computer clipboard to the currently selected User.
/// </summary>
public ICommand PasteUserSecurityPermissions
{
    get { return new ActionCommand(action => ((SecurityViewModel)ViewModel).
PasteSecurityPermissions(ClipboardManager.GetClipboardSecurityPermissions()), 
canExecute => ClipboardManager.HasClipboardGotDataFormat("SecurityPermissions")); }
}

Then in the child view model:

/// <summary>
/// Pastes the SecurityPermissions collection from the computer clipboard to the currently selected User object.
/// </summary>
public void PasteSecurityPermissions(SecurityPermissions securityPermissions)
{
    // Do whatever you like with the pasted data here
}

In your case, you could have one of these paste methods in each of your child view models... nice and easy. :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM