1

I'm trying to change the ColumnSpan based on a value from my ViewModel in a UWP for Windows 10 using the following:

<Setter Target="ProgramView.ColumnSpan" Value="{Binding
IsProgramViewVisible, Converter={StaticResource OneIfVisibleConverter}}"/>

I'm having 2 problems:

a) It doesn't allow me to bind b) It can't find the converter even though it's declared in my Page's resources.

When I move my mouse over the above, it displays an error:

Catastrophic Failure: (Exception from HRESULT: 0X8000FFFF (E_UNEXPECTED)

The error occurs whether I define my converter or not, so I'm assuming the problem is with the binding.

Is there a way I can achieve this?

Thanks.

Thierry
  • 6,142
  • 13
  • 66
  • 117
  • You might want to take a look at this http://blog.galasoft.ch/posts/2012/09/adventures-in-windows-8-placing-items-in-a-gridview-with-a-columnspan-or-rowspan/ – bit Dec 23 '15 at 06:34
  • @bit thanks for that. I found this article (still opened in my browser) but I thought it was over complicated for what I wanted to achieve, but I'll read through it in more details and see if I can grab and understand the basic concept and apply it to my solution. – Thierry Dec 24 '15 at 02:31
  • On that page goto the section *And now for the ColumnSpan!* and that is most probably what you would need – bit Dec 24 '15 at 04:53

2 Answers2

0

Since we cant provide an accurate solution without seeing your code.

Here is a check list you can follow to find out the bug:

1.Debug your Converter and check if it is returning desired value for all test cases.

2.Check if all the names are proper and there isn't any typo in ur xaml.

Here is an implementation for binding column span with an vm and updating it with a command bound to the click event.

<Page.Resources>
        <local:BoolToColumnSpanConverter x:Key="BoolToColumnSpanConverter" />
    </Page.Resources>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.DataContext>
            <local:Items />
        </Grid.DataContext>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100" />
            <ColumnDefinition Width="100" />
            <ColumnDefinition Width="100" />
            <ColumnDefinition Width="100" />
        </Grid.ColumnDefinitions>
        <Rectangle HorizontalAlignment="Stretch"
                      VerticalAlignment="Stretch"
                      Fill="Red"
                      Grid.ColumnSpan="{Binding span, Converter={StaticResource BoolToColumnSpanConverter}}" />
        <Button Click="Button_Click"
                  Content="change span"
                  Grid.ColumnSpan="2"
                  Grid.Column="2" 
                  Margin="5"
                  Command="{Binding ChangeSpanCommand, Mode=OneWay}" />
    </Grid>

the code behind:

The Converter just converts true to 2 and false to 1

The command is bound to a method in the VM called UpdateSpan which just turns the boolean inverse.

When the button is pressed , since it is bound to the command the command is called, since it just returns a new relaycommand with the UpdateSpan as a parameter this method is executed .. which will update the span boolean triggering a change which is notified by the System thru the OnPropertyChanged event and the value converter is executed turning the columnspan to 1 and 2 .

public class BoolToColumnSpanConverter : IValueConverter
    {
        public object Convert( object value , Type targetType , object parameter , string language )
        {
            var b = (bool)value;

            return b ? 2 : 1;
        }

        public object ConvertBack( object value , Type targetType , object parameter , string language )
        {
            throw new NotImplementedException();
        }
    }


    public class Items : INotifyPropertyChanged
    {
        private bool _span;
        public bool span
        {
            get { return _span; }
            set
            {
                if (value != _span) _span = value;
                OnPropertyChanged();
            }
        }

        public ICommand ChangeSpanCommand {
            get
            {
                return new RelayCommand(() => UpdateSpan());
            }
        }

        public Items()
        {
            span = true;
        }

        public void UpdateSpan()
        {
            span = !span;
        }

        #region Notify Property Changed Members
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged( [CallerMemberName]string propertyName = null )
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this , new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion
    }

    public class RelayCommand : ICommand
    {
        public event EventHandler CanExecuteChanged;

        public bool CanExecute( object parameter )
        {
            return true;
        }

        public void Execute( object parameter )
        {
            this._action();
        }

        private Action _action;

        public RelayCommand( Action action )
        {
            this._action = action;
        }
    }
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
Akash Gutha
  • 601
  • 8
  • 21
  • Hi Akash, thanks for the feedback and sorry for the lack of code. I thought that providing the above code would have been enough. The converter is definitely correct as I've tested in other parts of my app just to make sure it was correct. Also my properties are also correct as I can see them being changed accordingly as I displayed them for testing purpose. After reading for quite a bit, I think the code I provided will only work with WPF and not UWP! I found an post that stating that triggers are no longer available in Windows Store apps since windows 8. – Thierry Dec 24 '15 at 02:20
  • The MSDN post is https://social.msdn.microsoft.com/Forums/windowsapps/en-US/77604041-b991-4f1e-9adb-78c701ac4d9a/triggers-arent-supported-in-windows-8-xaml?forum=winappswithcsharp and one of the provided answer is that I basically should use VisualState. I'll have a read through this and see if I can get it to work this way. I'll post an update either way. – Thierry Dec 24 '15 at 02:22
  • Hmm .. you should use a visual state manager then :) .. and remember to place the visual state manager at the end of your visual tree just to make sure it doesnt produce any errors .. once i was having trouble where it didnt recognise a textblock name that was declared below it in the tree – Akash Gutha Dec 24 '15 at 06:59
0

Here's the answer to my problem.

Binding directly in a Setter is not allowed for Universal App (Windows Store & Windows Phone Apps), but works ok with WPF.

<Setter Target="ProgramView.ColumnSpan" Value="{Binding
IsProgramViewVisible, Converter={StaticResource OneIfVisibleConverter}}"/>

Triggers/DataTriggers are not supported in XAML and have been replaced by the VisualStateManager as explained in DataTriggers in WinRT post in StackOverflow.

I found various explanations on how to resolve this so here are a few links you might also find helpful:

So my solution as mentioned above was to use the VisualStateManager, more specifically, `DataTriggerBehavior.

One mistake which cost me a lot of time was to try to use this in conjunction with existing AdaptiveTrigger MinWindowWidth as I wanted to set my a VisualState based on the size and based on a binded property. This turned out to be a nightmare and it's a shame that there isn't a better mixture of the 2. Maybe there is a solution and I'm still missing something but for now my solution was a follows:

  1. Define my various VisualStates and set the various properties within it:

    <VisualState x:Name="ListOnly">
        <VisualState.Setters>
            <Setter Target="ProgramList.(Grid.Column)" Value="0" />
            ....
        </VisualState.Setters>
     </VisualState>
    
  2. Check my app's orientation and width from the MainPage.Xaml in the Page_SizeChanged event and create a static property in the App.cs and set it from there:

    private void Page_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        if (ApplicationView.GetForCurrentView().Orientation ==
        ApplicationViewOrientation.Landscape)
        {
            App.IsLandscape = (ApplicationView.GetForCurrentView()
            .VisibleBounds.Width < 600) ? false : true;
        }
        else
        {
            App.IsLandscape = false;
        }
    }
    
  3. From the relevant page that loaded in the Frame of MainPage i.e. ListPage.xaml for example, I created a function called SizeChanged in the relevant ViewModel i.e. ListPageViewModel and I call this from within the Page_SizeChange event of the page:

    private void Page_SizeChanged(object sender, Windows.UI.Xaml.SizeChangedEventArgs e)
    {
        this.GetViewModel.SizeChanged();
    }
    
  4. The SizeChange method in the relevant ViewModel will change a property that's binded to the DataTrigger based on my app's orientation and size:

    public void SizeChanged()
    {
        if (!App.IsLandscape)
        {
             ...
             this.ActivePart = ActivePartEnum.ListOnly.ToString();
             ...
        }
        else
        {
             ...
             this.ActivePart = ActivePartEnum.Both.ToString();
             ...
        }
    }
    
  5. Finally in the relevant XAML Page, call the DataTriggerBehaviour:

    <Interactivity:Interaction.Behaviors>
        <Core:DataTriggerBehavior Binding="{Binding ActivePart}"
         ComparisonCondition="Equal" Value="ListOnly">
            <Core:GoToStateAction StateName="ListOnly" />
        </Core:DataTriggerBehavior>
        ....
    </Interactivity:Interaction.Behaviors>
    

With the above, you should be able to apply a specific template based on orientation and size of your app and can be expanded if needed. One thing I will be looking into further is behaviour but couldn't get it to work and had to move on for now, but that would be an even better solution I think.

Hope this helps anyway and thanks for everyone's feedback.

Community
  • 1
  • 1
Thierry
  • 6,142
  • 13
  • 66
  • 117