i created a simple ListView in XAML which should bind to an ObservablaCollection:
<PivotItem x:Uid="pvItemMusic" Header="Music">
<StackPanel>
<TextBlock Name="tbSelectMusicHeader" Text="Select directories that should be included into your library" FontSize="18" Margin="20"></TextBlock>
<Button Name="btnSelectSourcePath" Content="Add path" Margin="30,10,0,10" Click="btnSelectSourcePath_Click"></Button>
<ListView Name="lvPathConfiguration" DataContext="{StaticResource configurationVM}" ItemsSource="{Binding MusicBasePathList, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate>
<RelativePanel>
<TextBlock Name="tbPath" Text="{Binding Mode=OneWay}" RelativePanel.AlignTopWithPanel="True" VerticalAlignment="Center" Width="400" Margin="20"></TextBlock>
<Button Name="btnRemovePath" x:Uid="btnRemovePath" Content="Remove" RelativePanel.RightOf="tbPath" Margin="10" Height="48"></Button>
</RelativePanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</PivotItem>
The namespace of my ViewModel is imported by
xmlns:applicationVM="using:Crankdesk.CrankHouseControl.ViewModel.Application"
and the Page Resource i added my ViewModel:
<Page.Resources>
<applicationVM:ConfigurationViewModel x:Key="configurationVM"></applicationVM:ConfigurationViewModel>
</Page.Resources>
btnSelectSourcePath should add a path to the list of source pathes that are stored in ViewModel, which will be done in CodeBehind:
private async void btnSelectSourcePath_Click(object sender, RoutedEventArgs e)
{
FolderPicker picker = new Windows.Storage.Pickers.FolderPicker();
picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.MusicLibrary;
picker.FileTypeFilter.Add("*");
StorageFolder folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
// Save path to configuration
App.ConfigurationViewModel.MusicBasePathList.Add(folder.Path);
}
}
In ViewModel the "INotifyPropertyChanged" Event is used and i use the "CollectionChanged" Event of my ObersableCollection to fire the PropertyChanged Event. When i add a path in debug mode, the RaisePropertyChanged Method will be executed, but the "handler" property is always NULL.
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Here is my whole ViewModel:
namespace Crankdesk.CrankHouseControl.ViewModel.Application
{
public class ConfigurationViewModel : INotifyPropertyChanged
{
private ObservableCollection<string> _musicBasePathList;
public ObservableCollection<string> MusicBasePathList
{
get
{
return _musicBasePathList;
}
set
{
_musicBasePathList = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ConfigurationViewModel()
{
_musicBasePathList = new ObservableCollection<string>();
_musicBasePathList.CollectionChanged += _musicBasePathList_CollectionChanged;
}
private void _musicBasePathList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged(nameof(MusicBasePathList));
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
What do i wrong here? I know i ask this question the 34th time here, but i didn't find a solution. In most cases they forgot to specify OneWay or TwoWay, but that's not the case here.
Thanks in advance....
Dave
You have at minimum two instances of ConfigurationViewModel in your application.
App.ConfigurationViewModel
defined in the page ressources as configurationVM
The view is bound to the 2. instance and in code behind you modify the 1. instance.
Related
It is necessary to track the link change in WebView 2. I have the following code in the VM:
VM
private Uri _myHtml;
public Uri MyHtml
{
get { return _myHtml; }
set
{
_myHtml = value;
CheckUri(MyHtml);
OnPropertyChanged();
OnPropertyChanged(nameof(MyHtml));
}
}
VMBASE
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
VIEW XAML
<Wpf:WebView2 Name="webView"
Source="{Binding MyHtml, UpdateSourceTrigger=Explicit}" Grid.RowSpan="2" Grid.ColumnSpan="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
Alas, the breakpoint on "set" is triggered only when the "MyHTML" variable is directly assigned a value. But when you change the URL in WebView2, nothing changes
You have two options
Set the Mode to TwoWay, and remove the UpdateSourceTrigger=Explicit
Source="{Binding MyHtml, Mode=TwoWay}"
If you want to keep Explicit mode, you have to call UpdateSource() to update the bound property (i.e.MyHtml). This can be done by handling NavigationCompleted event like so..
In .xaml
<Wpf:WebView2
Source="{Binding MyHtml, UpdateSourceTrigger=Explicit, Mode=TwoWay}"
NavigationCompleted="WebView_OnNavigationCompleted" ..
In .xaml.cs
private void WebView_OnNavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs args)
{
if (args.IsSuccess)
{
var bindingExpression =
webView.GetBindingExpression(WebView2.SourceProperty);
bindingExpression?.UpdateSource();
}
}
Note that in both options you need Mode=TwoWay.
Basically what i have is a ListBox with ContextMenu
<ListBox Margin="2,0,0,0" Grid.Row="1" ItemsSource="{Binding MyCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Style="{StaticResource NoVisualButton }" Tag="{Binding ID}" Width="430" toolkit:TiltEffect.IsTiltEnabled="True" Margin="0,0,0,12" Click="OnSelectWorkOutItemClick">
<StackPanel>
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
<toolkit:MenuItem Header="delete" Tag="{Binding ID}" Click="onContextMenuDeleteItemClick" IsEnabled="{Binding IsDeleteOptionEnable, ElementName=LayoutRoot}"/>
<toolkit:MenuItem Header="edit" Tag="{Binding ID}" Click="onContextMenuItemEditClick" />
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
...
</StackPanel>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
So if MyCollection has only one item, i have to disable delete MenuItem.
My model has a property
public bool IsDeleteOptionEnable
{
get
{
return MyCollection.Count() >= 2;
}
}
In the page i am setting the DataContext like:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (IsDataLoaded)
{
this.DataContext =MyModel;
}
}
The listbox is getting populated, but i can't disable "delete" MenuItem. What am i doing wrong?
Since the IsDeleteOptionEnable is a regular property, your view won't get notified when the property is changed. On options would be implementing INotifyPropertyChanged in your model (actually that should be ViewModel in an MVVM pattern) and calling the PropertyChanged event whenever items in your collection gets changed.
class YourModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
..
..
public YourModel() {
this.MyCollection = ...;
this.MyCollection.CollectionChanged += MyCollection_CollectionChanged;
}
public bool IsDeleteOptionEnable {
get {
return MyCollection.Count() >= 2;
}
}
private void MyCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
this.OnPropertyChanged("IsDeleteOptionEnable");
}
private void OnPropertyChanged(string name = null) {
if (this.PropertyChanged != null) {
PropertyChangedEventArgs ea = new PropertyChangedEventArgs(name);
this.PropertyChanged(this, ea);
}
}
}
Now when an item get removed or added to the collection, the model raises and PropertyChanged event so that the view will be aware that the IsDeleteOptionEnable property is (actually might) changed, and the enabled state of the button gets updated.
Try
IsEnabled="{Binding DataContext.IsDeleteOptionEnable, ElementName=LayoutRoot}"
As DataSource you need to use ObservableCollection. Then you need to implement INotifyPropertyChanged -interface in the class which contains the binded Property.
Example Class:
// Example of binded object
public class MyItem: INotifyPropertyChanged {
// Binded Property
private String itemIsVisible = "Yes";
public String ItemIsVisible{
get { return itemIsVisible; }
set {
itemIsVisible = value;
// This ensures the updating
OnPropertyChanged("ItemIsVisible");
}
}
protected void OnPropertyChanged(string name) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Example XAML:
<TextBlock Text="{Binding ItemIsVisible}" />
A couple of other posts seem to indicate that data can be shared between a View's code behind and viewmodel by binding the dependency property in the code behind and property in the viemodel. Also, I have read that the DP should be in the code behind when itself is being bound in a Main Window/User Control relationship.
The following is from the code behind (SetupUC)
public static readonly DependencyProperty UC1Property =
DependencyProperty.Register(
"UC1", typeof(string), typeof(SetupUC),
new FrameworkPropertyMetadata()
{
PropertyChangedCallback = OnUC1Changed,
BindsTwoWayByDefault = true
});
public string UC1
{
get { return (string)GetValue(UC1Property); }
set
{
SetValue(UC1Property, value);
}
}
public SetupUC()
{
InitializeComponent();
SetupViewModel svm = new SetupViewModel();
this.DataContext = svm;
Binding binding = new Binding("ViewModelStringProperty") { Source = svm, Mode = BindingMode.TwoWay };
BindingOperations.SetBinding(this, SetupUC.UC1Property, binding);
}
and the viewmodel (SetupViewModel)
private string _viewModelStringProperty;
public string ViewModelStringProperty
{
get { return _viewModelStringProperty; }
set
{
_viewModelStringProperty = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("ViewModelStringProperty"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
In debugging, UC1 seems to always be updated correctly from the Main Window as its changes are reflected in the user control view. However, in the viewmodel, ViewModelStringProperty does not ever seem to be updated - it's always null. Full disclosure(!), the following is how UC1 is bound in the user control XAML
<TextBox x:Name="tbx1" HorizontalAlignment="Left" Height="23" Margin="159,22,0,0" TextWrapping="Wrap" Text="{Binding UC1, ElementName=root}" VerticalAlignment="Top" Width="120" RenderTransformOrigin="0.017,0.304"/>
Again, this part seems to be fine, it's getting data to ViewModelStringProperty in the viewmodel that is not.
To ensure your changes in your TextBox are immediately pushed to the DependencyProperty and subsequently the view-model, ensure you have UpdateSourceTrigger=PropertyChanged on your TextBox.Text binding, i.e.:
Text="{Binding UC1, ElementName=root, UpdateSourceTrigger=PropertyChanged}"
With this, my example worked perfectly fine.
If this doesn't help, I suggest adding two additional TextBlocks, one bound to the UC1 and one bound to ViewModelStringProperty, this will make it easier to tell which are getting updated correctly, e.g.:
<StackPanel>
<TextBox Text="{Binding UC1, ElementName=Root, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{Binding UC1, ElementName=Root}" />
<TextBlock Text="{Binding ViewModelStringProperty}" />
</StackPanel>
I am generating Grid for every item from my ObservableCollection. Now I want to be able to change the source collection at runtime and I am not sure what needs to be done.
Here is my XAML:
<Window.Resources>
<c:GraphicsList x:Key="GraphicsData" />
</Window.Resources>
...
...
<ItemsControl x:Name="icGraphics" ItemsSource="{Binding Source={StaticResource GraphicsData}}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Tag="{Binding id}" Margin="15,0,15,15">
<Label Grid.Row="0" Content="{Binding name}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And C#:
myCollection1 = this.FindResource("GraphicsData") as GraphicsList;
myCollection1:
public class GraphicsList : ObservableCollection<Graphics>
{
public GraphicsList()
{
}
}
Graphics class:
class Graphics: INotifyPropertyChanged
{
// some properties not important
}
Its a simplyfied version of my code, but it works, I basically a want to change the source collection myCollection1 to myCollection2 (which is same class just different list). How do I do this?
You can Add or Remove items from collection as below
var dresource = this.Resources["GraphicsData"] as GraphicsList;
dresource.Add(new Graphics() { Name = "New Entry" });
But with StaticResource you can't assign new Collection to one in ResourceDictionary.
Ideally you should be using ViewModel and bind Collection if you want to assign completely new collection.
Your mainwindow class or viewmodel should implement INotifyPropertyChanged interface
Sample code
public partial class MainWindow : Window, INotifyPropertyChanged
{
private GraphicsList _graphicsData;
public MainWindow()
{
InitializeComponent();
DataContext = this;
this.Loaded += MainWindow_Loaded;
}
public GraphicsList GraphicsData
{
get { return _graphicsData; }
set
{
if (Equals(value, _graphicsData)) return;
_graphicsData = value;
OnPropertyChanged("GraphicsData");
}
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
//var resource = this.Resources["GraphicsData"] as GraphicsList;
var resource = new GraphicsList();
resource.Add(new Graphics(){Name = "Some new Collection of data"});
this.GraphicsData = resource;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And Your Xaml
<Grid>
<ListBox ItemsSource="{Binding GraphicsData}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I hope this will help
I'm trying to display the number of records retrieved by the query after the window loads. Here's what I have in my XAML:
<TextBlock Name="numRecordsAnalyzed_TAtab" TextWrapping="Wrap" Margin="12,0,0,4" Grid.RowSpan="2">
<Run Text="Records Found: " Foreground="{StaticResource Foreground}" FontSize="12"/>
<Run Text="{Binding Numrecords}" Foreground="Red" FontSize="12"/>
</TextBlock>
Here's my c#:
private int numOfrecords = 0;
public event PropertyChangedEventHandler PropertyChanged;
public string Numrecords
{
get { return Convert.ToString(numOfrecords); }
set
{
OnPropertyChanged("NumOfrecords");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
Then I add this to get the number of records and when I debug I see that the variable holds the number and everything but nothing is displayed in the window when the window launches:
numOfrecords = OpenTradesQuery.Count();
What am I missing?
You need to raise PropertyChanged event to notify GUI to update.
Declare property of type int, WPF will automaically call ToString() on your property, you need not to worry about that.
public int Numrecords
{
get { return numOfrecords; }
set
{
if(numOfrecords != value)
{
numOfrecords = value;
OnPropertyChanged("Numrecords");
}
}
}
Set the property:
Numrecords = penTradesQuery.Count();
You can set DataContext in code behind after InitializeComponent() in constructor of Window/UserControl:
DataContext = this;
Also, you can set it in XAML at root level like this:
<Window DataContext="{Binding RelativeSource={RelativeSource Self}}"/>