XAML
<Popup Name="popUpProgress" Width="225" Height="85"
IsOpen="{Binding PopUpIsOpen,Mode=OneWay}"
Placement="Center" PlacementTarget="{Binding ElementName=stkListview}"
VerticalAlignment="Top">
<Border BorderThickness="1" Background="Blue" >
<Grid Width="225" Height="85">
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Label x:Name="lblProgress" Content="Please Wait ...." Margin="10,5,0,0" HorizontalAlignment="Left" Grid.Row="1" />
</Grid>
</Border>
</Popup>
In view Model:
private bool _PopUpIsOpen;
public bool PopUpIsOpen
{
get { return _PopUpIsOpen; }
set
{
_PopUpIsOpen = value;
RaisePropertyChanged(() => this.PopUpIsOpen);
}
}
public RelayCommand SubmitCommand { get; private set; }
private bool SubmitCommandCanExecute()
{
return true;
}
private void SubmitCommandExecute()
{
PopUpIsOpen = true;
dsStandardListbyMarket = marketBL.StandardListbyMarketBL(Convert.ToInt32(SelectdMarketId), Convert.ToInt32(Users.UserId));
GetComboboxMappingCollections(Convert.ToInt32(this.SelectdMarketId), Users.UserId);
FItems = new ObservableCollection<MarketRecord.FItem>();
FItems.CollectionChanged += OnUICollectionChanged;
marketBL.FetchMarketRecords(Convert.ToInt32(this.SelectdMarketId));
IsSubmitButtonVisible = true;
PopUpIsOpen = false;
}
When I click on submit button control comes to SubmitCommandExecute but Popup window is not showing. I am bit new to WPF, scratching my head over it. Finally raising this question here. What might be wrong.
I think the problem is in the way you are testing the code. SInce you are sleeping in the UI thread, the UI does not feel the change from true to false on the bound property.
Try to use a timer instead of a Sleep in the thread.
Given the RaisePropertyChanged syntaxe on msdn :
protected internal void RaisePropertyChanged (
string propertyName
)
You should try RaisePropertyChanged("PopUpIsOpen"); instead of RaisePropertyChanged(() => this.PopUpIsOpen);
Related
I am working on re-writing one of my previous toy project as MVVM structure using WPF. But binding does not work.
My project converts a .xef file to a .mat file. To follow MVVM structure, first I created a xef2matcore class to do the business logic, and provided several events (e.g. FileLoaded, ConversionProgressUpdated). Then I created a ViewModel class where the properties that will bind to the UI are stored (e.g. Progress, IsButtionEnabled). The ViewModel implemented the INotifyPropertyChanged interface. And finally the properties are bound to the UI elements (e.g. progressbar.Value, buttion.IsEnabled).
Here comes the problem:
If the binding Property is changed directly by a call to the method defined in ViewModel, then the binding works fine. But if the binding Property is changed through a event handler method (and finally calls the same method defined in ViewModel), then the binding will not work (UI not updated even the binding Property value has been changed).
An explanation with the code is as below:
public class ViewModel: ObservableObject
{
private double _progress;
public double Progress
{
get => _progress;
set => Set(ref _progress, value);
}
private string _fileName;
public string FileName
{
get => _fileName;
set => Set(ref _fileName, value);
}
private bool _isButtonEnabled;
public bool IsButtonEnabled
{
get => _isButtonEnabled;
set => Set(ref _isButtonEnabled, value);
}
private bool _isBusy;
public bool IsBusy
{
get => _isBusy;
set => Set(ref _isBusy, value);
}
Xef2MatCore XCore;
public ViewModel()
{
Progress = 0;
FileName = "";
IsButtonEnabled = true;
}
private void XCore_FileLoaded()
{
IsButtonEnabled = false;
}
private void XCore_ProgressUpdated(double progress)
{
Progress = progress;
}
private void XCore_ExportFinished()
{
IsButtonEnabled = true;
Progress = 50;
}
public IAsyncCommand SelectFileCommandAsync { get => new AsyncCommand(UpdateFileSelectionExecuteAsync, CanFileSelectionExecute); }
private bool CanFileSelectionExecute() => !IsBusy;
private async Task UpdateFileSelectionExecuteAsync()
{
var folder_path = Environment.CurrentDirectory;
if (!Directory.Exists(folder_path)) Directory.CreateDirectory(folder_path);
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.InitialDirectory = Environment.CurrentDirectory;
openFileDialog.Filter = "Kinect Studio Data File|*.xef";
openFileDialog.RestoreDirectory = true;
openFileDialog.FilterIndex = 1;
var result = openFileDialog.ShowDialog();
if (result.HasValue && result.Value)
{
FileName = openFileDialog.FileName;
IsBusy = true;
XCore = new Xef2MatCore();
XCore.FileLoaded += XCore_FileLoaded;
XCore.ProgressUpdated += XCore_ProgressUpdated;
XCore.ExportFinished += XCore_ExportFinished;
//await Do_work();
await XCore.LoadAsync(FileName);
IsBusy = false;
}
else
{
return;
}
}
private async Task Do_work()
{
XCore_FileLoaded();
await Task.Run(() =>
{
int i = 0;
while (i < 100)
{
XCore_ProgressUpdated(i++);
Thread.Sleep(100);
}
});
XCore_ExportFinished();
}
}
The binding works if I uncomment await Do_work(); but does not work if I call await XCore.LoadAsync(FileName); (which fires XCore_FileLoaded, XCore_ProgressUpdated and XCore_ExportFinished through a event handler method).
The XAML file:
<Window x:Class="Xef2MatUI.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:local="clr-namespace:Xef2MatUI"
mc:Ignorable="d"
Title="Xef2Mat Converter" >
<Window.DataContext>
<local:ViewModel x:Name="_viewmodel"/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Label Grid.Column="0" Grid.Row="0"
x:Name="label1"
Content="Select Kinect Studio (.xef) file:"
HorizontalAlignment="Left" VerticalAlignment="Center"/>
<Button Grid.Column="0" Grid.Row="1"
x:Name="button"
Content="Select"
IsEnabled="{Binding IsButtonEnabled, Mode=OneWay}"
HorizontalAlignment="Left" VerticalAlignment="Center"
Command="{Binding SelectFileCommand}"
/>
<Button Grid.Column="1" Grid.Row="1"
Content="Select"
/>
<Label Grid.Column="0" Grid.Row="2"
x:Name="label2" Content="Progress:"
HorizontalAlignment="Left" VerticalAlignment="Center"/>
<ProgressBar Grid.Column="0" Grid.Row="3"
x:Name="progressBar"
Value="{Binding Progress, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<Label Grid.Column="1" Grid.Row="3"
x:Name="label3" Content="{Binding Progress, Mode=OneWay, StringFormat={}{0}%}"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Window>
Any help is appreciated.
As Selvin commented, when I called await XCore.LoadAsync(FileName);, the time-consuming task blocked OnEventHappened functions. Then separating them and putting the time-consuming task to a new thread solved the problem.
I want to disable the send button if any of the three entries is empty, but how that can be achieved in an MVVM fashion?
I thought of the CanExecute delegate, but how can I fire it whenever the TextChanged fired?
Also if I opt in the behaviors, how can I communicate with other controls like button if I'm using Behavior<Entry>
This is the view:
<ContentPage.Content>
<AbsoluteLayout>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="56"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="10"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Label Text="Contact Us" FontSize="Medium" Grid.ColumnSpan="3"/>
<Entry Text="{Binding ContactData.message_name}" x:Name="subject" Grid.Row="2" Grid.Column="1" Placeholder="Subject"/>
<Entry Keyboard="Email" Text="{Binding ContactData.receiver_email}" x:Name="email" Grid.Row="3" Grid.Column="1" Placeholder="Email"/>
<Editor Text="{Binding ContactData.message_subject}" x:Name="body" Grid.Row="4" Grid.Column="1" />
<Button Grid.Row="5" Grid.Column="1" Command="{Binding ContactFormSent}" Text="Send"/>
</Grid>
</AbsoluteLayout>
</ContentPage.Content>
in the ViewModel:
public ContactViewModel()
{
ContactFormSent = new RelayCommand(SendContactInfo);
ContactData = new ContactModel();
}
private bool CanSend() //this only get called when the view model is constructed
{
return !(string.IsNullOrWhiteSpace(ContactData.receiver_email) && string.IsNullOrWhiteSpace(ContactData.message_subject) &&
string.IsNullOrWhiteSpace(ContactData.message_name));
}
In the Behavior option, I wan it to be used with both Entry and Editor, so is my way to go is the Behavior class, not the generic version? if so, then how can I implement it?
Usually, in order to react to a view event using MVVM pattern, it is recommended that you use EventToCommandBehaviour.
But in this case it is not needed, as the setters on properties bound to Entry should trigger every time text changes. You can use that to trigger ChangeCanExecute() on command to notify view that the button can now be enabled/disabled.
For e.g.:
string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
if (_firstName != value)
SendCommand.ChangeCanExecute();
SetProperty(ref _firstName, value, nameof(FirstName));
}
}
string _lastName;
public string LastName
{
get { return _lastName; }
set
{
if (_lastName != value)
SendCommand.ChangeCanExecute();
SetProperty(ref _lastName, value, nameof(LastName));
}
}
string _email;
public string Email
{
get { return _email; }
set
{
if (_email != value)
SendCommand.ChangeCanExecute();
SetProperty(ref _email, value, nameof(Email));
}
}
Command _sendCommand;
public Command SendCommand
{
get
{
return _sendCommand ?? (_sendCommand = new Command(OnSend, CanSend));
}
}
bool CanSend(object obj)
{
return Validate();
}
void OnSend(object obj)
{
if(Validate())
{
//actual button click execution
}
}
bool Validate()
{
// disable button if any entry empty
return !string.IsNullOrEmpty(_firstName)
&& !string.IsNullOrEmpty(_lastName)
&& !string.IsNullOrEmpty(_email);
}
Command has a public method ChangeCanExecute().
When you new a Command, you could specify the canExecute condition. Once your TextChanged event is triggered, call the ChangeCanExecute() method.
Example:
Command someCommand = new Command(
() => SomeMethod(),
()=> { return CanSend(); }
);
Once a Button changed a text:
private void TextChanged(object sender, TextChangedEventArgs e)
{
ViewModel.someCommand.ChangeCanExecute();
}
This question already has answers here:
ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)
(21 answers)
Closed 6 years ago.
I would greatly appreciate some help with this binding issue I'm having. Basically I have a list view showing some information about Files. In the list view item itself, there's some text and also a button.
When this button is clicked I want to disable that button.
Currently I've set up an ObservableCollection - however even though the button click is being registered, the UI doesn't update. If I go to a different screen and return, then the UI updates. So it's not instantaneous.
I think there is some problem with the way RaisePropertyChanged() is working. I know from reading other SO articles that property changes in the object are harder to pick up than say, removing an item or adding an item to the ListView.
I'm completely stuck, any help would be most appreciated. Thanks.
Xaml:
<ListView RelativePanel.Below="heading" ItemsSource="{Binding Pages}" ReorderMode="Enabled" CanReorderItems="True" AllowDrop="True" Margin="0,10" SelectedItem="{Binding Path=SelectedFile,Mode=TwoWay}" >
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:File">
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Path= Name, Mode=TwoWay}" FontWeight="Bold" Padding="0,5" />
<TextBlock Text ="{x:Bind Path}" Grid.Row="1" TextWrapping="Wrap" Padding="10,0,0,0" Foreground="DarkGray" Opacity="0.8" />
<Button Content="X" Grid.Column="1" Grid.RowSpan="2" Command="{x:Bind EnableCommand}" IsEnabled="{x:Bind Path=IsEnabled, Mode=OneWay}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
File.cs:
public class File : ViewModelBase
{
public string Name { get; set; }
public string FileName { get; set; }
public string Path { get; set; }
public string Contents { get; set; }
private Boolean isEnabled = true;
public Boolean IsEnabled {
get { return isEnabled; }
private set {
isEnabled = value;
RaisePropertyChanged("IsChecked");
}
}
private ICommand enableCommand;
public ICommand EnableCommand
{
get
{
if(enableCommand == null)
{
enableCommand = new RelayCommand(() => {
isEnabled = false;
Name += "Disabled";
RaisePropertyChanged();
});
}
return enableCommand;
}
}
}
Viewmodel:
public class MyPageViewModel : BaseViewModel
{
private ObservableCollection<File> pages;
public ObservableCollection<File> Pages
{
get { return pages; }
set
{
pages = value;
RaisePropertyChanged();
}
}
private File selectedFile = new File();
public File SelectedFile
{
get { return selectedFile; }
set
{
Set(ref selectedFile, value);
}
}
public MyPageViewModel()
{
if (ApplicationData.FileList != null)
{
Pages = new ObservableCollection<File>(ApplicationData.FileList);
}
else
{
Pages = new ObservableCollection<File>();
}
}
You notify IsChecked when you should be notifying IsEnabled.
(ObsevarvableCollection only notifies when something is added or removed from it. Changes in the objects it holds are not notified by it.)
I have my custom Calendar control - Event Calendar. I use it in a View of some case.
<Controls:EventCalendar Grid.Row="0"
Grid.RowSpan="8"
Grid.Column="2"
Margin="20,50,0,0"
CalendarEvents="{Binding DataContext.CalendarEvents, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged}"
Header="{Binding DataContext.DataSpis.Header, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged}"
ViewModelBase="{Binding DataContext.ViewModel, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged}"
IsFunctionalityVisible="{Binding DataContext.IsFunctionalityVisible, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged}"
IsCaseLoaded="{Binding DataContext.IsLoaded, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
</Controls:EventCalendar>
I detect if the case is loaded (same view, different data) via IsCaseLoaded Dependency Property. When this happens, I add new DataContext to my Calendar Control. Like this:
private static void LoadPCCallback(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
if (((EventCalendar)source).IsCaseLoaded == true)
{
((EventCalendar)source).DataContext = null;
((EventCalendar)source).DataContext = new EventCalendarViewModel(((EventCalendar)source).Header, ((EventCalendar)source).ViewModelBase, ((EventCalendar)source).CalendarEvents);
}
}
In constructor of EventCalendarViewModel I set some visibility for Meetings or Tasks I want to show. By default Meetings are shown and Tasks are hidden.
When I want to show Tasks, I click on Button on this Calendar Control.
And now where the behaviour starts to be unexpected: I load the Case, click on Tasks Button, it works - Tasks are shown, Meetings are hidden.
I reload the Case, click on Tasks Button, it works - Tasks are shown, Meetings are hidden.
But third time I reload the Case (sometimes second, sometimes fourth - really random), Constructor works, sets Meetings as default, but when I click on Tasks Button, it suddenly has values from previous DataContext, so it thinks Tasks are true, Meetings are false... so nothing changes and Meetings are still shown.
public void ShowMeetingsButtonClick()
{
this.ShowTasks = false;
NotifyOfPropertyChange(() => ShowTasks);
this.ShowMeetings = true;
NotifyOfPropertyChange(() => ShowMeetings);
}
Show Tasks is also like that:
public void ShowTasksButtonClick()
{
this.ShowMeetings = false;
NotifyOfPropertyChange(() => ShowMeetings);
this.ShowTasks = true;
NotifyOfPropertyChange(() => ShowTasks);
}
So one thing that comes to my mind is, somehow this View of Calendar founds previous DataContext in Visual Tree and takes old values from there. Because after constructor of new DataContext everything seems fine, but after clicking on a button it suddenly has different values.
I also thought some of my threads are changing something, but I tried to debug it and no one them (only Main Thread) are active during this.
Ok, I try to rebuild some stuff to simulate your behavior. And came up with this and it should be fairly close to the behavior your are heading towards.
I added an InverseToBooleanConverter which show the the visibilty in the opposite way of the bool (false = Visible). This helps with the toggling stuff
I added a Converter for the GridLength (Your Height) coming from an Integer. And I took the liberty to create an Enum which represents the value of Show and Hide.
Important rule keep your ViewModels pure no Namespaces that starts with System.Windows or any view related stuff.
I somehow sorted your Properties and the PropertyNotification-Stuff. Goal is to keep it as tight and lean as possible. So for this code I only had calls to OnPropertyChanged from within the property itself.
For me the TaskListView is a Control and will have it's own ListOfTaskViewModel (with behavior) and it's collection of Tasks (depending on the complexity in it. This could also be an ObservableList<TaskItemViewModel>)
The same will apply for the MeetingListView with it's MeetingListViewModel.
Now it is important where and how to load Data. I could think about a Service which has at least 2 methods GetTasksForCaseID and GetMeetingsForCaseIDwhich could be injected in the ViewModel or the loaded data could be passed on. I prefer to keep things independent and would use some thing like an EventAggregator or a Messenger to notify the ViewModel with the matching ID as payload. And keep the responsibility to the ViewModel to fetch the data. But this depends and since I had not enough information about your context this was out of the scope for the example. But I hope you get the idea.
This right here is the MainViewModel class
The same thing would also apply for your actual Events in the calendar and the highlight stuff. It Should be separated in a own ViewModel with own view control to keep things clean.
public class MainViewModel:INotifyPropertyChanged
{
public MainViewModel()
{
Init();
}
public enum Calendar{
ShowCalendarMaxLength = 145,
HideCalenderHeight = 325,
}
private MeetingsListViewModel _listOfMeetingsViewModel;
public MeetingsListViewModel ListOfMeetingsViewModel {
get { return _listOfMeetingsViewModel; }
set
{
if (_listOfMeetingsViewModel != value)
{
_listOfMeetingsViewModel = value;
OnPropertyChanged("ListOfMeetings");
}
}
}
public TaskListViewModel _listOfTasksViewModel;
public TaskListViewModel ListOfTasksViewModel {
get{return _listOfTasksViewModel;}
set {
if (_listOfTasksViewModel != value)
{
_listOfTasksViewModel = value;
OnPropertyChanged("ListOfTasks");
}
}
}
private Calendar _calendarEventListBoxHeight;
public Calendar CalendarEventListBoxHeight
{
get { return _calendarEventListBoxHeight; }
set
{
if (_calendarEventListBoxHeight != value)
{
_calendarEventListBoxHeight = value;
OnPropertyChanged("CalendarEventListBoxHeight");
}
}
}
private bool _showCalendar;
public bool ShowCalendar
{
get { return _showCalendar; }
set {
if (_showCalendar != value)
{
_showCalendar = value;
OnPropertyChanged("ShowCalendar");
}
}
}
private bool _showTasks;
public bool ShowTasks
{
get { return _showTasks; }
set
{
if (_showTasks != value)
{
_showTasks = value;
OnPropertyChanged("ShowTasks");
}
}
}
private bool _showMeetings;
public bool ShowMeetings
{
get { return _showMeetings; }
set
{
if (_showMeetings != value)
{
_showMeetings = value; OnPropertyChanged("ShowMeetings");
}
}
}
public void ShowCalendarAction()
{
ShowCalendar = true;
CalendarEventListBoxHeight = Calendar.ShowCalendarMaxLength;
}
public void HideCalendarAction()
{
ShowCalendar = false;
CalendarEventListBoxHeight = Calendar.HideCalenderHeight;
}
public void ShowMeetingsAction()
{
ShowTasks = false;
ShowMeetings = true;
}
public void ShowTasksAction() {
ShowMeetings = false;
ShowTasks = true;
}
private void Init()
{
ShowCalendar = true;
CalendarEventListBoxHeight = Calendar.ShowCalendarMaxLength;
ShowMeetings = true;
ShowTasks = false;
ListOfMeetingsViewModel = new MeetingsListViewModel();
ListOfTasksViewModel = new TaskListViewModel();
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
And this is the 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:conv="clr-namespace:WpfApplication1.Converters"
xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
xmlns:cal="http://www.caliburnproject.org"
xmlns:views="clr-namespace:WpfApplication1.Views"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Title="MainWindow" Height="350" Width="525"
>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="VisibilityConverter"/>
<conv:InverseBooleanConverter x:Key="InverseVisibilityConverter"/>
<conv:GridViewLengthConverter x:Key="LengthConverter" />
</Window.Resources>
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Calendar Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,0,0"
Visibility="{Binding Path=ShowCalendar, Mode=TwoWay,Converter={StaticResource VisibilityConverter}}"
>
</Calendar>
<Button Margin="0,12,0,0"
FontSize="15"
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="Show Calendar"
Visibility="{Binding Path=ShowCalendar,Mode=TwoWay,Converter={StaticResource InverseVisibilityConverter}}"
ToolTip="ShowCalendar">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="ShowCalendarAction" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<Button Margin="0,32,0,0"
FontSize="15"
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Visibility="{Binding Path=ShowCalendar,Mode=TwoWay,Converter={StaticResource VisibilityConverter}}"
Content="Hide Calendar"
ToolTip="HideCalendarButtonClick">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="HideCalendarAction" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<Button Margin="0,5,0,0"
Grid.Row="1"
Grid.Column="0"
FontSize="15"
HorizontalAlignment="Left"
Visibility="{Binding Path=ShowMeetings,Mode=TwoWay,Converter={StaticResource InverseVisibilityConverter}}"
Content="Show Meetings"
ToolTip="ShowMeetingsButtonClick">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="ShowMeetingsAction" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<Button Margin="20,5,0,0"
Grid.Row="1"
Grid.Column="0"
FontSize="15"
Grid.ColumnSpan="3"
HorizontalAlignment="Left"
Visibility="{Binding Path=ShowTasks,Mode=TwoWay,Converter={StaticResource InverseVisibilityConverter}}"
Content="Show Tasks;"
ToolTip="ShowTasksButtonClick">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="ShowTasksAction" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<Grid Grid.Row="2"
Grid.RowSpan="3"
Grid.Column="0"
Grid.ColumnSpan="2"
MaxHeight="{Binding Path=CalendarEventListBoxHeight, Mode=TwoWay, Converter={StaticResource LengthConverter }}"
Visibility="{Binding Path=ShowMeetings, Mode=TwoWay,Converter={StaticResource VisibilityConverter}}"
>
<views:MeetingsListView DataContext="{Binding Path=ListOfMeetingsViewModel,Mode=TwoWay}">
</views:MeetingsListView>
</Grid>
<Grid Grid.Row="2"
Grid.RowSpan="3"
Grid.Column="0"
Grid.ColumnSpan="2"
MaxHeight="{Binding Path=CalendarEventListBoxHeight, Converter={StaticResource LengthConverter }}"
Visibility="{Binding Path=ShowTaks,Converter={StaticResource LengthConverter}}"
>
<views:TaskListView DataContext="{Binding Path=ListOfTasksViewModel,Mode=TwoWay}" />
</Grid>
</Grid>
</Grid>
</Window>
For the sake of completeness the two converters:
InverseBooleanToVisibiltyConverter
public class InverseBooleanConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (targetType != typeof(Visibility))
throw new InvalidOperationException("The target must be a boolean");
if (!(bool)value)
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
GridViewLengthConverter
class GridViewLengthConverter:IValueConverter{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double val = (int)value;
GridLength gridLength = new GridLength(val);
return gridLength;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
GridLength val = (GridLength)value;
return val.Value;
}
}
I guess you could remove some code by optimizing your toggle behavior with less booleans =)...
//Edit: I strongly believe that the issue is outside the code you have shown. Especially the loading and exchange part or what was describe in your comment as "a lot more complex functionality" in the case ViewMode. Nevertheless since you have already an IsCaseLoaded-Property in place. I assume you are doing some async data fetching here. Async/await could also be tricky with MVVM. Especially when mixing UI-related operations with background operations. Attached you find some helpful links how to deal with async code and MVVM. This series shows approaches for async-bindable-notification-properties, async IComannd implementation and async services.
Async Programming : Patterns for Asynchronous MVVM Applications: Data Binding
https://msdn.microsoft.com/en-us/magazine/dn605875.aspx
Async Programming : Patterns for Asynchronous MVVM Applications: Commands
https://msdn.microsoft.com/en-us/magazine/dn630647.aspx
Async Programming : Patterns for Asynchronous MVVM Applications: Services
https://msdn.microsoft.com/en-us/magazine/dn683795.aspx
Hope that helps...
I have the following class:
public class LooklessControl : Control
{
public List<int> IntList { get; private set; }
public int CurrentInt { get; private set; }
private int _index = 0;
static LooklessControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LooklessControl), new FrameworkPropertyMetadata(typeof(LooklessControl)));
}
public LooklessControl()
{
IntList = new List<int>();
for (int i = 0; i < 10; i++)
{
IntList.Add(i);
}
CurrentInt = IntList[_index];
}
public static readonly RoutedCommand NextItemCommand =
new RoutedCommand("NextItemCommand", typeof(LooklessControl));
private void ExecutedNextItemCommand(object sender, ExecutedRoutedEventArgs e)
{
NextItemHandler();
}
private void CanExecuteNextItemCommand(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
public static readonly RoutedCommand PrevItemCommand =
new RoutedCommand("PrevItemCommand", typeof(LooklessControl));
private void ExecutedPrevItemCommand(ExecutedRoutedEventArgs e)
{
PrevItemHandler();
}
private void CanExecutePrevItemCommand(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
public static readonly RoutedEvent NextItemEvent =
EventManager.RegisterRoutedEvent("NextItemEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(LooklessControl));
public event RoutedEventHandler NextItem
{
add { AddHandler(NextItemEvent, value); }
remove { RemoveHandler(NextItemEvent, value); }
}
private void RaiseNextItemEvent()
{
RoutedEventArgs args = new RoutedEventArgs(LooklessControl.NextItemEvent);
RaiseEvent(args);
}
public static readonly RoutedEvent PrevItemEvent =
EventManager.RegisterRoutedEvent("PrevItemEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(LooklessControl));
public event RoutedEventHandler PrevItem
{
add { AddHandler(PrevItemEvent, value); }
remove { RemoveHandler(PrevItemEvent, value); }
}
private void RaisePrevItemEvent()
{
RoutedEventArgs args = new RoutedEventArgs(LooklessControl.PrevItemEvent);
RaiseEvent(args);
}
private void NextItemHandler()
{
_index++;
if (_index == IntList.Count)
{
_index = 0;
}
CurrentInt = IntList[_index];
RaiseNextItemEvent();
}
private void PrevItemHandler()
{
_index--;
if (_index == 0)
{
_index = IntList.Count - 1;
}
CurrentInt = IntList[_index];
RaisePrevItemEvent();
}
}
The class has a default style, in Generic.xaml, that looks like this:
<Style x:Key="{x:Type local:LooklessControl}" TargetType="{x:Type local:LooklessControl}">
<Setter Property="Height" Value="200"/>
<Setter Property="Width" Value="90"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:LooklessControl}">
<Border BorderBrush="Black" BorderThickness="1" Padding="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="LightGray"/>
<Rectangle Grid.Row="1" Fill="Gainsboro"/>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="10"/>
</Grid.ColumnDefinitions>
<Path Grid.Column="0" x:Name="pathLeftArrow" Data="M0,0.5 L1,1 1,0Z" Width="6" Height="14" Stretch="Fill"
HorizontalAlignment="Center" Fill="SlateBlue"/>
<TextBlock Grid.Column="1" Name="textBlock"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=CurrentInt}"
HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Junction" FontSize="13"/>
<Path Grid.Column="2" x:Name="pathRightArrow" Data="M0,0 L1,0.5 0,1Z" Width="6" Height="14" Stretch="Fill"
HorizontalAlignment="Center" Fill="SlateBlue"/>
</Grid>
<ListBox Grid.Row="1" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Background="Transparent"
ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IntList}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
How do I make it so that when the user clicks on pathLeftArrow it fires LooklessControl.PrevItemCommand, or or they click on pathRightArrow and it fires LooklessControl.NextItemCommand, or they click on an item in the ListBox and LooklessControl is notified of the newly selected item?
In other words, without adding x:Class to the top of Generic.xaml and thus creating a code-behind file for it, which I assume you wouldn't want to do, how do you handle events for elements in your xaml that don't have a Command property (which is just about everything other than a Button)?
Should LooklessControl have it's own XAML file (much like what you get when you create a new UserControl) associated with it that Generic.xaml just pulls in as a MergedDictionar as its default template? Or is there some other acknowledged way to do what I'm trying to do?
To answer your last question: NO. The lookless control shouldn't require any known XAML. That is what lookless means.
You have a couple of options here, but I would recommend wrapping your elements in Buttons with a basically empty control template:
<ControlTemplate x:Key="contentOnlyButton" TargetType="{x:Type Button}">
<ContentPresenter />
</ControlTemplate>
...
<Button Grid.Column="0" Template="{StaticResource contentOnlyButton}"
Command="{x:Static local:LooklessControl.PrevItemCommand}">
<Path x:Name="pathLeftArrow" Data="M0,0.5 L1,1 1,0Z" Width="6" Height="14"
Stretch="Fill" HorizontalAlignment="Center" Fill="SlateBlue"/>
</Button>
Your other option (and I would say this is probably not what you should do for executing commands on clicks, but may be applicable in other circumstances), would be to look for the named part in your template in OnApplyTemplate, and wire up the events.
public override void OnApplyTemplate()
{
var prevElement = this.GetTemplateChild("PART_PathLeftArrow") as UIElement;
if (prevElement != null)
prevElement.MouseDown += (o, e) => PrevItemHandler();
...
}
One thing to note with doing this is that the Template isn't required to define the parts you are looking for, so you need to gracefully check for that circumstance. Throwing NullReferenceExceptions here will make restyling your control a royal pain for designers / developers who accidentally delete a required element. You will also want to follow the standard practice of naming your required elements with a PART_ syntax, and decorating your class with TemplatePart attributes.
[TemplatePart(Name = "PART_PathLeftArrow", Type = typeof(UIElement))]
[TemplatePart(Name = "PART_PathRightArrow", Type = typeof(UIElement))]
...
public class LooklessControl : Control
Edit: In order for the Button's to respond to the clicks, you need to setup CommandBindings to your functions that you had already defined. You would do this as a class command binding like so:
static LooklessControl()
{
CommandManager.RegisterClassCommandBinding(
typeof(LooklessControl),
new CommandBinding(NextItemCommand, ExecutedNextItemCommand, CanExecuteNextItemCommand));
CommandManager.RegisterClassCommandBinding(
typeof(LooklessControl),
new CommandBinding(PrevItemCommand, ExecutedPrevItemCommand, CanExecutePrevItemCommand));
}
The reason to do a class command binding is that if you add it to your control's CommandBindings collection, somebody using your control could inadvertently remove them. Also remember to update your command handling methods to have static semantics.