Collapsing/expanding action with wpf buttons - c#

First off, let me state I am an amateur when it comes to wpf. I am trying to create an a collapsing/expanding type action for a wpf button, meaning when a user clicks a button, I would like the button selected to expand a new list of buttons beneath it. This is meant to be the navigation type for the web-enabled application. I would also like to create a collapsing interaction when the button is pressed again on an opened list.

There is a default control for this in WPF, named the Expander. If you want to change the appearance or the animations you could look into templating and styling of WPF. By default this control should meet most of your requirements.

My first idea is to use a ToggleButton and bind its IsChecked property to a the visibility of the element you want to show. You would need a converter then to convert the boolean Checked value to a Visibility value. Here is an example:
<Grid>
<Grid.Resources>
<BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
</Grid.Ressources>
<ToggleButton x:Name="toggleButton" Content="Toggle" />
<Grid Visibility={Binding IsChecked, ElementName=toggleButton, Converter={StaticResource BoolToVisibilityConverter}}>
<!-- place your content here -->
</Grid>
</Grid>
The converter is a class implementig IValueConverter:
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool i = value is bool ? (bool) value : false;
return i ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Just use a ToggleButton and bind the visibility of the section to its IsChecked state as normally done in the Expander control (which you of course could just use instead).

Related

Having a run mode and an edit mode [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I have a question regarding running the same application in different "modes".
Basically, I want the app to be editable if you know what you're doing (e.g. you're an admin of sorts) but not if you're just using it.
I was thinking about making a bool/int value and make it hide show elements based on the mode. But is this the correct way? is there a better way to do this?
Thanks in advance!
You're right to have a single setting in the ViewModel that reflects the logical requirement. If there are only two options, it should be a boolean.
You probably have to think carefully about which controls need to be disabled, hidden or read-only. For example, control with a scroll bar shouldn't be disabled or the user won't be able to scroll. Text boxes could be either (read-only allow the user to select and copy, disabled doesn't). Other controls could be disabled, if there's some way that the user can enable them (e.g. a save button that is only enabled once there are changes to save), or hidden if the user will only ever have a read-only view.
You'll most likely need a few ValueConverters to convert the boolean 'IsAdmin' (or whatever) flag to values that the WPF expects. For example you can bind IsEnabled directly, but IsReadOnly will be a converter to invert it. If you want to hide something, you'll need a converter like this:
public class BoolVisiblityInverseConverter: IValueConverter
{
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.GetType() != typeof(bool))
{
throw new ArgumentException("BoolVisiblityInverseConverter can only accept a bool");
}
var val = (bool)value;
return val ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then you'll need to define it in the Resources section of your XAML
<converters:BoolVisiblityInverseConverter x:Key="BoolVisiblityInverseConverter" />
and bind it to the Visibility property
<Button Visibility="{Binding IsAdmin, Converter={StaticResource BoolVisiblityInverseConverter}}" />
The goal with this method is that the interface between the View and ViewModel is as simple as possible. The VM can decide what mode to use, and the View displays every control in a suitable state.
You can easy do it with Visibility Converter. Place editable control and readonly control in same place.
Xaml:
<Window.Resources>
<myapp:BoolToVisible x:Key="bool2visible"/>
</Window.Resources>
<Grid Margin="10">
<StackPanel>
<CheckBox IsChecked="{Binding Mode}">Mode on</CheckBox>
<StackPanel Margin="10">
<TextBlock Text="{Binding Number}" Visibility="{Binding Mode, Converter={StaticResource bool2visible}, ConverterParameter=1}"/>
<TextBox Text="{Binding Number}" Visibility="{Binding Mode, Converter={StaticResource bool2visible}}"/>
</StackPanel>
</StackPanel>
</Grid>
Bool to visibility converter
public class BoolToVisible:IValueConverter
{
public BoolToVisible()
{
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var val=(bool)value;
if (parameter?.ToString() == "1")
{
val=!val;
}
if (val)
{
return Visibility.Visible;
}else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

How to implement a converter from string to Visibility

So I have a ProgressRing and a TextBlock and I am trying to implement this basic hack, which is to display both elements when TextBlock's Text gets assigned a value (anything other than null), else both elements should hide when TextBlock's Text is null.
My Xaml looks like below. TextBlock's Text is binded to MessageForProgressRing and its Visibility is binded to both MessageForProgressRing and TargetNullValue. Same for me ProgressRing:
<StackPanel Panel.ZIndex="100" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center">
<mahControls:ProgressRing Height="50" IsActive="True" Width="50" Visibility="{Binding MessageForProgressRing, TargetNullValue=Collapsed, FallbackValue=Visible}" Foreground="White" Margin="0,0,0.2,0" />
<TextBlock Text="{Binding MessageForProgressRing}" Visibility="{Binding MessageForProgressRing, TargetNullValue=Collapsed, FallbackValue=Visible}"/>
</StackPanel>
Then, in code behind I just trigger the property and assign it a value on some button event handlers:
private void closeApplicationButtonTask()
{
((CaptureViewModel)DataContext).MessageForProgressRing = "Closing... ";
Application.Current.MainWindow.Close();
}
However, in my ViewModelBase (the parent of all my view models) it pops an error on OnPropertyChanged saying:
Requested value 'Closing...' was not found.
I think I need a converter because Visibility is binded to Closing... right? If yes how can I achieve it?
P.S I couldn't do it in OnPropertyChanged because I don't see the value to assign it. Also I don't think it is a good idea since it gets called big time before, during and after the execution.
I usually prefer to solve this problem by having a boolean property in my view model (e.g. HasMessageForProgressRing or IsProgressRingVisible). It's usually a more general-purpose solution. Then you can use a BooleanToVisibilityConverter.
If you truly want to implement a converter, just create a class that implements IValueConverter. The Convert implementation of this should be a piece of cake for your simple use case. ConvertBack isn't necessary in most cases (and won't be in yours). It would look something like this:
public class NullToCollapsed : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value != null ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Binding RadioButtons to choose one item from a collection displayed in a ListBox

I have an ObservableCollection in my Model, displayed in a ListBox in my View. Each ListBoxItem displays a radio button which should allow the user to choose one of the items. A property of the ViewModel should record this choice by holding a reference to the chosen item.
How can I set up a two-way binding to do this?
An IValueConverter should allow me to bind RadioButton.IsChecked to the VM property, with the actual item in which the radio button occurs passed to the converter either as a parameter or a value in an IMultiValueConverter. This way I can:
Return true / false for Convert() based on comparison of the VM property and the item the radio button is associated with.
Return the item if IsChecked==true and Binding.DoNothing otherwise for ConvertBack().
However, ValueConverter parameters cannot be bound to because they aren’t dependency properties (so I can't bind to the item to use as a ValueConverter parameter) and I cannot use the MultiConverter because although Convert() will receive both values of interest, ConvertBack() will only receive the value of IsChecked.
Notes
The built in ListBox selection mechanism is already in use for other purposes.
The collection of interest is nested in another collection and presented in a containing ListBox.
The ListBox is bound to a collection in the Model. I am hoping not to implement events in the Model collections that tell the VM how to record events in the View, for obvious reasons.
I managed to solve this:
Include a converter on the resources node of the ItemsPanel which wraps each ListBoxItem (or somewhere else within the repeated part of the tree), so there is one converter instance per item.
Add a property (Host in my example) to the converter for the item each converter attaches to and initialise it somehow* as each ListBoxItem is created.
Bind IsChecked to the ViewModel property you need to have populated using the converter.
*I tried the Host property as a DP with binding to the DataContext (which should be one member of the collection the ListBox is bound to at the point at which the converter is located), but I simply could not get this to work for my nested ListBoxes. I resorted to the Initialized event of the containing element and some code behind (which did allow Host to revert to an ordinary property; the DP was overkill but had been necessary for binding).
The DataTemplate:
<StackPanel Initialized="StackPanel_Initialized">
<StackPanel.Resources>
<!-- ExlcusionRadioConverter.Host is initialised in code behind -->
<local:ExclusionRadioConverter x:Key="ExclusionRadio" />
</StackPanel.Resources>
<RadioButton
GroupName="Exclusion"
IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}, Converter={StaticResource ExclusionRadio}, Path=DataContext.ExclusionCriterion}" />
<Label Content="{Binding Description}" />
</StackPanel>
The Panel Initialized event (NB this answer originally had this code in the Loaded event handler, but this was causing some problems):
private void StackPanel_Initialized(object sender, EventArgs e)
{
StackPanel panel = (StackPanel)sender;
ExclusionRadioConverter converter = (ExclusionRadioConverter)panel.FindResource("ExclusionRadio");
converter.Host = panel.DataContext as OptionListMember;
}
The Converter:
[ValueConversion(typeof(object), typeof(bool))]
public class ExclusionRadioConverter : IValueConverter
{
public OptionListMember Host { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ReferenceEquals(value, Host);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((bool)value) ? Host : Binding.DoNothing;
}
}

WPF: MVVM - disable button if command is null

I have binding on some command:
<Button Command="{Binding Save}" />
Save is command of some object that can be selected from list.
In initial state there is no any selected object so binding does not work and CanExecute does not invoked. How can i disable this button using MVVM?
Solution: WPF/MVVM: Disable a Button's state when the ViewModel behind the UserControl is not yet Initialized?
Guys, thanks for your answers and sorry for duplication of question.
Define a command that always return false to CanExecute. Declare it at a global position such as in your App.Xaml. you can specify this empty-command then as the FallbackValue for all your command bindings you expect a null value first.
<Button Command="{Binding Save,FallbackValue={StaticResource KeyOfYourEmptyCommand}}" />
You could create a trigger in XAML that disables the Button when the command equals x:Null.
An example can be found in the answer to this question: WPF/MVVM: Disable a Button`s state when the ViewModel behind the UserControl is not yet Initialized?
I'm not sure you'll be able to achieve this. However, an alternative would be to initialise the Command object initially with a basic ICommand where CanExecute simply returns False. You could then replace this when you're ready to put the real command in place.
Have a look at the Null Object Pattern
Create a NullToBooleanConverter and bind the IsEnabled property to the command, running it through the converter:
class NullToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value != null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then
<UserControl.Resources>
<Extentions:NullToBooleanConverter x:Key="NullToBooleanConverter" />
</UserControl.Resources>
<Button Content="Hello" IsEnabled="{Binding Save, Converter={StaticResource NullToBooleanConverter}}" />

Silverlight: Remove loading message on content load without code behind?

I'm building a wp7 app in Silverlight. I have some content that gets loaded asynchronously, and messages that indicate that loading is not yet done. I'd like to have the loading messages disappear as soon as the content's list box is not empty. Is it possible to do this just in XAML? Something like binding the Visibility property to StoryListBox.ItemsSource.IsEmpty?
StoryListBox is populated by having its ItemsSource set to an observable collection after the data is available.
<TextBox x:Name="LoadingMessage" Text="Loading..." Grid.Row="0" />
<ProgressBar x:Name="LoadingProgress" IsIndeterminate="True" Style="{StaticResource PerformanceProgressBar}" />
<ListBox x:Name="StoryListBox" Grid.Row="0" />
Update: I tried the following, but it doesn't work:
<StackPanel x:Name="Loading" Grid.Row="0" Visibility="{Binding StoryListBox.ItemsSource.IsEmpty, Converter={StaticResource visibilityConverter}}">
<TextBox Text="Loading..." />
<ProgressBar IsIndeterminate="True" Style="{StaticResource PerformanceProgressBar}" />
</StackPanel>
<ListBox x:Name="StoryListBox" Grid.Row="1" />
The Loading stack panel never collapses.
You seem to have answered your own question. Yes, you can simply bind the Visibility (or Busy/IsBusy on a BusyIndicator control to some attribute of another control).
If the specific property you want to bind to is not a bindable property, simply bind to the other control and customise the converter to get the member property you want. If you have specific code examples, just post them and I can post a more specific solution.
The usual problem is that the types (for visibility) are incompatible with boolean values, so you need to specifier a converter in the binding. Google for Silverlight VisibilityConvertor (they are a dime a dozen). Here is mine:
namespace Common.ValueConverters
{
using System;
using System.Windows;
using System.Windows.Data;
public class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool?)
{
if (string.IsNullOrEmpty((string)parameter))
{
return (value as bool?).Value ? Visibility.Visible : Visibility.Collapsed;
}
else
{
return (value as bool?).Value ? Visibility.Collapsed : Visibility.Visible;
}
}
throw new ArgumentException();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
A use of the converter would look like:
<Grid Visibility="{Binding ShowDualView, Converter={StaticResource VisibilityConverter}}">
But quite frankly you are better of with a BusyIndicator control bound to an IsBusy property:
<Controls:BusyIndicator IsBusy="{Binding IsBusy}">
Just put it around the controls to you want to have hidden by the busy display.

Categories

Resources