views:

76

answers:

1
<RichTextBox x:Name="OrigText" Margin="0,0,8,0" d:LayoutOverrides="Width"/>
    <Button x:Name="OrigFileBrowse" Command="{Binding BrowseCommand}" CommandParameter="{Binding ElementName=OrigText, Path=Document}" HorizontalAlignment="Center" Margin="0,0,8,2.442" Width="75" Content="Browse" Grid.Row="1" d:LayoutOverrides="Height"/>
    <RichTextBox x:Name="ModifiedText" Grid.Column="1" Margin="8,0,0,0"/>
    <Button x:Name="ModifiedFileBrowse" Command="{Binding BrowseCommand}" CommandParameter="{Binding ElementName=ModifiedText, Path=Document}" HorizontalAlignment="Center" Width="75" Content="Browse" Grid.Row="1" Grid.Column="1" Margin="0,0,0,2.442" d:LayoutOverrides="Height"/>
    <Button x:Name="Compare" Command="{Binding CompareCommand}" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Content="Compare" Grid.Row="2" Grid.ColumnSpan="2">
        <Button.CommandParameter>
            <x:Array Type="RichTextBox">
                <local:CompareTextView/>
            </x:Array>
        </Button.CommandParameter>
    </Button>

Trying to get 2 items to be passed when the Compare button is clicked as it will then execute a compare command. Attempted to make use of MultiBinding however that is firing on instantiation and therefore the converter then fires accordingly. It does NOT fire when I click compare and the compare command is executed.

With that not working, I am attempting to now reference the controls within XAML to pass within an ArrayExtension. Not sure of the syntax or if it is even possible as I know you cannot bind within the ArrayExtension. The above fails since it can not construct a new CompareTextView view, which has no default constructor since I am making use of Prism...

Pretty frustrating, hopefully someone can help me out...

EDIT:

Want to clear some things up. The issue is not that I want CanExecute called again. The issue is that at instantiation of the controls, the converter is called and executed and the values are returned...but where they go I have no clue? The converter is never called again. If I could get the initial references to the FlowDocument this would all be a moot point...but it doesn't return things anywhere per se...since this is a command...if that makes sense...when making use of MultiBinding.

        <Button x:Name="Compare" Command="{Binding CompareCommand}" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Content="Compare" Grid.Row="2" Grid.ColumnSpan="2">
        <Button.CommandParameter>
            <MultiBinding Converter="{StaticResource FlowDocumentConverter}">
                <Binding ElementName="OrigText" Path="Document"/>
                <Binding ElementName="ModifiedText" Path="Document"/>
            </MultiBinding>
        </Button.CommandParameter>
    </Button>

UPDATE:

Tried what refereejoe mentions here, scroll down a little bit to see his posting. While CanExecute continually fires, this does nothing to resolve the issue. In addition I switched the MultiBinding to be a single item, it is coming back null. Again when the converter fires on instantiation the FlowDocument references are there...

ANSWER:

Abe's mention that it was being cached led me to try something else. Since I knew that the FlowDocument references were available when the converter was called I knew they were there. Something was getting fouled up. The key piece appears to be in the converter itself. I was simply returning the object[]. Then when the command fired the arg was indeed an object[] but the two items were null. I created a class called Docs, which had two properties, one for each FlowDocument reference. When the converter fired I set the properties appropriately and then returned the Docs object. Now when I initiated the compare command, the Docs object was the args and it had the reference to the FlowDocuments just as I needed! Not sure if this is by design, but the fact that the items get lost when using the object[] array don't make sense to me.

+1  A: 

The proper way to do this is indeed with a MultiBinding on the CommandParameter. You won't see it call your CanExecute method unless WPF is informed that the method could return a different value than it had already cached (via the CanExecuteChanged event).

Since you are relying on the parameter passed in to determine this, we have to raise the event when the parameter changes. Since we can't really determine that in the command, we can use another technique: tell WPF to poll our command anytime it polls UICommands. This is done by implementing your ICommand like so:

public class MyCommand : ICommand
{
    public void Execute(object parameter) { /* do stuff */ }
    public bool CanExecute(object parameter { /* determine if we can do stuff */ }

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

Obviously, this prevents you from using the Prism DelegateCommand, but this will respond to changes in the command parameters.

UPDATE

Another thing to consider is that the Document property on the RichTextBox isn't actually changing. Instead, when you type into it, the content of the FlowDocument changes. Since the property instances don't change, the converter won't get fired again, and the originally converted value will get stored in the CommandParameter property.

One of the ways to force the converter to be called again is to add a Binding to the MultiBinding that is bound to a property that will change every time the text of the RichTextBox changes.

A somewhat hacky solution would be to use the IsKeyboardFocusWithin property, as that will mimic the default binding behavior of TextBox.Text (i.e. when the TextBox loses focus, the Binding updates):

<MultiBinding Converter="{StaticResource FlowDocumentConverter}">
    <Binding ElementName="OrigText" Path="Document" />
    <Binding ElementName="ModifiedText" Path="Document" />
    <Binding ElementName="OrigText" Path="IsKeyboardFocusWithin" />
    <Binding ElementName="ModifiedText" Path="IsKeyboardFocusWithin" />
</MultiBinding>

Obviously, in your converter, you will need to ignore these additional values, as they aren't relevant to your conversion.

Abe Heidebrecht
Abe, thanks for the feedback...so as a DelegateCommand via Prism it is not possible?
Aaron
Added an edit up top to clear some things up...
Aaron
My sample app originally used TextBoxes, as the converter was easier to write. I updated my answer to address the problem with RichTextBoxes.
Abe Heidebrecht
Abe figured it out! Check the answer above in my initial post...marking you as the answer since you began dialog which can often times be the answer in and of itself. Thanks!
Aaron
Ah, that does make sense. I can't remember where I read this, but I recall that the array is cleared after being passed to the converter. You can also just call the LINQ ToArray method, and have similar results. Glad I could offer some help!
Abe Heidebrecht