WPF Multi-Binding / Aggregate Binding to Collection - wpf

I have a Control that I want to automatically disappear if another control has no visibile children. I'm not sure how to implement that though. I feel as though I need to create a binding that returns bindings for each child element's visible property and then aggregates them into a MultiValueConverter. I think it is working but it seems as though when I add items to my collection, the collection binding isn't being re-evaluated. Has anyone done this before?
Below is my code:
<Grid.Resources>
<local:BindingExpander x:Key="BindingExpander"/>
<local:TestConverter x:Key="TestConverter" />
</Grid.Resources>
<Button Content="Button" HorizontalAlignment="Left" Margin="237,166,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click">
<Button.Visibility>
<MultiBinding Converter="{StaticResource TestConverter}">
<Binding ElementName="lstItems" Path="Items" Converter="{StaticResource BindingExpander}" ConverterParameter="Visibility"/>
</MultiBinding>
</Button.Visibility>
</Button>
<ListBox x:Name="lstItems" HorizontalAlignment="Left" Height="100" Margin="601,130,0,0" VerticalAlignment="Top" Width="100" DisplayMemberPath="Content"/>
and:
public class TestConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
var ret = Visibility.Collapsed;
foreach (var item in values) {
if(item is IEnumerable IE) {
foreach (var Child in IE) {
}
}
}
return ret;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
public class BindingExpander : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var ret = new List<Binding>();
if(value is IEnumerable IE) {
foreach (var item in IE) {
ret.Add(new Binding(parameter.ToString()) {
Source = item,
Mode = BindingMode.OneWay
});
}
}
return ret;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
````

I have a Control that I want to automatically disappear if another
control has no visibile children..
Simply create a Boolean property which reports the status of what the other control is binding to such as:
public bool HasItems { get { return _SomeArray?.Any(); }}
This property can be as elaborate as needed, but a basic one above for the example is shown.
Then bind the visibility flag of the control in question to the HasItems.
Note that the HasItems does not have the plumbing for INotifyPropertyChanged. In the code(s) where items are added to the _SomeArray simply put in a call to PropertyChanged("HasItems")
On my blog I provide a basic example of that (Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding) which looks like this where someone would bind to IsMemebershipAtMax such as what you are doing:
public bool IsMembershipAtMax
{
get { return MemberCount > 3; }
}
public int MemberCount
{
get { return _MemberCount; }
set
{
_MemberCount = value;
OnPropertyChanged();
OnPropertyChanged("IsMembershipAtMax");
}
}
public List<string> Members
{
get { return _Members; }
set { _Members = value; OnPropertyChanged(); }
}

Related

WPF MVVM Multibinding - How to pass two parameters to Command using RelayCommand

How to pass 2 parameters to command using RelayCommand. I need to pass COntrols as parameters (Grid and Window). I'm fully aware that such kind of problem has already existed on Stack Overflow but I'm struggling with adjusting that to my needs.
See look my first attempt is following. It obviously doesn't work because the Relay can't get 2 arguments.
Xaml code:
<Button Name="StretchScreenBtn" Content="Stretch screen" DataContext=" {StaticResource CommandWindow}" Command="{Binding ResizeScreenCommand}"
Width="100" Height="50">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource CommandParamsConv}">
<Binding ElementName="ScreenGrid" />
<Binding RelativeSource="{RelativeSource AncestorType=Window}" />
</MultiBinding>
</Button.CommandParameter>
</Button>
The Command code from ViewModel:
private ICommand _ResizeScreenCommand;
public ICommand ResizeScreenCommand
{
get
{
if (_ResizeScreenCommand == null)
{
_ResizeScreenCommand = new RelayCommand(
(argument1, argument2) =>
{
Grid grid = argument1 as Grid;
Window window = argument2 as Window;
if ((int)grid.GetValue(Grid.ColumnSpanProperty) == 2)
{
grid.SetValue(Grid.ColumnSpanProperty, 1);
window.WindowStyle = WindowStyle.None;
}
else
{
grid.SetValue(Grid.ColumnSpanProperty, 2);
window.WindowStyle = WindowStyle.SingleBorderWindow;
}
}
);
}
return _ResizeScreenCommand;
}
}
And the MultiValueConverter:
class CommandParamsConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
object[] parameters = values;
return parameters;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
My second attempt I followed the solution from How to Passing multiple parameters RelayCommand?
So, I've created within the ViewModel the new class, with two properties Grid and Window types and then tried to bind in xaml the Elements to these properties. But i this case the compiler complains that these properties are non-dependancy and cannot be bindded.
Please give me any hint how to solve it?
Below is the modified xaml code:
<Button Name="StretchScreenBtn" Content="Stretch screen" DataContext="{StaticResource CommandWindow}" Command="{Binding ResizeScreenCommand}"
Width="100" Height="50">
<Button.CommandParameter>
<vm:StretchingModel Grid="{Binding ElementName=ScreenGrid}" Win="{Binding RelativeSource={RelativeSource AncestorType=Window}}" />
</Button.CommandParameter>
</Button>
And the additionall class in the ViewModel:
class StretchingModel
{
public Grid Grid { get; set; }
public Window Win { get; set; }
}
Passing a Grid and a Window to a view model is not MVVM...a view model shouldn't have any dependencies upon any UI elements.
Anyway, to pass more than one value to the command you should combine your two approaches. The converter should return an instance of a StretchingModel:
class CommandParamsConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new StretchingModel() { Grid = values[0], Window = values[1] };
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
...that the command accepts:
private ICommand _ResizeScreenCommand;
public ICommand ResizeScreenCommand
{
get
{
if (_ResizeScreenCommand == null)
{
_ResizeScreenCommand = new RelayCommand(
(argument) =>
{
StretchingModel model = argument as StretchingModel;
...
}
);
}
return _ResizeScreenCommand;
}
}

How to change the font color of a datatemplated wpf listbox using a converter?

I have a DataTemplate that is used by a listbox:
<local:BooleanToFontColorConverter x:Key="boolToFontColor" />
<DataTemplate x:Key="ListBox_DataTemplateSpeakStatus">
<Label Width="Auto">
<TextBlock Name="MY_TextBlock" Text="Hello!" Foreground="{Binding Path=MY_COLOR, Converter={StaticResource boolToFontColor}}" />
</Label>
</DataTemplate>
MY_COLOR is the following bit of code:
public class Packet_Class : INotifyPropertyChanged
{
private bool _my_color = false;
public bool MY_COLOR { get { return _my_color; }
set { _my_color = value; RaisePropertyChanged("MY_COLOR"); } }
}
and then when appropriate I set the property, which I think would fire the RaisePropertyChanged function
myPacketClass.MY_COLOR = true;
while boolToFontColor is "trying" to use this bit:
public class BooleanToFontColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
if (value is Boolean)
{
return ((bool)value) ? new SolidColorBrush(Colors.Red) : new SolidColorBrush(Colors.Black);
}
return new SolidColorBrush(Colors.Black);
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}
When I change the value of MY_COLOR from true to false, or vice versa, I see no visible changes in my text foreground color during runtime. Is anyone able to give advice as to where I am going wrong? Much appreciated and thank you in advance.
EDIT:
Some additional information to attempt to provide more clarity. I am using my DataTemplate in a ListBox like this:
<ListBox x:Name="MyUserList" ItemTemplate="{StaticResource ListBox_DataTemplateSpeakStatus}" SelectionMode="Extended" />
And in my WPF Window element I set my local namespace to the namespace that my mainwindow.xaml.cs is encapsulated in:
xmlns:local ="clr-namespace:My_NameSpace"
the RaisePropertyChanged method should raise the PropertyChanged event define in the interface and look like:
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged (string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
the converter:
public class BooleanToFontColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
if (value is Boolean)
{
return ((bool)value) ? new SolidColorBrush(Colors.Red) : new SolidColorBrush(Colors.Black);
}
return new SolidColorBrush(Colors.Black);
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}
You have to use a SolidColorBrush to make it work.
It works on my environment, let me know if you encounter any trouble.

How can you get a XAML TextBlock in WP7 Silverlight to collapse when it contains no data?

I have a textblock inside a list view that I need to hide or collapse when it is empty or null. I tried using a string converter but that does not do it.
Any other ideas?
Update # 1:
Here is the code inside the textblock:
Visibility="{Binding Converter={StaticResource StringConverter}}
Here is the converter:
public class StringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
return string.IsNullOrEmpty(value.ToString()) ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
I would recommend creating text and visibility bindings on your textbox.
Here's an example of the view model properties you'd have.
public String TextBoxText
{
get { return textBoxText; }
set
{
if (value != textBoxText)
{
textBoxText= value;
SetTextBoxVisibility();
OnPropertyChanged("TextBoxText");
}
}
}
private String textBoxText;
public Visibility TextBoxVisibility
{
get { return textBoxVisibility; }
set
{
if (value != textBoxVisibility)
{
textBoxVisibility= value;
OnPropertyChanged("TextBoxVisibility");
}
}
}
private Visibility textBoxVisibility;
public void SetTextBoxVisibility()
{
this.TextBoxVisibility = String.IsNullOrEmpty(this.TextBoxText) ? Visibility.Collapsed : Visibility.Visible;
}
The only thing you've not shown of your code is where you instantiate the converter class. Is this because you're not doing so?
Typically you'd add something like this to app.xaml:
<Application.Resources>
<ResourceDictionary>
<conv:StringConverter x:Key="StringConverter " />
</ResourceDictionary>
</Application.Resources>

Bind a Silverlight TabControl to a Collection

I have a Collection of Model-objects in my ViewModel. I would like to be able to bind a TabControl to these and use a DataTemplate to extract the information from the Model-objects. When I try to do this I get the errormessage: Unable to cast object of type Model to object of type TabItem. After spending some time looking for a solution I found the following:
The Silverlight TabControl is
broken. Use a combination of ListBox
and ContentControl to mimic the
behaviour of a TabControl. (Means
that I have to skin the ListBox to
look like a TabControl)
TabControl does not override
PrepareContainerForItemOverride and
the solution is to make a
Converter. (Not so good because I
then need to specify the type of the
convertee in the Converter)
Anyone know any better solution?
XAML
<sdk:TabControl ItemsSource="{Binding Items, ElementName=MyControl}">
<sdk:TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</sdk:TabControl.ItemTemplate>
</sdk:TabControl>
C#
public ObservableCollection<Model> Items { get; set; }
public ViewModel()
Items = new ObservableCollection<Model>{
new Model { Name = "1"},
new Model { Name = "2"},
new Model { Name = "3"},
new Model { Name = "4"}
};
}
Suggested Converter:
public class TabConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
List<TabSource> source = value as List<TabSource>;
if (source != null)
{
List<TabItem> result = new List<TabItem>();
foreach (TabSource tab in source)
{
result.Add(new TabItem()
{
Header = tab.Header,
Content = tab.Content
});
}
return result;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Create converter
public class SourceToTabItemsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
var source = (IEnumerable)value;
if (source != null)
{
var controlTemplate = (ControlTemplate)parameter;
var tabItems = new List<TabItem>();
foreach (object item in source)
{
PropertyInfo[] propertyInfos = item.GetType().GetProperties();
//тут мы выбираем, то поле которое будет Header. Вы должны сами вводить это значение.
var propertyInfo = propertyInfos.First(x => x.Name == "name");
string headerText = null;
if (propertyInfo != null)
{
object propValue = propertyInfo.GetValue(item, null);
headerText = (propValue ?? string.Empty).ToString();
}
var tabItem = new TabItem
{
DataContext = item,
Header = headerText,
Content =
controlTemplate == null
? item
: new ContentControl { Template = controlTemplate }
};
tabItems.Add(tabItem);
}
return tabItems;
}
return null;
}
catch (Exception)
{
return null;
}
}
/// <summary>
/// ConvertBack method is not supported
/// </summary>
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException("ConvertBack method is not supported");
}
Create ControlTemplate:
<ControlTemplate x:Key="MyTabItemContentTemplate">
<StackPanel>
<TextBlock Text="{Binding Path=name}" />
</StackPanel>
</ControlTemplate>
And binding convert, controltemplate
<controls:TabControl x:Name="tabControl"
ItemsSource="{Binding ElementName=tabControl,
Path=DataContext,
Converter={StaticResource ConverterCollectionToTabItems},
ConverterParameter={StaticResource MyTabItemContentTemplate}}">
</controls:TabControl>
taken from the blog binding-tabcontrol

WPF/XAML - hide image if another control's text property is empty/not set

i'm new to wpf and this is my first attempt of creating a custom user control. its purpose is to display two values (myText1 and myText2) with their corresponding images (myimage1, myimage2). sometimes, one of these values is not set and therefore oneimage should be hidden as well. here's my code so far:
Window1.xaml
<local:myControl myText2="Hello World!" />
myControl.xaml
<TextBlock Text="{Binding ElementName=myControl,Path=myText1}" />
<Image Source="myimage1.jpg" />
<TextBlock Text="{Binding ElementName=myControl,Path=myText2}" />
<Image Source="myimage2.jpg" />
myText1 was not set in window1.xaml and therefore the textblock remains empty. but the image is still displayed. which lines of code am i missing to hide the image if myText1 (or myText2) was not set in window1.xaml?
You have write converter for text to visibility
public class TextToVisibilityConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is string && targetType == typeof(bool))
{
if (value.ToString().Equals(string.Empty))
return Visibility.Hidden;
else
return Visibility.Hidden;
}
else
{
return null;
}
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is Visibility && targetType == typeof(string))
{
if ((Visibility)value == Visibility.Visible)
{
return "Text";
}
else
{
return string.Empty;
}
}
else
{
return null;
}
}
}
And in XAML < TextToVisibilityConverter x:Key="myCnverter"/>
Couple of small mistakes there:
if (value is string && targetType == typeof(bool))
{
if (value.ToString().Equals(string.Empty))
return Visibility.Hidden;
else
return Visibility.Hidden;
}
Should be
if (value is string && targetType == typeof(Visibility))
{
if (value.ToString().Equals(string.Empty))
return Visibility.Hidden;
else
return Visibility.Visible;
}
You need the following usings:
using System.Windows;
using System.Windows.Data;
You may also consider returning Visibility.Collapsed rather than Visibility.Hidden
Once you create the right converter then is easy.
And not much answers also got that Text.IsEmpty is available for TextBlock Text property
I created a BooleanVisibilityConverter that depends on a parameter the boolean against True or False.
Gives you the !True flexibility that is missing on xaml.
public class BooleanVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Visibility v = Visibility.Collapsed;
bool checkValue = true;
if(parameter != null)
{
checkValue = Boolean.Parse(parameter.ToString());
}
if(value.Equals(checkValue))
{
v = Visibility.Visible;
}
return v;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then in your xaml import that namespace into:
xmlns:conv="clr-namespace:ConvertersNamespace"
Create the converter into the Resources:
<UserControl.Resources>
<conv:BooleanVisibilityConverter x:Key="bool2vis" />
</UserControl.Resources>
Then just use into your design:
<TextBlock Text="{Binding ElementName=myControl,Path=myText1}" x:Name="txtBlock"/>
<Image Source="myimage1.jpg"
Visibility="{Binding ElementName=txtBlock,Path=Text.IsEmpty,
Converter={StaticResource bool2vis},ConverterParameter=False}"/>

Resources