I'm stumped on this one. Why would a command parameter be continuously empty despite being bound to a property that has a value.
XAML
<UserControl x:Class="CatalogInterface.ctlMainButtonPanel"
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:CatalogInterface"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="MainButtonPanel">
<UserControl.Resources>
<local:MainButtonPanelViewModel x:Key="ViewModel" />
<!--#region Button Style-->
<!--#region FocusVisual-->
<!--#endregion FocusVisual-->
<!--#region Button-->
<!--#endregion Button-->
<!--#endregion Button Style-->
</UserControl.Resources>
<Grid DataContext="{StaticResource ViewModel}">
<!--/////////////////////////////////////////////////////////////////////////////////-->
<!--////////This is the button that won't pass the CommandParameter//////////////////-->
<!--/////////////////////////////////////////////////////////////////////////////////-->
<Button x:Name="cmdConvertToFBook"
CommandParameter="{Binding SelectedDocument}"
Command="{Binding ConvertToFacebookCommand}"
Content="CONVERT TO FACEBOOK"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Grid.Column="0" Grid.Row="2" />
</UserControl>
View Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace CatalogInterface
{
class MainButtonPanelViewModel : ViewModelBase
{
private Messanger _messanger = Messanger.Set_Messanger();
/////////////////////////////////////////////////////////////////////////////////
////////This is the property I'm binding my commandparameter to//////////////////
/////////////////////////////////////////////////////////////////////////////////
private object _selectedDocument
public object SelectedDocument
{
get { return _selectedDocument; }
set { _selectedDocument = value; }
}
public ConvertToFacebookCommand ConvertToFacebookCommand { get; set; }
public MainButtonPanelViewModel()
{
ConvertToFacebookCommand = new ConvertToFacebookCommand();
_messanger.Register(OnSentMessage, "DirFilesListBox_SelectedDocumentChanged");
}
public void OnSentMessage(object source, MessageEventArgs e)
{
_selectedDocument = e.MessageObject;
}
}
public class ConvertToFacebookCommand : SingleFuncitonBaseCommand
{
public override void ButtonFunction(object parameter)
{
ConvertToFacebookFn.Convert(parameter);
}
public override void NotExacutable(object parameter)
{
ButtonNotExacutable.Report(parameter);
}
}
}
ViewModelBase
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace CatalogInterface
{
public abstract class SingleFuncitonBaseCommand : ICommand
{
private bool _canExecute { get; set; } = true;
public SingleFuncitonBaseCommand()
{
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public virtual bool CanExecute(object parameter)
{
return true;
}
public virtual void Execute(object parameter)
{
if (_canExecute && parameter != null)
{
try
{
_canExecute = false;
ButtonFunction(parameter);
}
finally
{
_canExecute = true;
}
return;
}
NotExacutable(parameter);
}
public abstract void ButtonFunction(object parameter);
public abstract void NotExacutable(object parameter);
}
}
My SelectedDocument property is being set by the OnSentMessage event that updates the property when the user clicks on a File in a list box. I believe the object is a FileInfo object. I can confirm the event is firing properly and the property is being set.
Also if i change my SelectedDocument property to a readonly property and set it to a FileInfo object it then does get passed to the parameter.
Is it possible for my OnSentMessage event and CommandParameter binding to be operating on two different objects?
How can I debug this further?
Thanks All.
Related
I am a C# newbie trying to build a simple MVVM app, and I am having trouble tying events in my XAML View to methods in my Model or ViewModel. I understand why MVVM is used and feel like I get the broad strokes of how to put an MVVM app together, but I am lost in the details. I apologize in advance if it looks like I have no idea what I'm doing, but I don't, despite lots of reading up on the subject.
I want btnUpdate_Click in MainScreenViewModel to execute when the button is clicked, but I get the error
MC6005 Click="vm:btnUpdate_Click" is not valid. 'vm:btnUpdate_Click' is not a valid event handler method name. Only instance methods on the generated or code-behind class are valid.
If my classes are public and in the same namespace, what do I need to do to make them visible from my View? I don't want to move methods back to the MainWindow class.
<Window x:Class="SFM_Calculator.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:SFM_Calculator"
xmlns:vm="MainScreenViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
>
<Window.DataContext>
<local:SFMModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
...
<ColumnDefinition/>
</Grid.ColumnDefinitions>
...
</Grid.RowDefinitions>
...
<Button
Grid.Column="1"
Grid.Row="2"
x:Name="btnUpdate"
Content="Update"
Click="vm:btnUpdate_Click"
/>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Linq;
using System.ComponentModel;
namespace SFM_Calculator
{
public class MainScreenViewModel : INotifyPropertyChanged
{
private void btnUpdate_Click(object sender, System.Windows.RoutedEventArgs e)
{
TestInt = 999;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public SFMModel sfmModel = new SFMModel();
private int _testInt;
public int TestInt
{
get { return _testInt; }
set { _testInt = value; }
}
public MainScreenViewModel()
{
Debug.WriteLine("Got here.");
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace SFM_Calculator
{
public class SFMModel : INotifyPropertyChanged
{
private int _tprop;
public int TProp
{
get { return _tprop; }
set { _tprop = value; }
}
public SFMModel ()
{
TProp = 69;
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Configuration;
namespace SFM_Calculator
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
Your viewModel should expose a command, not a method. Plus you should access the exposed command via binding mechanism.
Sample Command that implements required ICommand interface
internal class Command : ICommand
{
private readonly Action execute;
public Command(Action execute)
{
this.execute = execute;
}
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter)
{
return true;
}
public void Execute(object? parameter)
{
execute();
}
}
Sample ViewModel that exposes ICommand (not a regular method as in your example). It will change the value of Text property after clicking the button - just to show that it works.
internal class ViewModel : INotifyPropertyChanged
{
public string Text { get; private set; }
public ICommand AwesomeCommand { get; }
public ViewModel()
{
AwesomeCommand = new Command(() => {
Text = "Button clicked";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
});
}
public event PropertyChangedEventHandler? PropertyChanged;
}
MainWindow:
<StackPanel>
<Button Command="{Binding AwesomeCommand}"></Button>
<Label Content="{Binding Text}" Height="100"></Label>
</StackPanel>
code-behind MainWindow, to hook-up ViewModel and the view (MainWindow):
public MainWindow()
{
DataContext = new ViewModel();
InitializeComponent();
}
I am building a WinUI 3 application that presents a user with a list of Animal IDs, along with a Button beside each ID that the user can click to view information about that specific Animal. I am using an ItemsRepeater, whose ItemsSource is bound to an ObservableCollection<Animal> inside an AnimalViewModel, so I can create a list of TextBox with a Button beside it using a DataTemplate -- the TextBox will have as its value the Animal.ID. I also want to use the MVVM design pattern to accomplish this, but this is my first time trying it.
For now, I want the ability to click a Button and have it display a dialog box with the Animal.ID it corresponds to (the TextBox adjacent to it). I have tried implementing this by using an ICommand and defining a function to display a MessageDialog in my AnimalViewModel, then defining the custom ICommand in its own file.
The issue seems to be that since I have bound my ItemsRepeater to ObservableCollection<Animal> in my AnimalViewModel, I can't bind the Button to the command in my AnimalViewModel. This leads me to believe I am not using MVVM correctly or I have improperly structured my code, but I am not sure what to change/how to move forward.
Error
BindingExpression path error: 'DisplayIDsCommand' property not found on 'MAIT.Models.Animal'
Below is the relevant code. I am able to create the list but I cannot get the Button to exhibit the desired behavior (please forgive any glaring errors, I had to simplify the code for this question):
MainWindow.xaml
<Window
x:Class="MAIT.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MAIT"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:l="using:MAIT.Models"
mc:Ignorable="d">
<Grid x:Name="MainGrid">
<Grid.Resources>
<muxc:StackLayout x:Name="VerticalStackLayout" Orientation="Vertical" Spacing="8"/>
<DataTemplate x:Key="AnimalTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="text" Text="{Binding ID}"></TextBlock>
<Button Command="{Binding DisplayIDsCommand}" CommandParameter="{Binding ElementName=text, Path=Text}">View</Button>-
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Auto" HorizontalAlignment="Center" VerticalAlignment="Center"
HorizontalScrollMode="Auto"
IsVerticalScrollChainingEnabled="False"
MaxHeight="500">
<muxc:ItemsRepeater
ItemsSource="{Binding Path=Animals}"
Layout="{StaticResource VerticalStackLayout}"
ItemTemplate="{StaticResource AnimalTemplate}"/>
</ScrollViewer>
</Grid>
</Window>
MainWindow.xaml.cs
using Microsoft.UI.Xaml;
using MAIT.ViewModels;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace MAIT
{
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
MainGrid.DataContext = new AnimalViewModel();
}
}
}
Animal.cs
using System.ComponentModel;
using System.Diagnostics;
namespace MAIT.Models
{
internal class Animal : INotifyPropertyChanged
{
string _ID;
public Animal(string id)
{
ID = id;
}
public string ID
{
get
{
return _ID;
}
set
{
_ID = value;
OnPropertyChanged("ID");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
AnimalViewModel.cs
using MAIT.Commands;
using MAIT.Models;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Data.OleDb;
using System.Diagnostics;
using Windows.UI.Popups;
namespace MAIT.ViewModels
{
internal class AnimalViewModel : INotifyPropertyChanged
{
public IDsCommand DisplayIDsCommand;
public AnimalViewModel()
{
Animals = new ObservableCollection<Animal>();
DisplayIDsCommand = new IDsCommand(DisplayIDs);
GetAnimals();
}
private void GetAnimals()
{
for(int i = 0; i < 3; i++)
{
Animal animal = new Animal(i.ToString());
Animals.Add(animal);
}
}
public async void DisplayIDs(string id)
{
MessageDialog t = new MessageDialog(id);
await t.ShowAsync();
}
private ObservableCollection<Animal> _Animals;
public ObservableCollection<Animal> Animals
{
get
{
return _Animals;
}
set
{
_Animals = value;
OnPropertyChanged("Animals");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
IDsCommand.cs
using MAIT.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace MAIT.Commands
{
internal class IDsCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private Action<string> _Execute;
public IDsCommand(Action<string> execute)
{
_Execute = execute;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_Execute.Invoke(parameter as string);
}
}
}
your first WPF appears to be well-on-its-way.
If you want to bind to your command, you will need to use a private field along with the getter and setter, such as with the Animals property, and call OnPropertyChanged in its setter as well. This will let the button know when a command is assigned!
Here are a few friendly pointers if you are interested about learning MVVM:
Consider replacing the using in the local parameter to xmlns:local="clr-namespace:MAIT".
In MVVM DataContext property can be set in the Window's constructor but usually it's put in the XALM above <Grid> as
<Window.DataContext>
<viewModels:AnimalViewModel/>
</Window.DataContext>
Because this context will not allow you to use local.ViewModels, you can declare another xmlns as xmlns:viewModels="clr-namespace:MAIT.ViewModels"
If you are using recent .NET versions, you might appreciate using PropertyChanged?.Invoke(this, new(propertyName)); as the method body for OnPropertyChanged. Moreover, you can use System.Runtime.CompilerServices to automatically get the calling member's name by changing the OnPropertyChanged's signature to private void OnPropertyChanged([CallerMemberName] string propertyName = "").
To be able to bind the Command property to a source property of a parent element, you need to get a reference to the parent element somehow. In Win UI, you cannot use something RelativeSource AncestorType=ItemsRepeater.
You could workaround this by creating an attached property that sets the DataContext of the Button to the parent ItemsRepeater:
public static class AncestorSource
{
public static readonly DependencyProperty AncestorTypeProperty =
DependencyProperty.RegisterAttached(
"AncestorType",
typeof(Type),
typeof(AncestorSource),
new PropertyMetadata(default(Type), OnAncestorTypeChanged)
);
public static void SetAncestorType(FrameworkElement element, Type value) =>
element.SetValue(AncestorTypeProperty, value);
public static Type GetAncestorType(FrameworkElement element) =>
(Type)element.GetValue(AncestorTypeProperty);
private static void OnAncestorTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement target = (FrameworkElement)d;
if (target.IsLoaded)
SetDataContext(target);
else
target.Loaded += OnTargetLoaded;
}
private static void OnTargetLoaded(object sender, RoutedEventArgs e)
{
FrameworkElement target = (FrameworkElement)sender;
target.Loaded -= OnTargetLoaded;
SetDataContext(target);
}
private static void SetDataContext(FrameworkElement target)
{
Type ancestorType = GetAncestorType(target);
if (ancestorType != null)
target.DataContext = FindParent(target, ancestorType);
}
private static object FindParent(DependencyObject dependencyObject, Type ancestorType)
{
DependencyObject parent = VisualTreeHelper.GetParent(dependencyObject);
if (parent == null)
return null;
if (ancestorType.IsAssignableFrom(parent.GetType()))
return parent;
return FindParent(parent, ancestorType);
}
}
Usage:
<DataTemplate x:Key="AnimalTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="text" Text="{Binding ID}"></TextBlock>
<Button local:AncestorSource.AncestorType="muxc:ItemsRepeater"
Command="{Binding DataContext.DisplayIDsCommand}"
CommandParameter="{Binding ElementName=text, Path=Text}">View</Button>-
</StackPanel>
</DataTemplate>
Please refer to this blog post for more information.
I just started learning MVVM and here is what seems to be basic question but I spent whole day trying to figure it out.
I have a solution that contains 3 projects one for Model, one for ViewModel and one for View. The Model contains a class that has 2 properties Text and CheckStatus.
The ViewModel has a list called listOfItems that has three items, each item has these 2 properties from the Model.
The View has a listView inside it there is a CheckBox. What is the proper way to bind the CheckBox content to the property Text?
Here is the model
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TheModel
{
public class CheckBoxListModel : INotifyPropertyChanged
{
private string text;
public string Text
{
get { return text; }
set
{
text = value;
RaiseChanged("Text");
}
}
private bool checkStatus;
public bool CheckStatus
{
get { return checkStatus; }
set
{
checkStatus = value;
RaiseChanged("CheckStatus");
}
}
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Here is the view model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using TheModel;
namespace TheViewModel
{
public class TheViewModel
{
public List<CheckBoxListModel> ListOfItems { get; set; }
public TheViewModelClass()
{
ListOfItems = new List<CheckBoxListModel>
{
new CheckBoxListModel
{
CheckStatus = false,
Text = "Item 1",
},
new CheckBoxListModel
{
CheckStatus = false,
Text = "Item 2",
},
new CheckBoxListModel
{
CheckStatus = false,
Text = "Item 3",
}
};
}
public static implicit operator List<object>(TheViewModelClass v)
{
throw new NotImplementedException();
}
}
}
and here is the View XAML
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctrl="clr-namespace:TheView.Managers" xmlns:TheViewModel="clr-
namespace:TheViewModel;assembly=TheViewModel"
x:Class="TheView.Styles.ListViewDatabaseStyle">
<UserControl.DataContext>
<TheViewModel:TheViewModelClass/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="100"/>
</Grid.RowDefinitions>
<Button Content="Continue" Style="{StaticResource ButtonStyle}"
Margin="1104,27,40,40"/>
<ListView x:Name="listView1" SelectionMode="Multiple"
Style="{StaticResource ListViewStyle}" Margin="10,55,10,10"
ctrl:ListViewLayoutManager.Enabled="true" ItemsSource="
{Binding TheViewModelClass}" >
<ListView.View>
<GridView>
<GridViewColumn Header="Competency Items"
ctrl:ProportionalColumn.Width="1100"/>
</GridView>
</ListView.View>
<ListView.ItemContainerStyle >
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding
CheckedStatus}"/>
<Setter Property="HorizontalContentAlignment"
Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox
Click="CheckBox_Click"
Content="{Binding Path=TheViewModelClass.Text}"
IsChecked="{Binding
Path=TheViewModelClass.CheckedStatus}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</UserControl>
Here is the View behind code, I know I shouldn't have something here but where should that part go?
using System.Windows;
using System.Windows.Controls;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System;
using System.Text;
using TheViewModel;
namespace TheView.Styles
{
public partial class ListViewDatabaseStyle : UserControl
{
public ListViewDatabaseStyle()
{
InitializeComponent();
}
public List<string> selectedNames = new List<string>();
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
var ChkBox = sender as CheckBox;
var item = ChkBox.Content;
bool isChecked = ChkBox.IsChecked.HasValue ? ChkBox.IsChecked.Value
: false;
if (isChecked)
selectedNames.Add(item.ToString());
else
selectedNames.Remove(item.ToString());
}
}
}
This is all quite ridiculous.
Here is a much easier way which involves no external libraries, no additional housekeeping classes and interfaces, almost no magic, and is very flexible because you can have viewmodels that contain other viewmodels, and you get to instantiate each one of them, so you can pass constructor parameters to them:
For the viewmodel of the main window:
using Wpf = System.Windows;
public partial class TestApp : Wpf.Application
{
protected override void OnStartup( Wpf.StartupEventArgs e )
{
base.OnStartup( e );
MainWindow = new MainView();
MainWindow.DataContext = new MainViewModel( e.Args );
MainWindow.Show();
}
}
For all other viewmodels:
This is in MainViewModel.cs:
using Collections = System.Collections.Generic;
public class MainViewModel
{
public SomeViewModel SomeViewModel { get; }
public OtherViewModel OtherViewModel { get; }
public Collections.IReadOnlyList<string> Arguments { get; }
public MainViewModel( Collections.IReadOnlyList<string> arguments )
{
Arguments = arguments;
SomeViewModel = new SomeViewModel( this );
OtherViewModel = new OtherViewModel( this );
}
}
This in MainView.xaml:
[...]
xmlns:local="clr-namespace:the-namespace-of-my-wpf-stuff"
[...]
<local:SomeView DataContext="{Binding SomeViewModel}" />
<local:OtherView DataContext="{Binding OtherViewModel}" />
[...]
As you can see, a viewmodel can simply be a member (child) of another viewmodel; in this case SomeViewModel and OtherViewModel are children of MainViewModel. Then, in the XAML file of MainView, you can just instantiate each of the child views and specify their DataContext by Binding to the corresponding child viewmodels.
First of all. Set dependencies of projects. ViewModel must have access Model. (View and Model projects do not have to reference to other projects.) If i were you i would make a StartUp Project to transfer the control to ViewModel.
This "StartUp" project should be WPF, all of others should be "class library" but don't forget to add the required references to projects (For example the system.xaml for your view project to create usercontrols.)
Projects dependencies:
- StartUp --> ViewModel;
(- ViewModel --> View; or avoid this with DI)
- ViewModel --> Model;
(I should make another project for interfaces just this is just my perversions.)
StartUp Project:
Now in your startup (WPF) project should contains in (app.xaml.cs):
protected override void OnStartup(StartupEventArgs e)
{
// delete the startupuri tag from your app.xaml
base.OnStartup(e);
//this MainViewModel from your ViewModel project
MainWindow = new MainWindow(new MainViewModel());
}
The only one thing (Window) in your startup wpf project (to display your UserControls).
MainWindow.xaml content:
<Window x:Class="StartUp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" WindowState="Maximized" WindowStyle="None" AllowsTransparency="True">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding Control}"/>
</Window>
(and xaml.cs)
public partial class MainWindow : Window
{
public MainWindow(INotifyPropertyChanged ViewModel)
{
InitializeComponent();
this.DataContext = ViewModel;
this.Show();
}
}
And Thats all your StartUp WPF project.
In this way we gave the control to your ViewModel project.
(Okay, its just an extra, but i should make a "ViewService" to handle my UserControls)
Interface to find all of View and match the View with ViewModel.
public interface IControlView
{
INotifyPropertyChanged ViewModel { get; set; }
}
I created a singleton to store and match my views with my viewmodels. (You can skip this part.) I defined this in Model project.
public class ViewService<T> where T : IControlView
{
private readonly List<WeakReference> cache;
public delegate void ShowDelegate(T ResultView);
public event ShowDelegate Show;
public void ShowControl<Z>(INotifyPropertyChanged ViewModel)
{
if (Show != null)
Show(GetView<Z>(ViewModel));
}
#region Singleton
private static ViewService<T> instance;
public static ViewService<T> GetContainer
{
get
{
if (instance == null)
{
instance = new ViewService<T>();
}
return instance;
}
}
private ViewService()
{
cache = new List<WeakReference>();
var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(s => s.GetTypes()).Where(r => typeof(T).IsAssignableFrom(r) && !r.IsInterface && !r.IsAbstract && !r.IsEnum);
foreach (Type type in types)
{
cache.Add(new WeakReference((T)Activator.CreateInstance(type)));
}
}
#endregion
private T GetView<Z>(INotifyPropertyChanged ViewModel)
{
T target = default(T);
foreach (var wRef in cache)
{
if (wRef.IsAlive && wRef.Target.GetType().IsEquivalentTo(typeof(Z)))
{
target = (T)wRef.Target;
break;
}
}
if(target==null)
target = (T)Activator.CreateInstance(typeof(Z));
if(ViewModel != null)
target.ViewModel = ViewModel;
return target;
}
}
And now you have got a "service" to show your UserControls in the mainwindow from your
ViewModel:
public class MainViewModel : INotifyPropertyChanged
{
private IControlView _control;
public IControlView Control
{
get
{
return _control;
}
set
{
_control = value;
OnPropertyChanged();
}
}
public MainViewModel()
{ //Subscribe for the ViewService event:
ViewService<IControlView>.GetContainer.Show += ShowControl;
// in this way, here is how to set a user control to the window.
ViewService<IControlView>.GetContainer.ShowControl<ListViewDatabaseStyle>(new TheViewModel(yourDependencyItems));
//you can call this anywhere in your viewmodel project. For example inside a command too.
}
public void ShowControl(IControlView ControlView)
{
Control = ControlView;
}
//implement INotifyPropertyChanged...
protected void OnPropertyChanged([CallerMemberName] string name = "propertyName")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
If you don't want to use this "ViewService". Just create an UserControl instance, match DataContext of View with your ViewModel and give this view to Control property.
Here is your ViewModel with list (still in ViewMoldel project.)
public class TheViewModel
{
private readonly ObservableCollection<ISelectable> listOfItems;
public ObservableCollection<ISelectable> ListOfItems
{
get { return listOfItems; }
}
public ICommand SaveCheckedItemsText{
get{ return new RelayCommand(CollectNamesOfSelectedElements);}
}
public IEnumerable<ISelectable> GetSelectedElements
{
get { return listOfItems.Where(item=>item.CheckStatus); }
}
public TheViewModel(IList<ISelectable> dependencyItems)
{
listOfItems= new ObservableCollection<ISelectable>(dependencyItems);
}
//here is your list...
private List<string> selectedNames
//use this...
private void CollectNamesOfSelectedElements()
{
selectedNames = new List<string>();
foreach(ISelectable item in GetSelectedElements)
{
//you should override the ToString in your model if you want to do this...
selectedNames.Add(item.ToString());
}
}
}
RelayCommand article
View: (Keep here all of your usercontrols.)
In your UserControl (xaml):
<UserControl x:Class="View.ListViewDataStyle"
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" namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d">
<Button Command={Binding SaveCheckedItemsText}/>
<!-- Another content -->
<ListView ItemsSource="{Binding ListOfItems}">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}" IsChecked="{Binding CheckedStatus}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>
And with interface here is the xaml.cs code (for UserControls):
public partial class ListViewDatabaseStyle : UserControl, IControlView
{
public ListViewDatabaseStyle ()
{
InitializeComponent();
}
public INotifyPropertyChanged ViewModel
{
get
{
return (INotifyPropertyChanged)DataContext;
}
set
{
DataContext = value;
}
}
}
And the last one is the Model project with your models:
public interface ISelectable
{
bool CheckStatus { get; set; }
}
public class CheckBoxListModel : INotifyPropertyChanged, ISelectable
{
private string text;
public string Text
{
get { return text; }
set
{
text = value;
RaiseChanged("Text");
}
}
private bool checkStatus;
public bool CheckStatus
{
get { return checkStatus; }
set
{
checkStatus = value;
RaiseChanged("CheckStatus");
}
}
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Excuse me for english grammar mistakes, i hope you understood my post.
Update:
Use the DI techn. to avoid the reference to view from viewmodel. DI service will inject the correct object with constructor injection.
<UserControl.DataContext>
<TheViewModel:TheViewModelClass/>
</UserControl.DataContext>
<ListView ItemsSource="{Binding ListOfItems}">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}" IsChecked="{Binding CheckedStatus}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I'm building an application using CefSharp and need to provide text search functionality to user just like Google Chrome has.
Can any one help me with the implementation of text search in CefSharp?
You can do it simple just add two buttons and a textbox on your form.
first buttons for next result,second buttons for previous result and textbox for search text provider.
On textbox's KeyUp event run below code
if (tosBrowserSearchTxt.Text.Length <= 0)
{
//this will clear all search result
webBrowserChromium.StopFinding(true);
}
else
{
webBrowserChromium.Find(0, tosBrowserSearchTxt.Text, true, false,false);
}
On next button click run below code
webBrowserChromium.Find(0, tosBrowserSearchTxt.Text, true, false, false);
On previous button click run below code
webBrowserChromium.Find(0, tosBrowserSearchTxt.Text, false, false, false);
When user type any character into text box KeyUp event's code will search that text, by using next and previous button you can navigate from one result to another.
I've built this demo application using CefSharp 47.0.3, hopefully this is what you're looking for.
The view:
<Window x:Class="CefSharpSearchDemo.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:wpf="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cefSharpSearchDemo="clr-namespace:CefSharpSearchDemo"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
d:DataContext="{d:DesignInstance {x:Type cefSharpSearchDemo:MainWindowViewModel}}">
<DockPanel>
<DockPanel DockPanel.Dock="Top">
<Button Content="Next" DockPanel.Dock="Right" Command="{Binding ElementName=SearchBehavior, Path=NextCommand}" />
<Button Content="Previous" DockPanel.Dock="Right" Command="{Binding ElementName=SearchBehavior, Path=PreviousCommand}" />
<TextBox DockPanel.Dock="Right" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</DockPanel>
<wpf:ChromiumWebBrowser x:Name="wb" DockPanel.Dock="Bottom"
Address="http://stackoverflow.com">
<i:Interaction.Behaviors>
<cefSharpSearchDemo:ChromiumWebBrowserSearchBehavior x:Name="SearchBehavior" SearchText="{Binding SearchText}" />
</i:Interaction.Behaviors>
</wpf:ChromiumWebBrowser>
</DockPanel>
</Window>
The code-behind for the view:
namespace CefSharpSearchDemo
{
using System.Windows;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
}
The view model:
namespace CefSharpSearchDemo
{
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _searchText;
public string SearchText
{
get { return _searchText; }
set
{
_searchText = value;
NotifyPropertyChanged();
}
}
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And now the important part. As you could see in the view there is a behavior attached to the ChromiumWebBrowser:
namespace CefSharpSearchDemo
{
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;
using CefSharp;
using CefSharp.Wpf;
public class ChromiumWebBrowserSearchBehavior : Behavior<ChromiumWebBrowser>
{
private bool _isSearchEnabled;
public ChromiumWebBrowserSearchBehavior()
{
NextCommand = new DelegateCommand(OnNext);
PreviousCommand = new DelegateCommand(OnPrevious);
}
private void OnNext()
{
AssociatedObject.Find(identifier: 1, searchText: SearchText, forward: true, matchCase: false, findNext: true);
}
private void OnPrevious()
{
AssociatedObject.Find(identifier: 1, searchText: SearchText, forward: false, matchCase: false, findNext: true);
}
protected override void OnAttached()
{
AssociatedObject.FrameLoadEnd += ChromiumWebBrowserOnFrameLoadEnd;
}
private void ChromiumWebBrowserOnFrameLoadEnd(object sender, FrameLoadEndEventArgs frameLoadEndEventArgs)
{
_isSearchEnabled = frameLoadEndEventArgs.Frame.IsMain;
Dispatcher.Invoke(() =>
{
if (_isSearchEnabled && !string.IsNullOrEmpty(SearchText))
{
AssociatedObject.Find(1, SearchText, true, false, false);
}
});
}
public static readonly DependencyProperty SearchTextProperty = DependencyProperty.Register(
"SearchText", typeof(string), typeof(ChromiumWebBrowserSearchBehavior), new PropertyMetadata(default(string), OnSearchTextChanged));
public string SearchText
{
get { return (string)GetValue(SearchTextProperty); }
set { SetValue(SearchTextProperty, value); }
}
public static readonly DependencyProperty NextCommandProperty = DependencyProperty.Register(
"NextCommand", typeof (ICommand), typeof (ChromiumWebBrowserSearchBehavior), new PropertyMetadata(default(ICommand)));
public ICommand NextCommand
{
get { return (ICommand) GetValue(NextCommandProperty); }
set { SetValue(NextCommandProperty, value); }
}
public static readonly DependencyProperty PreviousCommandProperty = DependencyProperty.Register(
"PreviousCommand", typeof (ICommand), typeof (ChromiumWebBrowserSearchBehavior), new PropertyMetadata(default(ICommand)));
public ICommand PreviousCommand
{
get { return (ICommand) GetValue(PreviousCommandProperty); }
set { SetValue(PreviousCommandProperty, value); }
}
private static void OnSearchTextChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var behavior = dependencyObject as ChromiumWebBrowserSearchBehavior;
if (behavior != null && behavior._isSearchEnabled)
{
var newSearchText = dependencyPropertyChangedEventArgs.NewValue as string;
if (string.IsNullOrEmpty(newSearchText))
{
behavior.AssociatedObject.StopFinding(true);
}
else
{
behavior.AssociatedObject.Find(1, newSearchText, true, false, false);
}
}
}
protected override void OnDetaching()
{
AssociatedObject.FrameLoadEnd -= ChromiumWebBrowserOnFrameLoadEnd;
}
}
}
And the minor additional code for the DelegateCommand:
namespace CefSharpSearchDemo
{
using System;
using System.Windows.Input;
public class DelegateCommand : ICommand
{
private readonly Action _action;
public DelegateCommand(Action action)
{
_action = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_action();
}
public event EventHandler CanExecuteChanged;
}
}
The resulting application has a TextBox on the top and two buttons labeled "Previous" and "Next" next to it.
The main area is a CefSharp browser which loads http://www.stackoverflow.com.
You can type into the TextBox and it will search in the browser (and highlight the scrollbar where the hits are, just like in Chrome). You can then press the Next/Previous buttons to cycle through the hits.
I hope this helps in developing your own solution.
All this said, let me just note that next time if you ask a question, actually provide some code what you tried, or try to ask a more specific question, because this is probably too broad for this site. Anyway I leave this here, maybe others will find it useful as well.
Important lesson: there are some methods exposed on ChromiumWebBrowser that you can use to implement the search functionality (namely: Find and StopFinding).
I need Attached Property that sets focus to UIElement from ViewModel.
I've created Attached property and in PropertyChangedCallback I set focus to UIElement.
private static void VoySetFocusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
if (o is UIElement)
{
if ((bool)e.NewValue)
{
(o as UIElement).Focus();
(o as UIElement).SetValue(VoySetFocusProperty, false);
}
}
}
but I want it to work like trigger in shotgun. I set true to Test in ViewModel ...
it invokes PropertyChangedCallback in MyAttachedProperties class,sets focus to UIElement
((o as UIElement).Focus();
and value of Test in ViewModel returns to false
((o as UIElement).SetValue(VoySetFocusProperty, false);)
Everything seems to work fine but SetValue doesn't change my value in ViewModel back.
Full code:
View:
<Window x:Class="WpfAttachedProperty.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfAttachedProperty"
Title="MainWindow" Height="127" Width="316">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBox local:MyAttachedProperties.VoySetFocus="{Binding Path=Test,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" Text="Focus Me"/>
<Button Grid.Row="1" Content="Click to Focus" HorizontalAlignment="Left" Margin="10" VerticalAlignment="Top" Width="75" Command="{Binding MyCommand}" />
</Grid>
Code Behind:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfAttachedProperty
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow:Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
/// <summary>
/// Command Class
/// </summary>
public class DelegateCommand:ICommand
{
private readonly Action _action;
public DelegateCommand(Action action)
{
_action = action;
}
public void Execute(object parameter)
{
_action();
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged
{
add
{
}
remove
{
}
}
}
/// <summary>
/// ViewModelClass
/// </summary>
public class ViewModel:INotifyPropertyChanged
{
private bool _test = false;
public bool Test
{
get
{
return _test;
}
set
{
_test = value;
this.NotifyPropertyChanged("Test");
}
}
public ICommand MyCommand
{
get
{
return new DelegateCommand(SetTestToTrue);
}
}
private void SetTestToTrue()
{
this.Test = true;
}
#region INotifyPropertyChanged
public void NotifyPropertyChanged(String PropertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class MyAttachedProperties
{
public static Object GetVoySetFocus(DependencyObject obj)
{
return (Object)obj.GetValue(VoySetFocusProperty);
}
public static void SetVoySetFocus(DependencyObject obj, Object value)
{
obj.SetValue(VoySetFocusProperty, value);
}
public static readonly DependencyProperty VoySetFocusProperty =
DependencyProperty.RegisterAttached("VoySetFocus", typeof(bool), typeof(MyAttachedProperties), new UIPropertyMetadata(false, new PropertyChangedCallback(VoySetFocusChanged), new CoerceValueCallback(CoerceVoySetFocus)));
private static object CoerceVoySetFocus(DependencyObject d, object baseValue)
{
return (bool)baseValue;
}
private static void VoySetFocusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
if (o is UIElement)
{
if ((bool)e.NewValue)
{
(o as UIElement).Focus();
// Doesn't set Test to false in ViewModel
(o as UIElement).SetValue(VoySetFocusProperty, false);
}
}
}
}
}
Greetings Marko.
Issue is with the line:
((o as UIElement).SetValue(VoySetFocusProperty, false);)
You should instead use SetCurrentValue to set the DP.
((o as UIElement).SetCurrentValue(VoySetFocusProperty, false);)
Explanation:
Setting the value directly of any DependencyProperty breaks the binding it with source property.
However, SetCurrentValue doesn't break the binding and push back the value back to the source property. Explanation from MSDN for SetCurrentValue:
This method is used by a component that programmatically sets the
value of one of its own properties without disabling an application's
declared use of the property. The SetCurrentValue method changes the
effective value of the property, but existing triggers, data bindings,
and styles will continue to work.
Moreover, I think setting it in PropertyChanged callback won't propagate that back to Viewmodel if do the operation synchronously since it reaches to callback from Viewmodel property setter only.
So, what we can do is do this operation asynchronously by enqueuing it on dispatcher using BeginInvoke so that it gets propagate to viewmodel Test property.
(o as UIElement).Focus();
Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate
{
// Doesn't set Test to false in ViewModel
(o as UIElement).SetCurrentValue(VoySetFocusProperty, false);
});