5

What's the shortest xamly way to make a ToggleButton contents depend on its checked state?

In WPF I'd probably go for a DataTrigger which doesn't exist in Silverlight.

I tried the following, but it doesn't work, as soon as I include the triggers, the binding to the source is broken. The triggers won't work anyway.

<ToggleButton
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
IsChecked="{Binding IsArchived, Mode=TwoWay}">
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="Checked">
      <ei:ChangePropertyAction
      TargetObject="{Binding
        RelativeSource={RelativeSource AncestorType=ToggleButton}}" 
      PropertyName="Content" Value="Unarchive project"/>
    </i:EventTrigger>
    <i:EventTrigger EventName="Unchecked">
      <ei:ChangePropertyAction 
      TargetObject="{Binding 
        RelativeSource={RelativeSource AncestorType=ToggleButton}}" 
      PropertyName="Content" Value="Archive project"/>
    </i:EventTrigger>
  </i:Interaction.Triggers>
</ToggleButton>
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
  • My own way didn't qualify as short, so I'm posting as a comment: override the ToggleButton template, use two TextBox, set their Visiblity property using the VisualStateManager. – jv42 Apr 04 '12 at 08:53
  • @jv42, that won't affect the control on first-time bounding, only when changed after rendering, see [my answer](http://stackoverflow.com/a/9989048/75500) that solves that too. – Shimmy Weitzhandler Apr 04 '12 at 09:03
  • I never had issues with that. Maybe I missed something. – jv42 Apr 04 '12 at 09:11
  • You have to understand that the control is not bound to the property, it's rather triggered when the value is changed. Hence, when the object is first bound, VSM/triggers won't affect the right property value until it's changed, using a `Binding` with a converter is the ultimate solution, and will also cost less performance than spreading event-handlers all around (I once heard that there are tremendous mem licks in the Expression interactivity `EventTrigger`s, and I try to avoid them since then. Anyway, IMHO the XAML is also more straight-forward, cleaner and less verbose when using binding. – Shimmy Weitzhandler Apr 04 '12 at 11:01
  • Just to clarify, by VSM, I meant using pre-defined states, not code-behind changes using the VSM. I will re-check my implementation, but I think it works in all cases (initially checked, initially not checked, changing states). – jv42 Apr 04 '12 at 13:11

2 Answers2

4
<ToggleButton Width="50" Height="50">
  <ToggleButton.Content>
        <TextBlock x:Name="obj" Text="Foo"/>
    </ToggleButton.Content>
  <i:Interaction.Triggers>
        <i:EventTrigger EventName="Checked">
            <ei:ChangePropertyAction PropertyName="Text" Value="On" TargetName="obj"/>
        </i:EventTrigger>
        <i:EventTrigger EventName="Unchecked">
            <ei:ChangePropertyAction PropertyName="Text" Value="Off" TargetName="obj"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ToggleButton>
Alex Burtsev
  • 12,418
  • 8
  • 60
  • 87
  • I took a look at ChangePropertyAction source code, and it uses combination of TypeConverter and reflection SetValue, and somehow it messes with the fact that content is Object and you set String to it. I decided not to spent time to understand this code in details ) – Alex Burtsev Apr 03 '12 at 04:16
1

I ended up using Kent Boogaart's converter, works great, and is also dependent on the bound property, not on a control trigger which might not fire at all (in a case where the property wasn't actually set), here is the code:

<ToggleButton.Content>
  <Binding Path="IsArchived"
    xmlns:boo="http://schemas.kent.boogaart.com/converters"
    xmlns:sys="clr-namespace:System;assembly=mscorlib">
    <Binding.Converter>
      <boo:MapConverter>
        <boo:Mapping To="Archive project">
          <boo:Mapping.From>
            <sys:Boolean>false</sys:Boolean>
          </boo:Mapping.From>
        </boo:Mapping>
        <boo:Mapping To="Unarchive project">
          <boo:Mapping.From>
            <sys:Boolean>true</sys:Boolean>
          </boo:Mapping.From>
        </boo:Mapping>
      </boo:MapConverter>
    </Binding.Converter>
  </Binding>
</ToggleButton.Content>
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
  • I would personally put this LOGIC in viewModel, instead of diggining it in XAML. It's less code, and it makes your intensions visible. I hate reading my teammates XAML searching for hidden logic inside it. If you work alone thats ok, though. – Alex Burtsev Apr 04 '12 at 04:22
  • @AlexBurtsev I disagree. This is matters of the View layer, not of ViewModel, whos duty is to expose an `IsArchived` property just like it does and remain UI agnostic. – Shimmy Weitzhandler Apr 04 '12 at 04:29
  • 1
    It can bee either way, depending on your usage of VM pattern. It a question of were do converters live in XAML or VM, I prefer the 2nd one, when VM provides view with ready to use data. Partly it's because "external" converters capabilities often limited. – Alex Burtsev Apr 04 '12 at 04:46
  • @AlexBurtsev, for each is own taste. In my taste converters are a matter of the view, I like to to provide the View with agnostic info, so I can use different controls/views etc. – Shimmy Weitzhandler Apr 04 '12 at 05:36