WPF: binding elements in main window and user control - wpf

I am new to WPF and MVVM and not sure how to solve this (probbaly simple) problem. I have a MainWindow with two regions: RibbonView and below it a TabControl (Telerik). I have big buttons in the ribbon control which are always enabled. For example, I have a button "Tenants" and when I click on this button I create a new tab item and place my TenantControl containing a GridView control (also from Telerik). Its content is automatically loaded.
Now I want to get the following to work: additionally I have some small buttons (Add, Delete, Clone etc) (Ribbon control) that are disabled by default and I want to enable these buttons when I select an item in a grid. Is the using of routed commands the proper way to solve the problem? How to enable a button in a main window if I select an item in a grid in own user control? And yes in a main window I have a reference to the user control. No need for code just a concept is needed - as I said before I am completelly new to WPF/MVVM.
Thanks for any help and best regards,
Erno

In my WPF Applications, I often use RibbonBars as well, but instead of creating different TabItems for the different views, I display them all in one place by using a ContentControl and then just changing the Content value to different view models which are then rendered as the relevant views:
In Mainview:
<ContentControl Content="{Binding ViewModel}" />
...
In MainViewModel:
ViewModel = new SomethingViewModel();
...
In App.xaml:
<DataTemplate DataType="{x:Type ViewModels:SomethingViewModel}">
<Views:SomethingView />
</DataTemplate>
In my MainViewModel class, I have a helper method:
private bool IsViewModelOfType<T>()
{
return ViewModel != null && ViewModel.GetType() == typeof(T);
}
This method is used in the ICommand.CanExecute property to enable and disable Commands dependant on the loaded view model:
public ICommand CopyTrackList
{
get { return new ActionCommand(action => ClipboardManager.SetClipboardText(
((ReleaseLabelCopyViewModel)ViewModel).TrackList), canExecute =>
IsViewModelOfType<ReleaseLabelCopyViewModel>() &&
((ReleaseLabelCopyViewModel)ViewModel).SomePropertyInChildViewModel == true); }
}

Related

Lazy load FlipView content

I have a FlipView with 2 tabs in UWP which I think behaves the as TabControl in WPF. I am following the post provided here lazy load tab control contents by Ruben Bartelink. But my problem is when I go to the screen, I see the breakpoint hitting all the InitilizeComponent() for all the user controls. Please help.
Here is my xaml:
<FlipView
x:Name="FlipViewStatus" Grid.Row="2"
Style="{StaticResource FlipViewStyleNoPageButtons}"
Background="Transparent"
SelectedIndex="{x:Bind ViewModel.SelectedTabIndex, Mode=TwoWay}">
<FlipView.ItemContainerStyle>
<Style TargetType="FlipViewItem">
<Setter Property="Padding" Value="2 4 4 2"/>
</Style>
</FlipView.ItemContainerStyle>
<FlipViewItem
x:Name="FlipViewItemOngoing"
Content="{x:Bind ViewModel.VmOnGoingRuns,Mode=OneWay}">
<FlipViewItem.ContentTemplate>
<DataTemplate x:DataType="model:VmOnGoingRunsCollection">
<Grid x:Name="FlipViewItemOngoingGrid"
PointerWheelChanged="PointerWheelIgnore">
<local:StatusRunsView
x:Name="StatusOngoingRuns"
RecentSpecimenRuns="{x:Bind OnGoingRunsCollection,
Mode=OneWay}"
IsLoadingRuns="{x:Bind IsLoadingOngoingRuns,
Mode=OneWay}"/>
</Grid>
</DataTemplate>
</FlipViewItem.ContentTemplate>
</FlipViewItem>
<FlipViewItem
x:Name="FlipViewItemSampleOutput"
Content="{x:Bind ViewModel.OutputStatusViewModel,Mode=OneWay}">
<FlipViewItem.ContentTemplate>
<DataTemplate>
<Grid x:Name="FlipViewItemSampleOutputGrid"
PointerWheelChanged="PointerWheelIgnore">
<local:StatusSampleOutputView x:Name="StatusSampleOutput"
ViewModel="{Binding}"/>
</Grid>
</DataTemplate>
</FlipViewItem.ContentTemplate>
</FlipViewItem>
</FlipView>
In the ViewModel associated to this FlipView I have some code as follows.
public class ViewModel : BindableBaseThreadSafe
{
private ISampleOutputStatusViewModel _outputStatusViewModel;
public ISampleOutputStatusViewModel OutputStatusViewModel
{
get => _outputStatusViewModel;
set => Set(ref _outputStatusViewModel, value);
}
public int SelectedTabIndex
{
//I am debugging here and the tab index is correct
get => _selectedTabIndex;
set
{
if (Set(ref _selectedTabIndex, value))
{
LoadData((StatusList)_selectedTabIndex)
.Await(OnErrorLoadingData);
}
}
}
public ViewModel()
{
VmOnGoingRuns =
lifetimeScope.Resolve<VmOnGoingRunsCollection>();
VmOnGoingRuns.OnGoingRunsCollection =
new ObservableCollection<VmStatusSpecimen>();
}
//The listname enum is being set from xaml.
private async Task LoadData(StatusList listName)
{
switch (listName)
{
case StatusList.OngoingRuns:
await LoadOngoingRuns();
break;
// This code executes correctly when the user selects the
// Output tab in the FlipView.
case StatusList.Output:
{
OutputStatusViewModel = _lifetimeScope
.Resolve<ISampleOutputStatusViewModel>();
break;
}
}
}
}
My problem is as soon as I navigate to this FlipView page, I see the breakpoint hitting the StatusSampleOutputView.xaml.cs InitilizeComponent(). I want it to lazy load, in other words only when the user selects the StatusSampleOutputView, I want this view to load. Please help what am I doing wrong.,
FlipView control from uwp is different from the TabControl in wpf, you can’t expect it to implement your requirement like you use TabControl in wpf app. Besides, FlipView derives from ItemsControl, which has a lazy load called UI virtualization. This means that UI elements represtenting the items are created on demand.
For an items control bound to multiple items collection, when items are close to being scrolled into view (a few pages away), the framework can generate UI for these items and caches them. Therefore, you could try to create multiple items(such as 100 items), then check if the framework generates UI for all items at the same time.
ContentControl can have lazy loading, you can try following:
Wrap actual item view model into some lightweight dummy view model with single property
In the FlipView item template place ContentControl and bind it to that property
Create EmptyWhenNullTemplateSelector for ContentControl, return new DataTemplate() for null and actual data template when data is present
When selection of the FlipView changes, load data and set actual view model
Uwp has a property called x:Load on a UI element. In my specific case if I wanted to lazy load a tab, I have a bool property in the ViewModel that Implements INotifyPropertyChanged, I would switch between true or false. On the other hand if you want to set the x:Load property of a UIElement in the xaml.cs, you need to do something like this.
if (sender is FrameworkElement frameworkElement)
{
// This loads the child UI elements when the expander is expanded
frameworkElement.FindName("ListViewPanels");
}

WPF - Creating a custom control with dynamic subcontrols

I am attempting to create a menu/navigation control in WPF that will be used across several applications. The control is intended to reside in a custom window, and will provide the maximize, minimize, close, drag, etc functionality. In addition to the standard "window" functions, the control will should also contain the main "menu" of the application - essentially a collection of buttons that each associate with a command and/or viewmodel - these buttons are custom controls as well (derived from radio buttons).
Essentially, my goal is to be able to add this menu control and it's buttons via XAML in a manner like this (this is pseudocode, to be clear):
<MenuControl Title="ApplicationTitle>
<MenuControl.MenuButtons>
<MenuButton Content="Button1" Command="Command1"/>
<MenuButton Content="Button2" Command="Command2"/>
</MenuControl.MenuButtons>
</MenuControl>
I've gotten to the point where I can get this working correctly for only ONE button. Once I add a second button, I get a "Specified argument was out of the range of the valid values" from my XAML.
Here is the code-behind related to the menu on my custom control:
private static readonly DependencyProperty MenuProperty = DependencyProperty.Register("Menu", typeof(ObservableCollection<NavigationButton>), typeof(CCTNavigationHeader), new FrameworkPropertyMetadata(new ObservableCollection<NavigationButton>()));
public ObservableCollection<NavigationButton> Menu
{
get
{
return (ObservableCollection<NavigationButton>)GetValue(MenuProperty);
}
set
{
SetValue(MenuProperty, value);
}
}
And here is the XAML:
<ItemsControl ItemsSource="{Binding ElementName=ctlCCTNavigationHeader, Path=Menu}"/>
This is the code utilizing the control that works, with only one button:
<Controls:CCTNavigationHeader Title="Test">
<Controls:CCTNavigationHeader.Menu>
<Controls:NavigationButton Content="Test"/>
</Controls:CCTNavigationHeader.Menu>
</Controls:CCTNavigationHeader>
And this is the code using the control that chokes, once I add a second button:
<Controls:CCTNavigationHeader Title="Test">
<Controls:CCTNavigationHeader.Menu>
<Controls:NavigationButton Content="Test"/>
<Controls:NavigationButton Content="Test"/>
</Controls:CCTNavigationHeader.Menu>
</Controls:CCTNavigationHeader>
I know I must be doing something incorrectly here, but I haven't been able to find any examples of accomplishing this type of solution anywhere. Can anyone familiar with creating custom user controls in WPF point me in the right direction?
Figured this one out. I wasn't initializing the Menu collection when creating the control. The following code fixed it:
public CCTNavigationHeader()
{
InitializeComponent();
Menu = new ObservableCollection<NavigationButton>(); //this line
}

WPF ViewModel overriding values set declaratively in XAML

I have the following user control:
<HMIClasses:vhMotor x:Name="vhDevice"
AutoSize="{Binding AutoSize,Mode=TwoWay}"
DeviceColour="{Binding DeviceColour,Mode=TwoWay}"
MotorOn="{Binding MotorOn,Mode=TwoWay}"
DeviceSize="{Binding DeviceSize,Mode=TwoWay}"
LayoutDirection="{Binding LayoutDirection,Mode=TwoWay}"
Speed="{Binding Speed,Mode=TwoWay}"
HorizontalAlignment="{Binding HorizontalAlignment,Mode=TwoWay}"
VerticalAlignment="{Binding VerticalAlignment,Mode=TwoWay}"
Margin="{Binding Margin,Mode=TwoWay}" >
This is bound to the datacontext of the (this) user control here:
public ucMotor() : base()
{
Initialise();
InitializeComponent();
this.DataContext = CreateViewModel()
}
When I place the user control in XAML, the DependenyProperties are only applied when I edit them. What I mean is, if I declare the user control and specify properties as such:
<local:ucMotor x:Name="motor1"
DeviceColour="Red"
MotorOn="True"
Speed="Superspeed"
....Sure enough the control responds to the changes within the designer.
If I close and reopen form, the control appears with the properties of the view model and does not reflect those of the XAML. For example, the view model's default colour is silver and the control appears in silver despite the XAM specifying red.If I edit the XAML, the user control is refreshed in the correct colour.
This is my first attempt at user controls and dependency properties, so can someone please tell me where I'm going wrong?
Thanks

Silverlight call command from button within listbox item template

I have seen this question asked a few times but I have not seen been able to find a complete answer to my scenario.
Within my project I have a user control that I have created as a listbox item. Within this user control I have a button
<Button x:Name="DetailButton"
Grid.Column="1"
Width="107"
Height="23"
Margin="196,94,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Click="MoreDetail_Click"
Command="{Binding GetCFSDetailCommand}"
Content="View Details [+]" />
the button has a click event specific to the view this basically expands or collapses a grid row based upon the state of visibility. I used an event here since it was specific to the ui. This button also has a command which is called in the VM.
VM code
public class SearchViewModel : INotifyPropertyChanged
{
private DelegateCommand _getCFSDetailCommand;
public DelegateCommand GetCFSDetailCommand
{
get
{
if (this._getCFSDetailCommand == null)
this._getCFSDetailCommand = new DelegateCommand(GetCFSDetailCommandExecute, CanGetCFSDetailCommandExecute);
return this._getCFSDetailCommand;
}
}
private void GetCFSDetailCommandExecute(object parameter)
{
//bind collection to model call here
}
The issue I am having is the command on the button is "lost" or never called when inside the listbox item I have the view bound to the vm and if I place this command on any other button within the view the command is called. Can anyone help me understand how to call a Command bound to button within a listbox item?
Thank you in advance
randyc,
In your original (first) post you was binding the CommandParameter to a local data context of list item. In the second post you miss that binding, and I think it is impossible in the context of second port.
In your case I propose to use the Element to Element binding to bind to the GetCFSDetailCommand command from the parent data context.
The issue in calling a command within the usercontrol as a listbox item is that the pattern is looking for the command within the context of the control. Apparently the listbox item jumps outside of the visual tree and so binding is not inherited.
To correct this I needed to explicitly set the data context of the button to the ViewModel. This was ultimately solved using the Element to Element binding which allowed me to point the datacontext of the user control to the main view that contained it.
Hope this helps

Designing WPF UserControl that gets its DataContext from outer controls: How to have some sample data in designer but use inherited DC at runtime?

I am designing a WPF user control which contains other user controls (imagine a WidgetContainer, containing different Widgets) - using M-V-VM architecture.
During development, I have WidgetContainerView in a window, window (View) spawns a WidgetContainerViewModel as its resource, and in a parameterless constructor of WidgetContainerViewModel, I fill its exposed collection with some sample widgets (WidgetViewModels).
WidgetContainer control inherits the DataContext from window, and inside, there is a ListView, that binds Widgets to WidgetView control (which is inside ListView.ItemTemplate).
Now this works OK in my WindowView, as I see my sample widgets, but once I edit the WidgetContainerView or WidgetView, there is no content - at design time, controls are standalone, and they don't inherit any DataContext, so I don't see a content, and have troubles designing them (a ListView is empty, Widget's fields as well...).
I tried adding a sample widget to the WidgetView:
public partial class WidgetView : UserControl
{
public WidgetView()
{
InitializeComponent();
if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)
{
//btw, MessageBox.Show(...) here sometimes crashes my Visual Studio (2008), but I have seen the message - this code gets executed at design time, but with some lag - I saw the message on reload of designer, but at that time, I have already commented it - wtf?
this.DataContext = new WidgetViewModel(); //creates sample widget
}
}
}
but that didn't work - I still don't see anything in designer.
I also wanted to create a WidgetViewModel as a resource in WidgetView, like this:
<UserControl x:Class="MVVMTestWidgetsControl.View.WidgetView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="WidgetViewModel" //this doesn't work!
Height="Auto" Width="Auto">
<UserControl.Resources>
<ResourceDictionary>
<ViewModel:WidgetViewModel x:Key="WidgetViewModel" />
</ResourceDictionary>
</UserControl.Resources>
<TextBlock Text="{Binding Path=Title}"></TextBlock>
</UserControl>
but I don't know how to assign a WidgetViewModel as a DataContext of a whole widget - I can't add DataContext attribute to UserControl, because WidgetViewModel is defined later in the code. Any ideas how to do this? I could use a sample data this way, and just override it in code so that it has the right content at runtime...
What are your best practices when developing user controls? Thank you, designing empty control is no fun :)).
In your second snippet, you should be able to refer to your DataContext as a DynamicResource:
DataContext="{DynamicResource WidgetViewModel}"
But most custom user controls have some sort of top level layout container, and you can set the DataContext on that container as a StaticResource.
In your case, however, you may want to consider dropping the VM portion of your code altogether since you're writing a custom UserControl. You should ask yourself what benefits are you gaining from a completely self-contained ViewModel with no real backing Model designed for just one View (i.e. the custom UserControl). Perhaps you could just define some DependencyProperties and use those?
I came up with several solutions: Add DC as resource (it will get automatically instantiated with parameterless constructor), and do the following in View's codebehind:
public PanelView()
{
InitializeComponent();
if (!DesignerProperties.GetIsInDesignMode(new DependencyObject())) //DeleteAtRelease:
{
//we are in runtime, reset DC to have it inherited
this.DataContextHolder.DataContext = DependencyProperty.UnsetValue;
}
}
Better way would be to only assign DC if we are at designtime, but VS didn't like it - it worked only sometimes, and quite nondeterministically, and once it even crashed.
Other check for design time is:
if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)
{
this.DataContext = new WidgetViewModel();
}

Resources