I have two ViewModels:
public class CommandViewModel
{
public string DisplayName { get; set; }
public ICommand Command { get; set; }
}
and
public class SomeViewModel : INotifyPropertyChanged
{
private bool someFlag;
private CommandViewModel someCommand;
public bool SomeFlag
{
get
{
return someFlag;
}
set
{
if (value == someFlag)
return;
someFlag = value;
OnPropertyChanged("SomeFlag");
}
}
public CommandViewModel SomeCommandViewModel
{
get
{
if (someCommand == null)
{
someCommand = new CommandViewModel();
// TODO: actually set the DisplayName and Command
}
return someCommand;
}
}
}
And I have two corresponding Views:
<UserControl x:Class="ButtonView"
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"
mc:Ignorable="d"
d:DesignHeight="28" d:DesignWidth="91">
<Button Content="{Binding Path=DisplayName}" Command="{Binding Path=Command}" />
</UserControl>
and
<UserControl x:Class="SomeView"
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"
mc:Ignorable="d" Height="125" Width="293" />
<ViewButton
Visibility="{Binding Path=SomeFlag, Converter={StaticResource BoolToVisibilityConverter}}"
DataContext="{Binding Path=SomeCommandViewModel}" />
</UserControl>
I'm having a problem getting ButtonView's Visibility bound when its DataContext is also bound. If I leave the DataContext out, the Visibility works just fine (when SomeFlag switches value, the button's visibility changes with it) - but the display text and command don't work. If I bind the DataContext, the display text and command work, but the visibility doesn't. I'm sure it has to do with the fact that when I bind the DataContext to SomeCommandViewModel, it is expecting "SomeFlag" to exist within it. And of course, it doesn't.
If you set the DataContext of any given Element EVERY Binding (including children ones) of this element will use the DataContext as Source unless you explicitly give another source.
What you seem to do is specify 2 DataContext at once (UserControl.DataContext is NOT read as ViewButton.DataContext is set and the first source it finds counts).
You can either explicitly take the datacontext of a given element as Kent states
OR
you can specify the source explicitly.
e.g.
<ViewButton
Visibility="{Binding Path=SomeFlag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Converter={StaticResource BoolToVisibilityConverter}}"
DataContext="{Binding Path=SomeCommandViewModel}" />
I don't condone your design, but this will work around your immediate problem:
<UserControl x:Name="root"
x:Class="SomeView"
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"
mc:Ignorable="d" Height="125" Width="293" />
<ViewButton
Visibility="{Binding Path=DataContext.SomeFlag, Converter={StaticResource BoolToVisibilityConverter}, ElementName=root}"
DataContext="{Binding Path=SomeCommandViewModel}" />
</UserControl>
Related
I'm trying to wrap my head around ReactiveUI. Most of it makes some degree of sense if you don't look at it too closely, but when I try to set up a TabControl everything explodes in my face.
I have a TabControl on my Window. I want to be able to add multiple different types of tabs to it dynamically at runtime based on different user actions. This answer explains a standard WPF way to do that, and it almost works, but since it's not ReactiveUI, anytime I try to open a tab with a Reactive view on it, everything blows up because the ViewModel dependency property hasn't been bound.
XAML:
<Window.Resources>
<DataTemplate DataType="{x:Type vm:MyTabViewModel}">
<views:MyTabEditor/>
</DataTemplate>
</Window.Resources>
...
<TabControl Name="Multitab" Grid.Column="2" ItemsSource="{Binding Tabs}">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Name}" />
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
ViewModel:
public ObservableCollection<ITabPage> Tabs { get; } = new();
public void AddNewTab()
{
var vm = new MyTabEditorViewModel();
Tabs.Add(vm);
}
XAML.cs
private void NewTab_Executed(object sender, ExecutedRoutedEventArgs e)
{
ViewModel.AddNewJob();
Multitab.SelectedIndex = Multitab.Items.Count - 1;
var last = Multitab.SelectedIndex;
this.OneWayBind(ViewModel, vm => vm.Tabs[last], v => GAH WHAT GOES HERE?!?);
}
And this is the part where I get lost. How do I set up the bindings to bind the new VM to the view that gets created for it? Multitab.SelectedItem returns the VM, not the View, and I can't seem to find any way to obtain the newly-created View object in order to bind it.
Does anyone know how to set this up properly?
Please refer to the below sample code.
Window1.xaml:
<reactiveui:ReactiveWindow x:Class="WpfApp1.Window1"
x:TypeArguments="local:ViewModel"
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:WpfApp1"
xmlns:reactiveui="http://reactiveui.net"
mc:Ignorable="d"
Title="Window1" Height="450" Width="800">
<Grid>
<TabControl Name="Multitab">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<reactiveui:ViewModelViewHost ViewModel="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</reactiveui:ReactiveWindow>
Window.xaml.cs:
public partial class Window1 : ReactiveWindow<ViewModel>
{
public Window1()
{
InitializeComponent();
ViewModel = new ViewModel();
this.WhenActivated(disposableRegistration =>
{
this.OneWayBind(ViewModel,
viewModel => viewModel.Tabs,
view => view.Multitab.ItemsSource)
.DisposeWith(disposableRegistration);
});
}
}
View Model:
public class ViewModel
{
public ObservableCollection<ITabPage> Tabs { get; } =
new ObservableCollection<ITabPage>() { new MyTabEditorViewModel() };
}
Tab View Model:
public interface ITabPage { }
public class MyTabEditorViewModel : ITabPage
{
public string Name { get; } = "Name...";
}
TabView.xaml:
<reactiveui:ReactiveUserControl x:Class="WpfApp1.TabEditorView"
x:TypeArguments="local:MyTabEditorViewModel"
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:reactiveui="http://reactiveui.net"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBlock>Tab content...</TextBlock>
</Grid>
</reactiveui:ReactiveUserControl>
TabView.xaml.cs:
public partial class TabEditorView : ReactiveUserControl<MyTabEditorViewModel>
{
public TabEditorView()
{
InitializeComponent();
}
}
I use one window to change the data, while using another window(MainWindow) to show the data.
Unexpectedly, when MainWindowViewModel catches the PropertyChanged event and RaisePropertyChanged to update MainWindow view, nothing happened in the view.
In the debugger, I found the MainWindowViewModel property has changed,and Debug has printed the message, but view not change.
I'm using Mvvmlight.
Sorry for my poor English.
I'd appreciate it if you could help me. XD!
Here is the View code:
<Window x:Class="OneTimetablePlus.Views.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:OneTimetablePlus.Views"
xmlns:tb="http://www.hardcodet.net/taskbar"
mc:Ignorable="d"
Title="MainWindow" Height="730" Width="91.52"
AllowsTransparency="True"
WindowStyle="None"
Background="Transparent"
ShowInTaskbar="False"
Topmost="True"
ResizeMode="NoResize"
DataContext="{Binding Main, Source={StaticResource Locator}}"
>
<ListBox ItemsSource="{Binding TodayDayCourses}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ShowName}" Style="{StaticResource LargeText}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
Here is the ViewModel code:
public class MainViewModel : ViewModelBase
{
public MainViewModel(IDataProvider dataProvider)
{
DataProvider = dataProvider;
dataProvider.PropertyChanged += (sender, e) =>
{
if (e.PropertyName == GetPropertyName(() => dataProvider.TodayDayCourse))
{
Debug.Print("Catch PropertyChanged TodayDayCourse");
RaisePropertyChanged(() => TodayDayCourses);
}
};
}
public List<Course> TodayDayCourses => DataProvider.TodayDayCourse2;
public IDataProvider DataProvider { get; }
}
As Clemens said, we should use => DataProvider.TodayDayCourse2.ToList() instead of => DataProvider.TodayDayCourse2.
Because the latter always returns the same instance, while is not a property changed.
Alright so I've been on this for ages now and can't see why it doesn't work.
I Have a binding from my UserControl label onto a property in the class cell. This class implements the interface INotifyPropertyChanged. There everytime the set method is called it will call OnPropertyChanged("value)which is expected to update the label that it's binded to. However this is not the case.
I did some research on stack to clarify some of the problems i've checked:
I do have my datacontext set since I use mvvm light it is set to the viewmodel locator.
this property is outside of the viewmodel but it's suppose to be in order to not make it more expandable towards other sizes of sudoku.
Edit: The Property changed is fired however the handler is always null I suspect it's not being subscribed to, hence the title.
Edit 2 The code is Sudoku game which is basically a grid of 3x3 with grids of 3x3 The hiarchy here would be Outtergrid has innergrids and innergrids have cells I thought it would be better not to include these codes since they are basically the same.
Snipit of the datacontext declared MainWindow.Window:
<Window x:Class="SudokuWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SudokuWPF"
DataContext="{Binding Main, Source={StaticResource Locator}}"
Title="SudokuWindow" Height="350" Width="525">
here is the code of the Cell class
public class Cell : INotifyPropertyChanged
{
private int _value;
public int Value
{
get { return _value; }
set
{
_value = value;
//Content = _value;
OnPropertyChanged("Value");
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And the binding in the usercontrol
<UserControl x:Class="SudokuWPF.UserControlSudoku"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:LocalControl="clr-namespace:SudokuWPF"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
HorizontalAlignment ="Stretch"
HorizontalContentAlignment ="Stretch"
VerticalAlignment ="Stretch"
VerticalContentAlignment ="Stretch"
Foreground="White"
Width="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}">
<UserControl.Resources>
<LocalControl:Cell x:Key="Cell"></LocalControl:Cell>
<LocalControl:Innergrid x:Key="Innergrid"></LocalControl:Innergrid>
<LocalControl:OuterGrid x:Key="OuterGrid"></LocalControl:OuterGrid>
<DataTemplate x:Key="CellTemplate">
<Border x:Name="Border" BorderBrush="AliceBlue" BorderThickness="1">
<Label Content="{Binding Source={StaticResource Cell}, Path=Value, UpdateSourceTrigger=PropertyChanged}"></Label>
</Border>
</DataTemplate>
Any help would be greatly appreciated.
Alright so after wrongfully eliminating the datacontext out of the problem it turned out this was the core of the problem. I now binded the datacontext to my outer grid object like this:
<UserControl x:Class="SudokuWPF.UserControlSudoku"
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" mc:Ignorable="d"
HorizontalAlignment ="Stretch"
HorizontalContentAlignment ="Stretch"
VerticalAlignment ="Stretch"
VerticalContentAlignment ="Stretch"
Foreground="White"
Width="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"
DataContext="{Binding Source=OuterGrid}"
>
public OuterGrid outerGrid = new OuterGrid();
public UserControlSudoku()
{
InitializeComponent();
MainList.DataContext = outerGrid;
}
I need to put all colors from class Colors to combobox, but without Transparent. I know how it made, but it is additionally condition - I have to do all using binding.
I have:
<Window.Resources>
<ObjectDataProvider ObjectInstance="{x:Type Colors}" MethodName="GetProperties" x:Key="colorPropertiesOdp" />
</Window.Resources>
<ComboBox ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}" DisplayMemberPath="Name" SelectedValuePath="Name"/>
and it provide all colors. But I don't know how I can delete Transparent.
Thanks for help!
I can't think of a pure XAML solution to this problem. Even a CollectionViewSource with a filter will require a function in either the codebehind or the viewmodel depending on your approach. So, you can save some code on both ends and just filter the list on the backend before its attached to the combobox. For the sake of simplicity the code below uses the window's codebehind instead of a viewmodel.
On the backend:
public static IEnumerable<String> ColorsWithoutTransparent
{
get
{
var colors = typeof (Colors);
return colors.GetProperties().Select(x => x.Name).Where(x => !x.Equals("Transparent"));
}
}
Modified XAML (Take note of the added Window DataContext):
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ComboBox Margin="50" ItemsSource="{Binding ColorsWithoutTransparent}"/>
</Grid>
You can assign this to a CollectionViewSource and Filter the transparent.
<Window.Resources>
<ObjectDataProvider ObjectInstance="{x:Type Colors}" MethodName="GetProperties" x:Key="colorPropertiesOdp" />
<CollectionViewSource x:Key="FilterCollectionView" Filter="CollectionViewSource_Filter" Source="{StaticResource colorPropertiesOdp}" />
</Window.Resources>
<ComboBox ItemsSource="{Binding Source={StaticResource FilterCollectionView}}" DisplayMemberPath="Name" SelectedValuePath="Name"/>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void CollectionViewSource_Filter(object sender, FilterEventArgs e)
{
System.Reflection.PropertyInfo pi = (System.Reflection.PropertyInfo)e.Item;
if (pi.Name == "Transparent")
{
e.Accepted = false;
}
else
{
e.Accepted = true;
}
}
}
I want to be able to share controls between my main window and tab controls. Right now, I have a tab control and status bar on the main window. Status bar contains a button and progress bar. I want to share the progress bar. When the button is clicked, it resets progress bar.
The tabs contain a button. When clicked, it should increase the progress bar by 1.
What I've done: after digging through SO/other all day, I was able to set this up.
MainWindowView
-> Declares instance of MainWindowViewModel in xaml
-> Declares tabs as usercontrols in xaml b/c will always have the same tabs
TabControl1View
-> Declares instance of TabControl2ViewModel in xaml
I need advice on how to talk to MainWindowViewModel in TabControl1View.
<Window x:Class="TabControlTest.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TabControlTest"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<DockPanel>
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem HorizontalContentAlignment="Stretch" DockPanel.Dock="Top">
<Button Height="25" Content="Reset" Command="{Binding MainWindowViewModel_ButtonClickCommand}"/>
</StatusBarItem>
<StatusBarItem HorizontalContentAlignment="Stretch" DockPanel.Dock="Bottom">
<ProgressBar Height="25" Maximum="10" Value="{Binding MainWindowViewModel_Progress, Mode=TwoWay}"/>
</StatusBarItem>
</StatusBar>
<TabControl>
<TabItem>
<local:TabControl1View/>
</TabItem>
</TabControl>
</DockPanel>
class MainWindowViewModel : ViewModelBase
{
int progress = 0;
public int MainWindowViewModel_Progress
{
get
{
return progress;
}
set
{
SetAndNotify(ref this.progress, value, () => this.MainWindowViewModel_Progress);
}
}
ICommand _ButtonClickCommand;
public ICommand MainWindowViewModel_ButtonClickCommand
{
get
{
return _ButtonClickCommand ?? (_ButtonClickCommand = new CommandHandler(() => MainWindowViewModel_ButtonClick(), true));
}
}
public void MainWindowViewModel_ButtonClick()
{
MainWindowViewModel_Progress = 0; // Reset progress
}
}
x
<UserControl x:Class="TabControlTest.TabControl1View"
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:TabControlTest"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<local:TabControl1ViewModel/>
</UserControl.DataContext>
<Grid>
<Button Content="UP" Command="{Binding TabControl1ViewModel_UpClickCommand}"/>
</Grid>
class TabControl1ViewModel : ViewModelBase
{
ICommand _UpClickCommand;
public ICommand TabControl1ViewModel_UpClickCommand
{
get
{
return _UpClickCommand ?? (_UpClickCommand = new CommandHandler(() => TabControl1ViewModel_UpClick(), true));
}
}
public void TabControl1ViewModel_UpClick()
{
// I want to increase the progress bar here
}
}
I found a good answer on using a mediator pattern here:
How can I update a property of mainWindowViewModel from another ViewModel?
But I don't understand how I can store a reference to TabControl1ViewModel in MainWindowViewModel.
Also found a ton of articles saying to declare the ViewModels inside MainViewModel and keep track of them, but they were always code snippets.
Question: how do I pass MainViewModel.Progress to TabControlViewModel?
I'm now going down the path of trying to put something like this in my MainViewModel
MainViewModel.ProgressChanged = TabControlView.(get TabControlViewModel).ProgressChanged
When you do this
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
or
<UserControl.DataContext>
<local:TabControl1ViewModel/>
</UserControl.DataContext>
You are letting the View to create a instance of MainWindowViewModel or TabControl1ViewModel by itself and bind it to your View's datacontext. That makes you loose control of passing any constructor parameter to the control.
In your MainWindowViewModel's constructor initialize the User control's Datacontext like
class MainWindowViewModel
{
MainWindowViewModel
{
ChildViewModel = new TabControl1ViewModel(this);
}
public TabControl1ViewModel ChildViewModel {get; private set;}
}
class TabControl1ViewModel
{
public MainWindowViewModel ParentViewModel {get; private set;}
TabControl1ViewModel(MainWindowViewModel mainWindowViewModel)
{
ParentViewModel = mainWindowViewModel;
}
}
View.Xaml
<Window x:Class="TabControlTest.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TabControlTest"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<DockPanel>
.
.
.
<TabControl>
<TabItem>
<local:TabControl1View DataContext={Binding ChildViewModel}/>
</TabItem>
</TabControl>
</DockPanel>