I just finished with watching practical mvvm presentation video on youtube and if I understand correctly moving logic from codebehing to view model is good practice and we should strive to this pattern.
Having this in mind I have one simple question. Inside xaml I have four radio buttons
<StackPanel x:Name="panel">
<RadioButton GroupName="myGroup" Name="Option1" Content="option one" IsChecked="True" Width="40"/>
<RadioButton GroupName="myGroup" Name="Option2" Content="option two" IsChecked="False" Width="80"/>
<RadioButton GroupName="myGroup" Name="Option3" Content="option three" IsChecked="False" Width="60"/>
</StackPanel>
I want to use this code inside viewmodel below to fetch selected radio btn.
var checkedValue = panel.Children.OfType<RadioButton>()
.FirstOrDefault(r => r.IsChecked.HasValue && r.IsChecked.Value);
Question is: How can I access to this panel object from the viewmodel? It's not data to use binding.
Update:
as #Rohit Vatss said "View objects should not be accessed from ViewModel" I would change question to How to know which radio button is selected using viewmodel?
You can do it by creating one property in you ViewModel, lets say GroupIndex
private int _groupIndex = 1;
public int GroupIndex
{
get { return _groupIndex; }
set
{
if (_groupIndex == value) return;
_groupIndex = value;
OnPropertyChanged("GroupIndex");
}
}
then create simple converter which will convert current GroupIndex value to true or false and back:
public class IndexBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || parameter == null)
return false;
else
return (int)value == System.Convert.ToInt32(parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || parameter == null)
return null;
else if ((bool)value)
return System.Convert.ToInt32(parameter);
else
return DependencyProperty.UnsetValue;
}
}
and then bind your RadioButton to GroupIndex which will will be set to 1, 2 or 3 depending on which RadioButton is checked
<StackPanel>
<StackPanel.Resources>
<local:IndexBooleanConverter x:Key="IndexBooleanConverter"/>
</StackPanel.Resources>
<RadioButton Content="Option1" IsChecked="{Binding Path=GroupIndex, Converter={StaticResource IndexBooleanConverter}, ConverterParameter=1}"/>
<RadioButton Content="Option2" IsChecked="{Binding Path=GroupIndex, Converter={StaticResource IndexBooleanConverter}, ConverterParameter=2}"/>
<RadioButton Content="Option3" IsChecked="{Binding Path=GroupIndex, Converter={StaticResource IndexBooleanConverter}, ConverterParameter=3}"/>
</StackPanel>
In this case GroupIndex is int but you can also use same logic if for example your GroupIndex is an enum
Well an aprroach would be for example using a Command and a Command Parameter on the radiobuttons Binded to your viewmodel. For example:
<RadioButton GroupName="myGroup" Name="Option1" Content="option one" IsChecked="True" Width="40" Command="{Binding RbCheckedCommand}" CommandParameter="RB1"/>
<RadioButton GroupName="myGroup" Name="Option2" Content="option two" IsChecked="False" Width="80" Command="{Binding RbCheckedCommand}" CommandParameter="RB2"/>
<RadioButton GroupName="myGroup" Name="Option3" Content="option three" IsChecked="False" Width="60" Command="{Binding RbCheckedCommand}" CommandParameter="RB3"/>
Then on your ViewModel:
private readonly DelegateCommand<object> rbCheckedCommand;
public ICommand RbCheckedCommand
{
get { return this.rbCheckedCommand; }
}
private void RbCheckedCommandExecute(object obj)
{
string rbselected = obj.ToString();
}
And on the constructor of the class:
this.rbCheckedCommand = new DelegateCommand<object>(RbCheckedCommandExecute);
You will have to use the prism adding:
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.ViewModel;
And making your class inherits from NotificationObject so you can use the Property changed easily.
Hope it helps.
Related
I have an application with four radio buttons to select the mode I am operating.
All the 4 Radio Buttons are binded to the same property. When i start the program the none of the radio button is checked.
This is the code for the radio buttons:
<GroupBox Grid.Row="0" Margin="10,10,10,10" FontSize="16"
FontWeight="Bold">
<GroupBox.Header>Tipo di Rientro</GroupBox.Header>
<StackPanel>
<RadioButton Name="RdBtnExternalEntry" FontSize="12"
FontWeight="Normal" GroupName="SelectionType" IsChecked="{Binding
Path=CurrentOption, Mode=TwoWay, Converter={StaticResource
enumConverter}, ConverterParameter=ExternalEntry}">Entrata da
Esterno</RadioButton>
<RadioButton Name="RdBtnEntryAfterCheck" FontSize="12"
FontWeight="Normal" GroupName="SelectionType" IsChecked="{Binding
Path=CurrentOption, Mode=TwoWay, Converter={StaticResource
enumConverter}, ConverterParameter=EntryAfterCheck}">Rientro dopo
visione</RadioButton>
<RadioButton Name="RdBtnEntryMissingShipping" FontSize="12"
FontWeight="Normal" GroupName="SelectionType" IsChecked="{Binding
Path=CurrentOption, Mode=TwoWay, Converter={StaticResource
enumConverter}, ConverterParameter=EntryMissingShipping}">Rientro
per mancata Spedizione</RadioButton>
<RadioButton Name="RdBtnEntryAfterPicking" FontSize="12"
FontWeight="Normal" GroupName="SelectionType" IsChecked="{Binding
Path=CurrentOption, Mode=TwoWay, Converter={StaticResource
enumConverter}, ConverterParameter=EntryAfterPicking}">Rientro
dopo Picking</RadioButton>
</StackPanel>
</GroupBox>
This is the property:
public RadioOptions CurrentOption
{
get => _currentOption;
set
{
_currentOption = value;
NewLoadCommand.RaiseCanExecuteChanged();
ConfirmCommand.RaiseCanExecuteChanged();
if (value == RadioOptions.ExternalEntry)
{
_selectedStockUnitCode = PalletToDo;
SelectedLoadnumber = LoadToDo;
RaisePropertyChanged("SelectedLoadnumber");
RaisePropertyChanged("SelectedStockUnitCode");
}
else
{
SelectedLoadnumber = "0";
RaisePropertyChanged("SelectedLoadnumber");
}
RaisePropertyChanged("SelectedStockUnitCodeIsEnabled");
RaisePropertyChanged("SelectedLoadnumberIsEnabled");
}
}
And this is the converter:
public class EnumMatchToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value == null || parameter == null)
return false;
string checkValue = value.ToString();
string targetValue = parameter.ToString();
return checkValue.Equals(targetValue,
StringComparison.InvariantCultureIgnoreCase);
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value == null || parameter == null)
return null;
bool useValue = (bool)value;
string targetValue = parameter.ToString();
if (useValue)
return Enum.Parse(targetType, targetValue);
return Binding.DoNothing;
}
}
these are the radio options:
public enum RadioOptions { ExternalEntry, EntryAfterCheck, EntryMissingShipping, EntryAfterPicking }
I expect the first combobox to be checked at the start of the program
Resolved! I was, by mistake launching another copy of my window as a popup istance messing up with the way xaml manages the radio buttons. I deleted one of the istance and then it worked fine.
i'm trying to bind a name of a file given by a filepath to a TextBlock. The filepath is stored in a list which is bound to the ItemsSourceProperty of a ListBox. The TextBlock is set as DataTemplate.
My question is: How can i get the name without path and extension and bind it to the TextBlock?
The XAML code for better explanation:
<ListBox Name="MyListBox" Margin="2">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And the code behind:
string path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
List<string> PathList = Directory.GetFiles(path, "*.txt").ToList();
Binding myBind = new Binding();
myBind.Source = PathList;
myListBox.SetBinding(ListBox.ItemsSourceProperty, myBind);
One uses a converter to change the text of a selected listbox item which is fully pathed to just the filename.
In the following example there is a list and a textbox next to it. Once an item is selected, the textbox bound to the list's SelectedItem extracts the pathed string which is passed to a converter which returns just the filename to show.
Example
XAML
<Window x:Class="WPFStack.ListBoxQuestions"
xmlns:local="clr-namespace:WPFStack"
xmlns:converters="clr-namespace:WPFStack.Converters"
.../>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<converters:PathToFilenameConverter x:Key="FilenameConverter" />
<x:Array x:Key="FileNames" Type="system:String">
<system:String>C:\Temp\Alpha.txt</system:String>
<system:String>C:\Temp\Beta.txt</system:String>
</x:Array>
</StackPanel.Resources>
<ListBox Name="lbFiles"
ItemsSource="{StaticResource FileNames}" />
<TextBlock Text="{Binding SelectedItem,
ElementName=lbFiles,
Converter={StaticResource FilenameConverter}}"
Margin="6,0,0,0" />
</StackPanel>
Converter
namespace WPFStack.Converters
{
public class PathToFilenameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
object result = null;
if (value != null)
{
var path = value.ToString();
if (string.IsNullOrWhiteSpace(path) == false)
result = Path.GetFileNameWithoutExtension(path);
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
}
ItemTemplate Use of Converter
The converter is reused in the template as such
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource FilenameConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
If you just want to add a static list to list box you should do it like this.
XAML:
<ListBox x:Name="lb" ItemsSource="{Binding Collection}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The code behind constructor for in the main window:
public MainWindow()
{
InitializeComponent();
List<string> l = new List<string>();
l.Add("string path 1");
l.Add("string path 2");
l.Add("string path 3");
l.Add("string path 4");
lb.ItemsSource = l;
}
You should be aware that there is a much better way of doing these things. I would honestly suggest you go look MVVM and doing a proper binding to a ViewModel.
I have 3 Checkbox in xaml file.Name sit,stand,sleep.
Extype having the following values 1,2,3
CheckBox Content="Sit" Margin="127,89,212,136" IsChecked="{Binding Extype}" RenderTransformOrigin="1.817,-1.029"/>
CheckBox Content="Stand" Margin="127,89,212,136" IsChecked="{Binding Extype}" RenderTransformOrigin="1.817,-1.029"/>
CheckBox Content="Sleep" Margin="127,89,212,136" IsChecked="{Binding Extype}" RenderTransformOrigin="1.817,-1.029"/>
If the Extype value is one means I need to select Sit check box.
If the Extype value is two means I need to select stand check box.
If the Extype value is three means I need to select both check boxes.
How can I do this?
XAML:
<CheckBox Content="Sit" IsChecked="{Binding IsSit, Mode=OneWay}" IsEnabled="False"/>
<CheckBox Content="Stand" IsChecked="{Binding IsStand, Mode=OneWay}" IsEnabled="False"/>
<CheckBox Content="Sleep" IsChecked="{Binding IsSleep, Mode=OneWay}" IsEnabled="False"/>
ViewModel:
public bool IsSit
{
get
{
return ExtType == 1 || ExtType == 3;
}
}
public bool IsStand
{
get
{
return ExtType == 2 || ExtType == 3;
}
}
private int _extType;
public int ExtType
{
get
{
return _extType;
}
set
{
_extType = value;
RaisePropertyChanged("IsSit");
RaisePropertyChanged("IsStand");
}
}
You can use a ValueConverter with a parameter. Use the same ValueConverter in each checkbox but change the parameter value.
<CheckBox Content="Sit" IsChecked="{Binding Extype, Converter={StaticResource YourConverter}, ConverterParameter=Sit}" />
<CheckBox Content="Standup" IsChecked="{Binding Extype, Converter={StaticResource YourConverter}, ConverterParameter=Standup}" />
Here an example of a ValueConverter with parameters:
http://wpftutorial.net/ValueConverters.html
(Remember to add your ValueConverter as a Resource.)
And then put your business logic code inside the ValueConverter or even better, call a function in your Business Layer.
I am using IValueconverter interface to change the tooltip text of an image.
The tool tip should change based on label.
<Label Content="9898980001" Height="28" HorizontalAlignment="Left" Margin="1733,231,0,0" Name="lbl02scanning" VerticalAlignment="Top" Foreground="Blue" >
<Image Height="49" HorizontalAlignment="Right" Margin="0,131,113,0"
Name="img02scanning"
Source="/TEST;component/Images/LoadingStation.png" Stretch="Fill"
VerticalAlignment="Top" Width="30" Cursor="Hand">
<Image.ToolTip>
<StackPanel Background="AliceBlue">
<TextBlock Padding="5" Foreground="White" MinHeight="20"
Background="Blue" FontWeight="Bold"
Text="Scanning Station" />
<StackPanel Orientation="Horizontal">
<Image
Source="pack://application:,,,/TEST;component/Images/coilonsaddle_large.png"
Height="100" Width="100" />
<TextBlock Padding="10" TextWrapping="WrapWithOverflow"
MaxWidth="200" Background="AliceBlue"
Foreground="Black" FontWeight="Bold"
Text="{Binding ElementName=lbl02scanning, Path=Name,
ConverterParameter=255,
Converter={StaticResource FormatterFOrCoilToolTip}}"/>
</StackPanel>
<TextBlock Padding="5" Foreground="White" MinHeight="20"
Background="Blue" FontWeight="Bold"
Text="Report to admin in case of coil location mismatch"/>
</StackPanel>
</Image.ToolTip>
</Image>
The converter class:
public class FormatterForCoilToolTip : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(parameter.ToString() == "02")
{
return value.ToString() + " Startin";
}
else
{
return value.ToString() + " Finishing";
}
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The tooltip's Textblock content is not changing. But if i change to:
Text="{Binding ConverterParameter=255, Converter={StaticResource FormatterFOrCoilToolTip}}
then it is working. But i want to pass the lbl02scanning text value. Why it is not working??
First of all you should bind to Content property and not Name property in case you want Text of Label.
Most importantly Tooltip does not lies in same Visual Tree as that of label, hence binding with elementName won't work. However, you can use x:Reference to get the element even if it doesn't exist in same Visual Tree.
Text="{Binding Source={x:Reference lbl02scanning}, Path=Content,
ConverterParameter=255,
Converter={StaticResource FormatterFOrCoilToolTip}}"/>
Note - x:Reference is introduced in WPF 4.0. If you are using WPF 3.5 you can't use this.
Update for error - service provider is missing the name resolver service
Just found out bug is reported at Microsoft site that x:Reference fails in case Target is Label. However, i couldn't reproduce this issue at my end since i have WPF 4.5 installed at my end and i guess they have fixed the issue in future version.
In case you target WPF 4.0, i would advise you to use TextBlock in place of Label:
<TextBlock Text="9898980001" Height="28" HorizontalAlignment="Left"
Margin="1733,231,0,0" Name="lbl02scanning" VerticalAlignment="Top"
Foreground="Blue" />
and then bind with Text property instead of Content.
Text="{Binding Source={x:Reference lbl02scanning}, Path=Text,
ConverterParameter=255,
Converter={StaticResource FormatterFOrCoilToolTip}}"/>
Either, you can refer to workaround provide under workarounds section here.
You can override the ProvideValue method of the Reference class and skip the reference search login in design time:
[ContentProperty("Name")]
public class Reference : System.Windows.Markup.Reference
{
public Reference()
: base()
{ }
public Reference(string name)
: base(name)
{ }
public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget valueTargetProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (valueTargetProvider != null)
{
DependencyObject targetObject = valueTargetProvider.TargetObject as DependencyObject;
if (targetObject != null && DesignerProperties.GetIsInDesignMode(targetObject))
{
return null;
}
}
return base.ProvideValue(serviceProvider);
}
Update with another workaround
This will work for all versions WPF 3.5, WPf 4.0 and WPF 4.5.
First of all bind Image Tag with content of label.
Second host your stackPanel inside ToolTip control so that you can
take benefit of PlacementTarget property.
Third bind with PlacementTarget.Tag of Tooltip.
Relevant code will look like this:
<Image Tag="{Binding ElementName=lbl02scanning,Path=Content}">
<Image.ToolTip>
<ToolTip>
<TextBlock Text="{Binding RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType=ToolTip},
Path=PlacementTarget.Tag,
ConverterParameter=255,
Converter={StaticResource FormatterFOrCoilToolTip}}"/>
</ToolTip>
</Image.ToolTip>
</Image>
Also you need to update converter code to put null check over there since PlacementTarget will be null until you open tooltip.
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value != null)
{
if (parameter.ToString() == "02")
{
return value.ToString() + " Starting";
}
else
{
return value.ToString() + " Finishing";
}
}
return String.Empty;
}
Try This
Text="{Binding Path=Content,ElementName=lbl02scanning, ConverterParameter=255, Converter={StaticResource FormatterFOrCoilToolTip}}
My DataGrid has a default set of columns to display, but I'd also like to let the user select/un-select columns displayed on their application. Is there a relatively easy way to do that in WPF?
The DataGrid is bound to a DataTable.
Note: I may just go with a simple "Default Columns/All Columns" via RadioButton solution if the above feature is too complicated.
The short answer is, bind the Visibility property of each column to a boolean flag that you're able to set (via a CheckBox or some other mechanism), and use a BooleanToVisibilityConverter to make the column visibility Collapsed when the flag is unset.
Dig this similar question, and especially this answer! His blog post lists what would be my ideal solution. :)
Bind the DataGrid.Columns to an ItemsControl with a DataTemplate that contains a CheckBox for visibility-toggling, no code required except for the VisbilityToBoolConverter:
<Window
...
DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}" Loaded="Window_Loaded">
<Window.Resources>
<local:VisibilityToBoolConverter x:Key="VisibilityToBoolConv"/>
</Window.Resources>
<StackPanel Orientation="Vertical">
<DataGrid ItemsSource="{Binding Data}" Name="DGrid"/>
<ItemsControl ItemsSource="{Binding ElementName=DGrid, Path=Columns}" Grid.IsSharedSizeScope="True" Margin="5">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="A"/>
<ColumnDefinition SharedSizeGroup="B"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Header}" Margin="5"/>
<CheckBox Grid.Column="1" IsChecked="{Binding Visibility, Converter={StaticResource VisibilityToBoolConv}}" Margin="5" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Window>
Note: I have a TextBlock which assumes the Column-Header to be a string, might need to be adjusted if that is not the case.
VisibilityConverter:
[ValueConversion(typeof(Visibility), typeof(bool))]
public class VisibilityToBoolConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Visibility vis = (Visibility)value;
switch (vis)
{
case Visibility.Collapsed:
return false;
case Visibility.Hidden:
return false;
case Visibility.Visible:
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if ((bool)value) return Visibility.Visible;
else return Visibility.Collapsed;
}
#endregion
}
I prefer to extend DataGrid class and add this functionality.
public class DataGridEx : DataGrid
{
public DataGridEx() : base()
{
// Create event for right click on headers
var style = new Style { TargetType = typeof(DataGridColumnHeader) };
var eventSetter = new EventSetter(MouseRightButtonDownEvent, new MouseButtonEventHandler(HeaderClick));
style.Setters.Add(eventSetter);
ColumnHeaderStyle = style;
}
private void HeaderClick(object sender, MouseButtonEventArgs e)
{
ContextMenu menu = new ContextMenu();
// Fill context menu with column names and checkboxes
var visibleColumns = this.Columns.Where(c => c.Visibility == Visibility.Visible).Count();
foreach (var column in this.Columns)
{
var menuItem = new MenuItem
{
Header = column.Header.ToString(),
IsChecked = column.Visibility == Visibility.Visible,
IsCheckable = true,
// Don't allow user to hide all columns
IsEnabled = visibleColumns > 1 || column.Visibility != Visibility.Visible
};
// Bind events
menuItem.Checked += (object a, RoutedEventArgs ea)
=> column.Visibility = Visibility.Visible;
menuItem.Unchecked += (object b, RoutedEventArgs eb)
=> column.Visibility = Visibility.Collapsed;
menu.Items.Add(menuItem);
}
// Open it
menu.IsOpen = true;
}
}
Now you can use this control instead of original DataGrid:
<dg:DataGridEx ItemsSource="{Binding OfflineData, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False">
<dg:DataGridEx.Columns>
<DataGridTextColumn Binding="{Binding PortID}" Header="ID" Width="50" Visibility="Collapsed"/>
<DataGridTextColumn Binding="{Binding SwitchIP}" Header="Switch IP" Width="100" Visibility="Visible"/>
<DataGridTextColumn Binding="{Binding Port}" Header="Port" Width="50" Visibility="Visible"/>
</dg:DataGridEx.Columns>
</dg:DataGridEx>
Now user can right-click on column header to open menu with checkboxes: