I've been trying to set up a "downloading..." popup as a rectangle with its visibility set programmatically in the related viewModel. If I set the boolean in the xaml.cs file it works perfectly but obviously it needs to be set in the viewmodel and it just won't change its visibility if done so.
I've checked on previous solutions involving raising the propertyChanged event and setting the binding two-way.
<Rectangle
Width="400"
Height="200"
x:Name="popup"
Fill="Red"
Visibility="{Binding PopupIsVisible, Converter={StaticResource ResBoolToVisibilityConverter}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
private bool popupIsVisible;
public bool PopupIsVisible
{
get { return popupIsVisible; }
set
{
Set(ref popupIsVisible, value);
RaisePropertyChanged("PopupIsVisible");
}
}
EDIT: as requested, here's the Converter
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
bool val;
try
{
val = (bool)value;
}
catch (Exception)
{
return Visibility.Visible;
}
if(val)
{
return Visibility.Visible;
}
else
{
return Visibility.Hidden;
}
}
EDIT2: The popup should be visibile either by pressing a button or by waiting 10 seconds on the view, oddly enough it gets shown only with the second scenario which is launched with the following code:
worker = Task.Factory.StartNew(() =>
{
while (cycle)
{
// Check for cancellation
cancellationToken.ThrowIfCancellationRequested();
LoadProcessList();
Task.Delay(TIME_TO_REFRESH).Wait();
}
}, cancellationToken);
Any ideas?
FINAL EDIT
I managed to solve it by encasing the function containing the changes to the boolean as below:
Task.Run(() =>
{
LoadProcessList();
});
thanks to #lionthefox for pointing me in the right direction!
It seems like there's something wrong with your binding OR the boolean to visibility converter. Here's an example that is working perfectly.
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool popupIsVisible;
public bool PopupIsVisible
{
get
{
return popupIsVisible;
}
set
{
popupIsVisible = value;
OnPropertyChanged("PopupIsVisible");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Following is the XAML code,
<Window.Resources>
<BooleanToVisibilityConverter x:Key="ResBoolToVisibilityConverter" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
</Grid.RowDefinitions>
<Rectangle Width="400" Height="200"x:Name="popup" Fill="Red" Visibility="{BindingPopupIsVisible, Converter={StaticResource ResBoolToVisibilityConverter}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
<Button Grid.Column="1" Content="Toggle" Click="Button_Click"/>
</Grid>
It is working here.
Related
Enable button_A when button_B is enabled and image source has a specific .png icon
I have two Buttons and an Image object in a WPF application built with .NET Core and C#. What I want on the bottom line is to enable Button_A only when the Button_B is enabled and the Image has a specific .png icon of a checkmark. For those three objects an MVVM model exists. More details in the code below,
XAML file
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:local="clr-namespace:MyApp"
mc:Ignorable="d"
Height="1080"
Width="1920"
ResizeMode="NoResize">
<Grid x:Name="MyGrid"
Background="White"
HorizontalAlignment="Center"
ShowGridLines="False">
<!--Grid Columns-->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<!--Grid Rows-->
<Grid.RowDefinitions>
<RowDefinition Height="45"/>
<RowDefinition Height="45"/>
</Grid.RowDefinitions>
<Button
x:Name="Button_A"
Click="Button_A_Click"
Content="Execute"
IsEnabled="{Binding EnableButtonA}"
HorizontalAlignment="Left"
Width="80"
Height="25"
Margin="135,0,0,0"
Grid.Column="0"
Grid.Row="0"/>
<Button
x:Name="Button_B"
Click="Button_B_Click"
Content="Execute"
IsEnabled="{Binding EnableButtonB}"
HorizontalAlignment="Left"
Width="80"
Height="25"
Margin="135,0,0,0"
Grid.Column="1"
Grid.Row="1">
</Button>
<Image
x:Name="IconSymbol"
Source="{Binding Path=ImageChangeSource,UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1"
Grid.Column="1"
Width="{Binding Path=CalculationsImageWidth}"
Height="Auto"
HorizontalAlignment="Left"
Margin="190,0,0,0"
Visibility="Visible"
IsEnabled="True"/>
</Grid>
</Window>
.CS file - MVVM model
namespace MyApp
{
public class CustomViewModel : INotifyPropertyChanged
{
//Button B
private bool _enableButtonB;
public bool EnableButtonB
{
get
{
return _enableButtonB;
}
set
{
_enableButtonB = value;
OnPropertyChanged("EnableButtonB");
}
}
//Image
private ImageSource _imageChangeSource;
public ImageSource ImageChangeSource
{
get
{
return _imageChangeSource;
}
set
{
_imageChangeSource = value;
OnPropertyChanged("ImageChangeSource");
}
}
//Image width
private int _changeImageWidth;
public int ImageWidth
{
get
{
return _changeImageWidth;
}
set
{
_changeImageWidth= value;
OnPropertyChanged("ImageWidth");
}
}
//Button A
private bool _enableButtonA;
public bool EnableButtonA
{
get
{
return _enableButtonA;
}
set
{
//What to write here?
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
}
What I tried so far is based on this similar question I asked in the past. A relevant answer posted in the attached question is the use of IMultiValueConverter. However, I am not confident to figure out how to properly use the Converter for my task.
(The code below won't work)
public class EnableReportConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return ((bool)values[0]=true, (ImageSource)values[1]=new BitmapImage(new Uri("/Assets/checkmark.png", UriKind.Relative)));
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
<Button
x:Name="Button_A"
Click="Button_A_Click"
Content="Execute"
HorizontalAlignment="Left"
Width="80"
Height="25"
Margin="135,0,0,0"
Grid.Column="0"
Grid.Row="0">
<Button.IsEnabled>
<MultiBinding>
<MultiBinding.Converter>
<local:EnableReportConverter/>
</MultiBinding.Converter>
<Binding Path="EnableButtonB"/>
<Binding Path="ImageChangeSource"/>
</MultiBinding>
</Button.IsEnabled>
</Button>
[EDIT]--example of an ICommand
public ICommand ButtonACommand
{
get { return new DelegateCommand<object>(FuncBrowseFileCommand); }
}
public void FuncBrowseFileCommand(object parameters)
{
var final_result = BrowseFile(FilesFilePath);
Nullable<bool> browse_result = final_result.browse_result;
FilesFilePath = final_result.filename;
//below are some MVVM object-- dont pay them attention
if (browse_result == true)
{
EnableFilesLoadButton = true;
EnableFilesBrowseButton = true;
EnableFilesViewButton = false;
FilesPanelVisibility = false;
}
else
{
FilesImageVisibility = true;
return;
}
}
Better approach:
I understand "WHAT" you need but , I am not sure "WHY" you need this. There are better ways to enable a button. I also notice that you are using button click which is obviously code behind. When you have MVVM model, try to use ICommand and attach to the "command" property of the button. If you do that , then you can easily assign a delegate to "CanExecute" to make the enabling of the button.
Solution to current problem: Regardless of the above suggestion, solution to your current problem is as below.
The below line in your converter is wrong. This returns object[] again. Basically, you receive an array from the XAML and return the same again. You need to receive the array, process it and return a result (which is "bool" in your case : to enable a button).
return ((bool)values[0]=true, (ImageSource)values[1]=new BitmapImage(new Uri("/Assets/checkmark.png", UriKind.Relative)));
So, do the validation like below..
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
//INCOMING DATA
bool is_buttonB_enabled = (bool)values[0]; //This is the first value in the object array.
ImageSource _image = (ImageSource) values[1]; //This is the second value in the object array.
//YOUR EXPECTED IMAGE.
ImageSource _expected_image = new BitmapImage(new Uri("/Assets/checkmark.png", UriKind.Relative));
//VALIDATE
return (is_buttonB_enabled == true && _image == _expected_image );
}
UPDATE:
In case, you use an ICommand (and return a delegatecommand), then you can follow below approach.
public ICommand ButtonACommand
{
get { return new DelegateCommand<object>(FuncBrowseFileCommand,_canEnableButton); }
}
private bool _canEnableButton(object obj)
{
//YOUR EXPECTED IMAGE.
ImageSource _expected_image = new BitmapImage(new Uri("/Assets/checkmark.png", UriKind.Relative));
return (EnableButtonB == true && ImageChangeSource == _expected_image );
}
then, you don't need converter..
I have a WPF window that uses multiple viewmodel objects as its DataContext. The window has a control that binds to a property that exists only in some of the viewmodel objects. How can I bind to the property if it exists (and only if it exists).
I am aware of the following question/answer: MVVM - hiding a control when bound property is not present. This works, but gives me a warning. Can it be done without the warning?
Thanks!
Some example code:
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="1" Name="ListView" Margin="25,0,25,0" ItemsSource="{Binding Path=Lst}"
HorizontalContentAlignment="Center" SelectionChanged="Lst_SelectionChanged">
</ListBox>
<local:SubControl Grid.Row="3" x:Name="subControl" DataContext="{Binding Path=SelectedVM}"/>
</Grid>
SubControl Xaml:
<UserControl x:Class="WpfApplication1.SubControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="200" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffAlways}"/>
<CheckBox IsChecked="{Binding Path=Always}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
<StackPanel Grid.Row="3" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffSometimes}"/>
<CheckBox IsChecked="{Binding Path=Sometimes}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
</Grid>
MainWindow Code Behind:
public partial class MainWindow : Window
{
ViewModel1 vm1;
ViewModel2 vm2;
MainViewModel mvm;
public MainWindow()
{
InitializeComponent();
vm1 = new ViewModel1();
vm2 = new ViewModel2();
mvm = new MainViewModel();
mvm.SelectedVM = vm1;
DataContext = mvm;
}
private void Lst_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox lstBx = sender as ListBox;
if (lstBx != null)
{
if (lstBx.SelectedItem.Equals("VM 1"))
mvm.SelectedVM = vm1;
else if (lstBx.SelectedItem.Equals("VM 2"))
mvm.SelectedVM = vm2;
}
}
}
MainViewModel (DataContext of MainWindow):
public class MainViewModel : INotifyPropertyChanged
{
ObservableCollection<string> lst;
ViewModelBase selectedVM;
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel()
{
Lst = new ObservableCollection<string>();
Lst.Add("VM 1");
Lst.Add("VM 2");
}
public ObservableCollection<string> Lst
{
get { return lst; }
set
{
lst = value;
OnPropertyChanged("Lst");
}
}
public ViewModelBase SelectedVM
{
get { return selectedVM; }
set
{
if (selectedVM != value)
{
selectedVM = value;
OnPropertyChanged("SelectedVM");
}
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
ViewModel1 (with sometimes property):
public class ViewModel1 : ViewModelBase, INotifyPropertyChanged
{
private bool _always;
private string _onOffAlways;
private bool _sometimes;
private string _onOffSometimes;
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel1()
{
_always = false;
_onOffAlways = "Always Off";
_sometimes = false;
_onOffSometimes = "Sometimes Off";
}
public bool Always
{
get { return _always; }
set
{
_always = value;
if (_always)
OnOffAlways = "Always On";
else
OnOffAlways = "Always Off";
OnPropertyChanged("Always");
}
}
public string OnOffAlways
{
get { return _onOffAlways; }
set
{
_onOffAlways = value;
OnPropertyChanged("OnOffAlways");
}
}
public bool Sometimes
{
get { return _sometimes; }
set
{
_sometimes = value;
if (_sometimes)
OnOffSometimes = "Sometimes On";
else
OnOffSometimes = "Sometimes Off";
OnPropertyChanged("Sometimes");
}
}
public string OnOffSometimes
{
get { return _onOffSometimes; }
set
{
_onOffSometimes = value;
OnPropertyChanged("OnOffSometimes");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
ViewModel2 (without Sometimes property):
public class ViewModel2 : ViewModelBase, INotifyPropertyChanged
{
private bool _always;
private string _onOffAlways;
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel2()
{
_always = false;
_onOffAlways = "Always Off";
}
public bool Always
{
get { return _always; }
set
{
_always = value;
if (_always)
OnOffAlways = "Always On";
else
OnOffAlways = "Always Off";
OnPropertyChanged("Always");
}
}
public string OnOffAlways
{
get { return _onOffAlways; }
set
{
_onOffAlways = value;
OnPropertyChanged("OnOffAlways");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public class AlwaysVisibleConverter : IValueConverter
{
#region Implementation of IValueConverter
public object Convert(object value,
Type targetType, object parameter, CultureInfo culture)
{
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
There are many different ways one could approach your scenario. For what it's worth, the solution you already have seems reasonable to me. The warning you get (I presume you are talking about the error message output to the debug console) is reasonably harmless. It does imply a potential performance issue, as it indicates WPF is recovering from an unexpected condition. But I would expect the cost to be incurred only when the view model changes, which should not be frequent enough to matter.
Another option, which is IMHO the preferred one, is to just use the usual WPF data templating features. That is, define a different template for each view model you expect, and then let WPF pick the right one according to the current view model. That would look something like this:
<UserControl x:Class="TestSO46736914MissingProperty.UserControl1"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:l="clr-namespace:TestSO46736914MissingProperty"
mc:Ignorable="d"
Content="{Binding}"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<DataTemplate DataType="{x:Type l:ViewModel1}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffAlways}"/>
<CheckBox IsChecked="{Binding Path=Always}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
<StackPanel Grid.Row="3" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffSometimes}"/>
<CheckBox IsChecked="{Binding Path=Sometimes}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type l:ViewModel2}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffAlways}"/>
<CheckBox IsChecked="{Binding Path=Always}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
</Grid>
</DataTemplate>
</UserControl.Resources>
</UserControl>
I.e. just set the Content of your UserControl object to the view model object itself, so that the appropriate template is used to display the data in the control. The template for the view model object that doesn't have the property, doesn't reference that property and so no warning is generated.
Yet another option, which like the above also addresses your concern about the displayed warning, is to create a "shim" (a.k.a. "adapter") object that mediates between the unknown view model type and a consistent one the UserControl can use. For example:
class ViewModelWrapper : NotifyPropertyChangedBase
{
private readonly dynamic _viewModel;
public ViewModelWrapper(object viewModel)
{
_viewModel = viewModel;
HasSometimes = viewModel.GetType().GetProperty("Sometimes") != null;
_viewModel.PropertyChanged += (PropertyChangedEventHandler)_OnPropertyChanged;
}
private void _OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
_RaisePropertyChanged(e.PropertyName);
}
public bool Always
{
get { return _viewModel.Always; }
set { _viewModel.Always = value; }
}
public string OnOffAlways
{
get { return _viewModel.OnOffAlways; }
set { _viewModel.OnOffAlways = value; }
}
public bool Sometimes
{
get { return HasSometimes ? _viewModel.Sometimes : false; }
set { if (HasSometimes) _viewModel.Sometimes = value; }
}
public string OnOffSometimes
{
get { return HasSometimes ? _viewModel.OnOffSometimes : null; }
set { if (HasSometimes) _viewModel.OnOffSometimes = value; }
}
private bool _hasSometimes;
public bool HasSometimes
{
get { return _hasSometimes; }
private set { _UpdateField(ref _hasSometimes, value); }
}
}
This object uses the dynamic feature in C# to access the known property values, and uses reflection on construction to determine whether or not it should try to access the Sometimes (and related OnOffSometimes) property (accessing the property via the dynamic-typed variable when it doesn't exist would throw an exception).
It also implements the HasSometimes property so that the view can dynamically adjust itself accordingly. Finally, it also proxies the underlying PropertyChanged event, to go along with the delegated properties themselves.
To use this, a little bit of code-behind for the UserControl is needed:
partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public ViewModelWrapper ViewModelWrapper { get; private set; }
public UserControl1()
{
DataContextChanged += _OnDataContextChanged;
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
private void _OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ViewModelWrapper = new ViewModelWrapper(DataContext);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ViewModelWrapper)));
}
}
With this, the XAML is mostly like what you originally had, but with a style applied to the optional StackPanel element that has a trigger to show or hide the element according to whether the property is present or not:
<UserControl x:Class="TestSO46736914MissingProperty.UserControl1"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:l="clr-namespace:TestSO46736914MissingProperty"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid DataContext="{Binding ViewModelWrapper, RelativeSource={RelativeSource AncestorType=UserControl}}">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="40"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffAlways}"/>
<CheckBox IsChecked="{Binding Path=Always}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
<StackPanel Grid.Row="3" Orientation ="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,5,0,5">
<StackPanel.Style>
<p:Style TargetType="StackPanel">
<p:Style.Triggers>
<DataTrigger Binding="{Binding HasSometimes}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</p:Style.Triggers>
</p:Style>
</StackPanel.Style>
<TextBlock Margin="5,0,5,0" Text="{Binding Path=OnOffSometimes}"/>
<CheckBox IsChecked="{Binding Path=Sometimes}">
<TextBlock Text="On/Off"/>
</CheckBox>
</StackPanel>
</Grid>
</UserControl>
Note that the top-level Grid element's DataContext is set to the UserControl's ViewModelWrapper property, so that the contained elements use that object instead of the view model assigned by the parent code.
(You can ignore the p: XML namespace…that's there only because Stack Overflow's XAML formatting gets confused by <Style/> elements that use the default XML namespace.)
While I in general would prefer the template-based approach, as the idiomatic and inherently simpler one, this wrapper-based approach does have some advantages:
It can be used in situations where the UserControl object is declared in an assembly different from the one where the view model types are declared, and where the latter assembly cannot be referenced by the former.
It removes the redundancy that is required by the template-based approach. I.e. rather than having to copy/paste the shared elements of the templates, this approach uses a single XAML structure for the entire view, and shows or hides elements of that view as appropriate.
For completeness, here is the NotifyPropertyChangedBase class used by the ViewModelWrapper class above:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
_RaisePropertyChanged(propertyName);
}
protected void _RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
For what it's worth, I prefer this approach to re-implementing the INotifyPropertyChanged interface in each model object. The code is a lot simpler and easier to write, simpler to read, and less prone to errors.
Here's a fairly simple solution using DataTriggers and a custom converter:
<Style TargetType="CheckBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={HasPropertyConverter PropertyName=Sometimes}}" Value="True">
<Setter Property="IsChecked" Value="{Binding Sometimes}" />
</DataTrigger>
</Style.Triggers>
</Style>
The converter:
public class HasPropertyConverter : IValueConverter
{
public string PropertyName { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (string.IsNullOrWhiteSpace(PropertyName))
return DependencyProperty.UnsetValue;
return value?.GetType().GetProperty(PropertyName) != null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
public class HasPropertyConverterExtension : MarkupExtension
{
public string PropertyName { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
=> new HasPropertyConverter { PropertyName = PropertyName };
}
I would like to get content from my combobox. I already tried some ways to do that, but It doesn't work correctly.
This is example of my combobox:
<ComboBox x:Name="cmbSomething" Grid.Column="1" Grid.Row="5" HorizontalAlignment="Center" Margin="0 100 0 0" PlaceholderText="NothingToShow">
<ComboBoxItem>First item</ComboBoxItem>
<ComboBoxItem>Second item</ComboBoxItem>
</ComboBox>
After I click the button, I want to display combobox selected item value.
string selectedcmb= cmbSomething.Items[cmbSomething.SelectedIndex].ToString();
await new Windows.UI.Popups.MessageDialog(selectedcmb, "Result").ShowAsync();
Why this code does not work?
My result instead of showing combobox content, it shows this text:
Windows.UI.Xaml.Controls.ComboBoxItem
You need the Content property of ComboBoxItem. So this should be what you want:
var comboBoxItem = cmbSomething.Items[cmbSomething.SelectedIndex] as ComboBoxItem;
if (comboBoxItem != null)
{
string selectedcmb = comboBoxItem.Content.ToString();
}
I have expanded on my suggestion regarding using models instead of direct UI code-behind access. These are the required parts:
BaseViewModel.cs
I use this in a lot of the view models in my work project. You could technically implement it directly in a view model, but I like it being centralized for re-use.
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Hashtable values = new Hashtable();
protected void SetValue(string name, object value)
{
this.values[name] = value;
OnPropertyChanged(name);
}
protected object GetValue(string name)
{
return this.values[name];
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
ComboViewModel.cs
This what you'll bind to make it easy to get values. I called it ComboViewModel because I'm only dealing with your ComboBox. You'll want a much bigger view model with a better name to handle all of your data binding.
public class ComboViewModel : BaseViewModel
{
public ComboViewModel()
{
Index = -1;
Value = string.Empty;
Items = null;
}
public int Index
{
get { return (int)GetValue("Index"); }
set { SetValue("Index", value); }
}
public string Value
{
get { return (string)GetValue("Value"); }
set { SetValue("Value", value); }
}
public List<string> Items
{
get { return (List<string>)GetValue("Items"); }
set { SetValue("Items",value); }
}
}
Window1.xaml
This is just something I made up to demonstrate/test it. Notice the various bindings.
<Window x:Class="SO37147147.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ComboBox x:Name="cmbSomething" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" HorizontalAlignment="Center" MinWidth="80"
ItemsSource="{Binding Path=Items}" SelectedIndex="{Binding Path=Index}" SelectedValue="{Binding Path=Value}"></ComboBox>
<TextBox x:Name="selectedItem" MinWidth="80" Grid.Row="2" Grid.Column="0" Text="{Binding Path=Value}" />
<Button x:Name="displaySelected" MinWidth="40" Grid.Row="2" Grid.Column="1" Content="Display" Click="displaySelected_Click" />
</Grid>
</Window>
Window1.xaml.cs
Here's the code-behind. Not much to it! Everything is accessed through the dataContext instance. There's no need to know control names, etc.
public partial class Window1 : Window
{
ComboViewModel dataContext = new ComboViewModel();
public Window1()
{
InitializeComponent();
dataContext.Items=new List<string>(new string[]{"First Item","Second Item"});
this.DataContext = dataContext;
}
private void displaySelected_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(String.Format("Selected item:\n\nIndex: {0}\nValue: {1}", dataContext.Index, dataContext.Value));
}
}
You can add business logic for populating models from a database, saving changes to a database, etc. When you alter the properties of the view model, the UI will automatically be updated.
I have below code to bind ListBox data using MVVM. I would like to implment the Command from MVVM, data is binded completely and I don't know why it doesn't work with the Command. I don't receive the message when clicking on the button.
ViewModel
public class BookmarkViewModel : INotifyPropertyChanged
{
public BookmarkViewModel()
{
DataSource ds = new DataSource();
deleteBookmark = new Command(executeCommand) { Enabled = true };
_bk = ds.getBookmarkDetail();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
List<BookmarkDetail> _bk;
public List<BookmarkDetail> Bookmarks
{
get { return _bk; }
set
{
if (_bk != value)
{
_bk = value;
OnPropertyChanged("Bookmarks");
}
}
}
private Command deleteBookmark;
public Command DeleteBookmark
{
get
{
return deleteBookmark;
}
set
{
deleteBookmark = value;
}
}
void executeCommand()
{
System.Windows.MessageBox.Show(_bk[0].SuraName);
}
public class Command : ICommand
{
private readonly Action executeAction;
private bool enabled;
public bool Enabled
{
get
{
return enabled;
}
set
{
if (enabled != value)
{
enabled = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, new EventArgs());
}
}
}
public Command(Action executeAction)
{
this.executeAction = executeAction;
}
public bool CanExecute(object parameter)
{
return enabled;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
executeAction();
}
}
}
and XAML binding
<ListBox x:Name="lsbBookmarks" FontFamily="./Fonts/ScheherazadeRegOT.ttf#Scheherazade"
FlowDirection="RightToLeft"
Style="{StaticResource ListBoxStyle1}"
ItemsSource="{Binding Bookmarks}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="60"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0"
HorizontalAlignment="Stretch">
<TextBlock Padding="20,0,10,0" HorizontalAlignment="Stretch">
<Run FontSize="50" Text="{Binding ArabicText.ArabicAyaNumber}"
FontFamily="./Fonts/KPGQPC.otf#KFGQPC Uthmanic Script HAFS"
Foreground="Blue"/> <Run FontSize="30" Text="{Binding ArabicText.Aya}"/>
</TextBlock>
</StackPanel>
<Button Grid.Column="1" Tag="{Binding ArabicText.ArabicTextID}"
VerticalAlignment="Center"
Height="60" Width="50" HorizontalAlignment="Right"
Content="X" BorderBrush="Red"
Background="Red" BorderThickness="0"
Padding="0" Command="{Binding DeleteBookmark}"></Button>
</Grid>
<Line X1="0" X2="1" Y1="0" Y2="0" Stretch="Fill" VerticalAlignment="Bottom"
StrokeThickness="1" Stroke="LightGray" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Any Ideas, How to implement the Command using MVVM?
Thanks!
If I were you I would:
Move the Command implementation to a separate file or declare it outside the scope of the BookmarkViewModel class.
Use the second option as ig2r suggested. Your binding would look like this:Command="{Binding DataContext.DeleteBookmark, ElementName=lsbBookmarks}" You can also use any other ElementName other than lsbBookmarks that's defined as a parent of the ListBox.
It appears that your DeleteBookmark command is exposed as a property on the BookmarkViewModel class, whereas the actual data context within the DataTemplate used to render individual ListBox items will be an instance of BookmarkDetail. Since BookmarkDetail does not declare a DeleteBookmark command, the binding fails.
To correct this, either:
Define and expose the DeleteBookmark command on the BookmarkDetail class, or
Extend your command binding to tell the binding system where to look for the delete command, e.g., Command="{Binding DataContext.DeleteBookmark, ElementName=lsbBookmarks}" (untested).
Im having text box and list view and when you are pressing on the button
you are the list view is filled with data ,currently the list view is under the button and the text box and
always is there and filled after you press on the button.
There is a way to hide the list view from the page until you press on the button and requesting the data?
public class ModelView
{
public ModelView()
{
GetServiceCollection = new ObservableCollection<string>();
}
bool isDataLoaded = false;
MyCommand goCommand;
public ICommand GoCommand
{
get { return goCommand ?? (goCommand = new MyCommand(() => OnGoCommand(), () => !isDataLoaded)); }
}
public ObservableCollection<string> GetServiceCollection { get; set; }
void OnGoCommand()
{
GetServiceCollection.Clear();
foreach (var item in _configServiceModel.CollectList)
{
GetServiceCollection.Add(item);
}
isDataLoaded = true;
goCommand.RaiseCanExecuteChanged();
}
......
The xaml
<Button Content="Go" Grid.Column="3" Grid.Row="1" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="75" Height="21.96" Command="{Binding GoCommand}"/>
<ListView Grid.Column="2" HorizontalAlignment="Center" Height="230"
Margin="5,20,0,0" Grid.Row="2" VerticalAlignment="Top" Width="330"
ItemsSource="{Binding GetCollection}" }" >
}
ViewModel
public class ConfigModelView:INotifyPropertyChanged
{
public ConfigModelView()
{
GetServiceCollection=new ObservableCollection<string>();
}
bool isDataLoaded;
public bool IsDataLoaded
{
get { return isDataLoaded; }
set { isDataLoaded = value; OnPropertyChanged("IsDataLoaded"); }
}
MyCommand goCommand;
public ICommand GoCommand
{
get{return goCommand ?? (goCommand=new MyCommand(()=>Command(),()=>!isDataLoaded));}
}
public ObservableCollection<string> GetServiceCollection{get;set;}
void Command()
{
foreach (var item in _configServiceModel.CollectList)
{
GetServiceCollection.Add(item);
}
isDataLoaded = true;
OnPropertyChanged("IsDataLoaded");
goCommand.RaiseCanExecuteChanged();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
BooleanToVisibilityConverter
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool)
{
if ((bool)value)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
xaml
<Window x:Class="WpfApplication3.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication3"
Title="Window1" Height="300" Width="800">
<Window.Resources>
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
</Window.Resources>
<StackPanel>
<Button Content="Go" Grid.Column="3" Grid.Row="1" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="75" Height="21.96" Command="{Binding GoCommand}"/>
<ListView Grid.Column="2" HorizontalAlignment="Center" Height="230"
Margin="5,20,0,0" Grid.Row="2" VerticalAlignment="Top" Width="330"
Visibility="{Binding IsDataLoaded,
Converter= {StaticResource BoolToVisibilityConverter}}"
ItemsSource="{Binding GetCollection}" />
</StackPanel>
The best bet here would be to create another property on the ViewModel that you bind the Visibility of the ListView to. In the GoCommand implementation, set this property to visible.
As a side note, your ViewModel doesn't implement INotifyPropertyChanged, so you'll need to do that as well to have the visiblity update when the property is changed:
private Visibility listViewVisibility;
public Visibility ListViewVisibility
{
get { return listViewVisibility; }
set
{
if (this.listViewVisibility == value)
return;
this.listViewVisibility = value;
this.OnPropertyChanged("ListViewVisibility");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
The xaml:
<ListView Grid.Column="2" HorizontalAlignment="Center" Height="230"
Margin="5,20,0,0" Grid.Row="2" VerticalAlignment="Top" Width="330"
Visibility="{Binding ListViewVisibility}"
ItemsSource="{Binding GetCollection}" />
Well load your form then in the put "listView1.hide()".
Then create your button event.
Type "listView1.show()".
P.S. you can also set all of those values in your c# code.