I have a form that has multiple fields. I have also a "Validate" button that will action a DB input. I would like that button to be activated only if the bare minimum fields are defined by the user.
So far it was quite simple as all the fields were text:
<Button x:Name="Manage" Content="Manage">
<Button.IsEnabled>
<MultiBinding Mode="OneWay" Converter="{StaticResource FieldsFilledinToVisible}">
<Binding ElementName="name1" Path="Text"/>
<Binding ElementName="name2" Path="Text"/>
</MultiBinding>
</Button.IsEnabled>
</Button>
Converter being:
public class AllValuesDefinedConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
bool isEnabled = false;
for (int i = 0; i < values.Length; i++)
{
isEnabled = isEnabled || string.IsNullOrEmpty(values[i].ToString());
}
return !isEnabled;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return null;
}
}
But would now have to consider extra check boxes, where the condition should be [Any of these checkboxes checked + previous text fields defined --> Activate the validation button]:
<WrapPanel Style="{StaticResource WrapStyle_Inputs}">
<CheckBox Content="Check1" IsChecked="{Binding Checked1, Mode=TwoWay}"/>
<CheckBox Content="Check2" IsChecked="{Binding Checked2, Mode=TwoWay}"/>
<CheckBox Content="Check3" IsChecked="{Binding Checked3, Mode=TwoWay}"/>
</WrapPanel>
Would you know how I could do so?
thank you!
Converters are the wrong tool for the job. You should instead look at validation, commands, and view models. Your view model would implement validation logic and expose a command that your button is bound to. The command would only be executable if your validation logic passes.
Related
I have an ItemsControl with ItemsSource filled by an ObservableCollection of Paths. The Path class that implements INotifyPropertyChanged has a property named StrokeThickness :
private double _strokeThickness;
public double StrokeThickness
{
get { return _strokeThickness; }
set
{
_strokeThickness = value;
OnPropertyChanged(nameof(StrokeThickness));
}
}
In our ViewModel we have :
public ObservableCollection<Path> PathCollection
{
get { return _pathCollection; }
set
{
_pathCollection = value;
OnPropertyChanged(nameof(PathCollection));
}
}
And this is my View :
<!-- Paths -->
<ItemsControl ItemsSource="{Binding PathCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Stroke="{Binding Stroke}"
Data="{Binding Data}">
<Path.StrokeThickness>
<MultiBinding Converter="{StaticResource
CorrectStrockThiknessConvertor}">
<MultiBinding.Bindings>
<Binding Source="{Binding
StrokeThickness}"></Binding>
<Binding ElementName="RootLayout"
Path="DataContext.ZoomRatio" >
</Binding>
</MultiBinding.Bindings>
</MultiBinding>
</Path.StrokeThickness>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="Canvas"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
I have used a Multibinding Convertor to give me true StrokeThickness based on ZoomRatio. ZoomRatio is being calculated every time map zoomed.
This is my MultiBinding converter :
public class CorrectStrockThiknessConvertor : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values != null & values.Length == 2 && (double)values[0] != 0)
{
return (double)values[1] / (double)values[0];
}
return 1;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
when I trace this Converter its work fine but every time it's entry data for StrokeThikness is same and it means return value doesn't change StrokeThikness of Paths.
Am I doing something to wrong?
The first Binding in the MultiBinding is wrong. It should look like this:
<MultiBinding.Bindings>
<Binding Path="StrokeThickness" />
<Binding ElementName="RootLayout" Path="DataContext.ZoomRatio" />
</MultiBinding.Bindings>
Also check if the property name is correct, as you constantly write StrokeThikness instead of StrokeThickness in your question.
You should also check your converter code. It seems you are dividing ZoomRatio by StrokeThickness, which to my understanding should be the other way round.
I have a "Checked ListBox", a ListBox with a CheckBox for each of my Items.
In this ListBox I have a List of Players.
<ListBox ItemsSource="{Binding MyPlayers}" SelectedItem="{Binding SelectedPlayer}" Margin="69,51,347.4,161.8">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem>
<CheckBox IsChecked="{Binding ???}" Content="{Binding Path=Pseudo}" />
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ViewModel :
public class MainWindowViewModel : BaseViewModel
{
SavingContext MyDatabaseContext;
public ObservableCollection<Player> MyPlayers
{
get
{
return MyDatabaseContext.MyPlayers.Local;
}
}
Tournament _MyTournament;
public Tournament MyTournament
{
get
{
return _MyTournament;
}
set
{
_MyTournament = value;
}
}
public MainWindowViewModel(Tournament myTournament)
{
MyDatabaseContext = new SavingContext();
MyDatabaseContext.MyPlayers.Load();
MyTournament = myTournament;
}
}
I pass in my ViewModel a Tournament, that contains an HashSet of Players called Participants.
I would like to bind my IsChecked properties to the result of MyTournament.Participants.Contains(this) with this being the Player related to the CheckBox. But I can't manage to make it works.
Edit :
I tried to use a Converter, but with no success.
<helper:PlayerToTournamentRegistered x:Key="PlayerToTournamentRegistered" />
<ListBoxItem>
<CheckBox IsChecked="{Binding Converter={StaticResource PlayerToTournamentRegistered}}" Content="{Binding Path=Pseudo}" />
</ListBoxItem>
public class PlayerToTournamentRegistered : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//Temp test to see if it would work
if (value == null) return false;
return true;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I get an error each time Provide value on 'System.Windows.Data.Binding' threw an exception
Any Advices ?
So I managed to get it working using a MultiConverter.
The first error that I had was due to the fact that the Mode is by Default Two Way, I only want to have my Converter work OneWay.
I used a MultiBinding to send to my Converter both my ListBoxItem and my Tournament.
My Converter return true or false, and is linked to my CheckBox.IsChecked properties.
List Box :
<ListBox ItemsSource="{Binding MyPlayers}" SelectedItem="{Binding SelectedPlayer}" Margin="69,51,347.4,161.8">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem>
<CheckBox Content="{Binding Path=Pseudo}" >
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource PlayerToTournamentRegistered}" Mode="OneWay">
<Binding />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=Window}" Path="DataContext.MyTournament"/>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Converter :
public class PlayerToTournamentRegistered : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if(!(values[0] is Student) || !(values[1] is Tournament))
{
return false;
}
Tournament myTournament = (Tournament)values[1];
Student myPlayer = (Student)values[0];
if (myTournament.Participants.Contains(myPlayer))
return true;
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Is it possible to bind the same property of the control more than once?
To example:
<Popup IsOpen="{Binding Path=(local:ListViewBehavior.IsColumnHeaderClicked),
RelativeSource={RelativeSource FindAncestor, AncestorType=GridViewColumnHeader}}" ...
As you can see Popup.IsOpen is bound to attached property. I'd like to bind it to ViewModel IsPopupOpened, but have no idea how.
Trying #Arhiman answer without much success:
<Popup.IsOpen>
<MultiBinding Converter="{local:MultiBindingConverter}">
<Binding Path="(local:ListViewBehavior.IsColumnHeaderClicked)"
RelativeSource="{RelativeSource FindAncestor, AncestorType=GridViewColumnHeader}" />
<Binding Path="DataContext.IsPopupId"
RelativeSource="{RelativeSource FindAncestor, AncestorType=UserControl}" />
</MultiBinding>
</Popup.IsOpen>
Naive converter logic:
public class MultiBindingConverter : MarkupExtension, IMultiValueConverter
{
public MultiBindingConverter() { }
public override object ProvideValue(IServiceProvider serviceProvider) => this;
object[] _old;
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (_old == null)
_old = values.ToArray();
// check if one of values is changed - that change is a new value
for (int i = 0; i < values.Length; i++)
if (values[i] != _old[i])
{
_old = values.ToArray();
return values[i];
}
return values[0];
}
// replicate current value
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
Enumerable.Repeat(value, targetTypes.Length).ToArray();
}
You could simply use a MultiBinding with a converter to implement the logic you'd like.
<Popup.IsOpen>
<MultiBinding Converter="{StaticResource openLogicConverter}">
<Binding Path="MyAttachedProperty" ... />
<Binding Path="IsPopupOpened" />
</MultiBinding>
</Popup.IsOpen>
I'd usually put this logic in the ViewModel, but as it is an AttachedProperty, something directly in the View seems more appropriate to me.
In my WPF application (using MVVM) I have a CheckBox and a TextBlock. When the CheckBox is checked the value from the TextBlock will be saved. There is a binding from both controls to my ViewModel. Below simplified XAML:
<StackPanel>
<Label>Add to list</Label>
<CheckBox IsChecked="{Binding Path=AddItem}"></CheckBox>
<Label>Gross amount:</Label>
<TextBlock Text="{Binding Path=Amount}"></TextBlock>
</StackPanel>
Now I would like to have the CheckBox checked when a user starts typing in the TextBlock. I know binding can do that but I already bind to a property in my ViewModel. How can I bind to a property in ViewModel and to other control?
You should use the multibinding. Something like this:
<CheckBox Content="CheckBox" HorizontalAlignment="Left" Margin="191,82,0,0" VerticalAlignment="Top">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource checkConverter}">
<Binding Path="IsChecked"/>
<Binding Path="UserStartedTyping"/>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
checkConverter is a MultiValueConverter that you need in order to decide what to do with the values you are binding with (such as &&, || etc.).
public class CheckConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return (bool)((bool)values[0] || (bool)values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
object[] splitValues = { value, false };
return splitValues;
}
}
UserStartedTyping is a property in the ViewModel that would be set to true when KeyDown event is fired.
Hope it helps.
You can try setting the AddItem to true when the user starts to change the amount value:
private string _amt;
public string Amount
{
get{return _amt;}
set
{
_amt = value;
if(AddItem == false)
AddItem = true;
//PropertyChanges here
}
}
I using json web server to retrieve data and put it into ObservableCollection that I bind it into my xaml, so i want to show index like
xxxx
xxxxxxx
How i can get number 1, 2 and so on?
If you are using DataGrid then in that case you need to enable DisplayRowNumber property and in the LoadingRow event of DataGrid you can set Row.Header with the index property. Code can be like
<DataGrid Name="dataGrid" LoadingRow="OnLoadingRow" behaviors:DataGridBehavior.DisplayRowNumber="True" ItemsSource="{Your Binding}" />
void OnLoadingRow(object sender, DataGridRowEventArgs e)
{
e.Row.Header = (e.Row.GetIndex() + 1).ToString();
}
Edit: As you want this for ListBox, so I suggest you to please check this solution. In this user is creating Index field and binding that with the ListBox.
Index = myCollection.ToList().IndexOf(e)
Also you can check Hannes blog post as well. He is showing example for Silverlight but it will work with WPF also.
You can do that using IMultiValueConverter which will return index.
XAML
<ListBox x:Name="listBox" ItemsSource="{Binding YourCollection}">
<ListBox.Resources>
<local:RowIndexConverter x:Key="RowIndexConverter"/>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource RowIndexConverter}">
<Binding/>
<Binding ElementName="listBox" Path="ItemsSource"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Converter:
public class RowIndexConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
IList list = (IList)values[1];
return list.IndexOf(values[0]).ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}