I am having a problem updating Entity while using WPF MVVM and commands.
My WPF looks like:
<Popup Margin="10,10,0,13" Name="UpdatePopup" HorizontalAlignment="Left" VerticalAlignment="Top" Width="450" Height="100" IsOpen="{Binding IsOpen, Mode=TwoWay}">
<Border Padding="5" Background="WhiteSmoke">
<StackPanel Orientation="Horizontal" DataContext="{Binding CommendationEntity}" Width="450" Height="100">
<Label Content="Nazwa żódła" Margin="10,10,10,10" Width="75" Height="30"/>
<TextBox Text="{Binding Name}" Margin="10,10,10,10" Width="130" Height="30" x:Name="Name" />
<Button Command="{Binding DataContext.UpdateCommand, ElementName=UpdatePopup}" CommandParameter="{Binding id}" Content="Update" Margin="10,10,10,10" Width="80" Height="30"/>
<Button Command="{Binding DataContext.CancelCommand, ElementName=UpdatePopup}" Content="Anuluj" Margin="10,10,10,10" Width="80" Height="30"/>
</StackPanel>
</Border>
</Popup>
Now to update record I need id and new name so I am passing id with button binding, and I am having trouble passing name, my update method looks like:
public void UpdateEntity(object obj)
{
this.CommendationEntity = this._catalog.Commendations.Single(entity => entity.id == (int)obj);
this.CommendationEntity.Name = this.Name;
this._catalog.Commendations.Attach(this.CommendationEntity);
this._catalog.Entry(this.CommendationEntity).State = System.Data.Entity.EntityState.Modified;
this._catalog.SaveChanges();
}
And in the view model I have a property named:
public string Name
{
get { return _name; }
set
{
if (_name == value)
{
return;
}
this._name = value;
RaisePropertyChanged("Name");
}
}
But when I click Update id is passed as (object obj), with is right, but Name property does not update, what could be wrong?
Maybe its data context (DataContext="{Binding CommendationEntity}") as model name and view model property has same name? I'm new WPF so I can be wrong.
Or maybe there is a way to just click button and whole object will be passed as (object obj)?
this.Name indicates that the Name property belongs to the same class as the UpdateCommand property and then the path of the binding to the Name property should be set up the same way:
<TextBox Text="{Binding DataContext.Name, ElementName=UpdatePopup}" Margin="10,10,10,10" Width="130" Height="30" x:Name="Name" />
Otherwise you are binding to some other Name property or the binding simply fails.
Two possibilities:
Is the binding for Name correct? For the commands you are using DataContext.UpdateCommand, ElementName=UpdatePopup so it may be you need to do this as well. If the binding fails you don't get an exception but you should get a message in the output window of VS - worth checking there.
If for some reason in the Name setter: if (_name == value) (perhaps both are emptry strings?) is true then the name won't update.
As per you code you are trying to bind Name property of CommendationEntity with textbox control.It is not getting updated because you have not implemented NotifyPropertyChanged for this.CommendationEntity.Name property. You need to implement INotifyProperty at CommendationEntity to reflect the property changed of CommendationEntity class.
Related
I am working on a Win10 UWP app using MVVMLight (I've never used MVVMLight, and never done commands "properly"). I have an ItemsControl bound to an ObservableCollection. The Participant has two properties - Name and Laps. I have controls in the ItemsControl.ItemTemplate to display a button (subtract), the Name property of the item, the Laps property of the item, and another button (add). Here is the XAML:
<ItemsControl
ItemsSource="{Binding Participants, Mode= TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid
MinWidth="300"
Margin="0, 12">
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="2*"></ColumnDefinition>
<ColumnDefinition
Width="6*"></ColumnDefinition>
<ColumnDefinition
Width="2*"></ColumnDefinition>
<ColumnDefinition
Width="3*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button
Content=""
FontFamily="Segoe MDL2 Assets"
FontSize="20"
Grid.Column="0"
Margin="0, 0, 12, 0"></Button>
<TextBlock
Text="{Binding Path=Name, Mode=TwoWay}"
FontSize="20"
FontWeight="Bold"
Grid.Column="1"></TextBlock>
<TextBlock
Text="{Binding Laps, Mode=TwoWay}"
FontSize="20"
FontWeight="Bold"
Grid.Column="2"></TextBlock>
<Button
Content=""
FontFamily="Segoe MDL2 Assets"
FontSize="20"
Command="{Binding AddLapCommand}"
CommandParameter="{Binding}"
Grid.Column="3"
Margin="12, 0, 0, 0"></Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The view model creates an ObservableCollection called Participants. I am trying to bind the add button (and then of course the subtract button) to a RelayCommand in the VM. I've tried a number of things, so I can't post all I've tried here. Here is the latest of what I have, (but it still doesn't work):
public RelayCommand<object> AddLapCommand
{
get
{
if (_addLapCommand == null)
{
_addLapCommand = new RelayCommand<object>((e) => ExecuteAddLapCommand(e));
}
return _addLapCommand;
}
}
private void ExecuteAddLapCommand(object o)
{
throw new NotImplementedException();
}
The intellisense in the xaml tells me that it cannot resolve the property AddLapCommand in data context of type LapCounter.Model.Participant. I'm not trying to get to the Participant class, but to the HomeViewModel class, which is the DataContext of the page. I think I can see where it is getting the LapCounter.Model.Participant from, from the individual item in the collection that the ItemsControl is bound to. But I was under the impression that if a DataContext couldn't be found, it would continue up the visual tree until it found the right one. Here's the DataContext in the page declaration:
DataContext="{Binding Home, Source={StaticResource Locator}}"
How do I get it to look at the VM for the RelayCommand? What I need to do is have the button send the Participant that it represents to the RelayCommand as a parameter, and use that to increment (and decrement in the case of the subtract button) the Laps integer for that particular Participant.
I appreciate any help I can get. Thanks!
EDIT
Added Participants property from the VM to show because my view is not updating. Here is the Participants property:
/// <summary>
/// The <see cref="Participants" /> property's name.
/// </summary>
public const string ParticipantsPropertyName = "Participants";
private ObservableCollection<Participant> _participants = new ObservableCollection<Participant>();
/// <summary>
/// Sets and gets the Participants property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public ObservableCollection<Participant> Participants
{
get
{
return _participants;
}
set
{
if (_participants == value)
{
return;
}
_participants = value;
RaisePropertyChanged(() => Participants);
}
}
Thanks to Eldar Dordzhiev for his help so far!
Try this:
Command="{Binding Home.AddLapCommand, Source={StaticResource Locator}}"
There's not much to explain to you as long as everything you've written is absolutely right.
As for incrementing\decrementing the Laps, you can simply pass the Participant into the command handler and alter the Laps. If you use MVVMLight that is done with RelayCommand<Participant>. Don't forget to pass the Participant in CommandParameter.
I've got about 20 of the following underneath a ComboBox (which means that I can't use any x:Name parameter due to duplicate naming):
<ComboBoxItem>
<StackPanel Orientation="Horizontal">
<Image Source="folder/someimage.png" Height="20" Width="20"/>
<TextBlock Text="SampleText" Margin="5,0,0,0"/>
</StackPanel>
</ComboBoxItem>
How can I access the string from the TextBlock inside the SelcetionChanged event?
Currently when I choose the item normally, the selected text (of my ComboBox) is set to
System.Windows.Controls.ComboBoxItem
This is the return value from the ComboBoxItem.ToString() method!
How can I change the return type of my xaml based ComboBoxItem?
After long research I've found a solution. I've created a new UserControl and then added it as item of my ComboBox.
MyComboBoxItem.xaml:
<StackPanel Orientation="Horizontal">
<Image x:Name="img" Source="{Binding Image, ElementName=myComboBoxItem}" Height="18" Width="18"/>
<TextBlock x:Name="txt" Text="{Binding Text, ElementName=myComboBoxItem}" Margin="5,0,0,0"/>
</StackPanel>
MyComboBoxItem.xaml.cs:
public string Text
{
get { return txt.Text; }
set { txt.Text = value; }
}
public override string ToString()
{
return Text;
}
MainWindow.xaml:
<ComboBox>
<local:MyComboBoxItem Image="image1.png" Text="Item1"/>
<local:MyComboBoxItem Image="image2.png" Text="Item2"/>
<local:MyComboBoxItem Image="image3.png" Text="Item3"/>
</ComboBox>
Now it works like a charm and does exactly what I've wanted.
I'm not sure of the correct terminology to use. I created a Windows Store app about a year ago and the main page was created by Visual Studio and I never changed it much. It uses a view model that works fine but I don't know enough to fix problems. Anyhow...
The page uses a GridView to display the contents of CollectionViewSource element to reference an ObservableCollection. This all works fine. The DataTemplate for one of the data items looks like this right now:
<DataTemplate x:Key="TopImageTileTemplate">
<Grid MinHeight="135" Width="350" Margin="0" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="135"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Path=ImagePath}" FontSize="33"/>
<usercontrols:WaitingImageControl SourcePath="{Binding Path=ImagePath}" Grid.Row="0" Width="350" Height="165" HorizontalAlignment="Center" VerticalAlignment="Stretch" AutomationProperties.Name="{Binding Title}" Visibility="{Binding TypeDescription, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource TextToVis}}"/>
<usercontrols:WaitingImageControl SourcePath="XXX" Grid.Row="0" Width="350" Height="165" HorizontalAlignment="Center" VerticalAlignment="Stretch" AutomationProperties.Name="{Binding Title}" Visibility="{Binding TypeDescription, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource TextToVis}}"/>
<ProgressRing Opacity="0.5" Foreground="#FF8A57FF" Grid.Row="0" Name="TheProgressControl" IsActive="True" Height="32" Width="32" Background="Transparent" VerticalAlignment="Center" HorizontalAlignment="Center"/>
...
</Grid>
</DataTemplate>
The problem that I have is that the data item for this contains a string called ImagePath that I want to pass into the WaitingImageControl usercontrol and it's not working. The TextBlock works fine and the text displays the ImagePath string just fine. The second WaitingImageControl works fine and the code that handle SourcePath does get passed the "XXX" just fine too. But the first WaitingImageControl never gets passed the ImagePath value from the data item.
This is some sort of binding issue and I know so little about binding that I'm to even sure what to try (or what to show in this question). given that the TextBlock binding works and the second WaitingImageControl binding works, I'm at a loss.
Here's the WaitingImageControl code for the SourcePath property:
public static readonly DependencyProperty SourcePathProperty =
DependencyProperty.Register("SourcePath", typeof(string), typeof(WaitingImageControl), new PropertyMetadata(""));
public string SourcePath
{
get { return m_SourcePath; }
set
{
if( string.IsNullOrEmpty( value ) )
return;
m_SourcePath = value;
ResourcesStore Store = new ResourcesStore();
if( Store.Count() == 0 )
{
var IgnoreMe = CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( CoreDispatcherPriority.Normal, () =>
{
// No progress and no image...
TheProgressControl.Visibility = Visibility.Collapsed;
TheImage.Visibility = Visibility.Collapsed;
} );
return;
}
ResourceItem Item = Store.getItemByFilename( m_SourcePath );
LocalInboxService.Instance.InboxStatusChanged -= InboxStatusChanged;
InboxStatusChanged( null );
LocalInboxService.Instance.InboxStatusChanged += InboxStatusChanged;
}
}
The code is supposed to show the Image element and hide the ProgressRing element when the image has been downloaded.
And the code for the data item, which again, works just fine when the ImagePath is passed automatically to the TextBlock:
public string ImagePath
{
get
{
return this._imagePath;
}
set
{
this._imagePath = value;
this.SetProperty(ref this._imagePath, value);
}
}
Any help is appreciated making the ImagePath to SourcePath binding (below) work:
<usercontrols:WaitingImageControl SourcePath="{Binding Path=ImagePath}"
Grid.Row="0" Width="350" Height="165" HorizontalAlignment="Center"
VerticalAlignment="Stretch" AutomationProperties.Name="{Binding Title}"
Visibility="{Binding TypeDescription, RelativeSource={RelativeSource
Mode=TemplatedParent}, Converter={StaticResource TextToVis}}"/>
After hours of searching, I found a StackOverflow answer to a similar question. The answer was to add a PropertyChanged function to the Propertymetadata. I'm not sure yet what this actually means or why it is only needed here, but it works properly:
public static readonly DependencyProperty SourceImageResourceIdProperty =
DependencyProperty.Register("SourceImageResourceId", typeof(string), typeof(WaitingImageControl), new PropertyMetadata( string.Empty, OnSourcePathPropertyChanged ));
private static void OnSourcePathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as WaitingImageControl).SourceImageResourceId = e.NewValue.ToString();
}
The OnSourcePathPropertyChanged function gets called and the property gets set like it should.
Now I just hope that it wasn't one of the twenty other experiments that actualy fixed this!
I cannot find any examples to make me understand how and if I can change the databind in c# at the click of a button on, in my case a toggleswitch, Basically I have 32 buttons in my app and those 32 buttons act the same but need different text with-in them depending on some toggle switches they are currently databinded so the text can be saved and retrieved from local storage but what values it gets depends on the state of these toggle switches.
So I currently have :
<Button x:Name="_ovButton1" Content="{Binding Source={StaticResource AppSettings}, Path=ovName1_1Value, Mode=TwoWay}" Margin="2,0,250,0" VerticalAlignment="Top" FontSize="14" Height="72" FontWeight="Bold" MouseLeftButtonUp="_ovButton1_MouseLeftButtonUp" MouseLeftButtonDown="_ovButton1_MouseLeftButtonDown" ClickMode="Hover" Hold="_ovButton1_Hold"/>
and I want when a user changes the state of a toggleswitch to change the
{StaticResource AppSettings}, Path=ovName1_1Value, Mode=TwoWay}
to for example:
{StaticResource AppSettings}, Path=ovName1_2Value, Mode=TwoWay}
but I cannot find any example that shows how to do that in c#
what code do I need to do that?
You can specify the target of databinding in code like this:
MyData myDataObject = new MyData(DateTime.Now);
Binding myBinding = new Binding("MyDataProperty");
myBinding.Source = myDataObject;
myText.SetBinding(TextBlock.TextProperty, myBinding);
See more at http://msdn.microsoft.com/en-us/library/ms742863.aspx
-- Edit Note I don't have access to a WP8 Emulator to test this ---
In the view model it looks like this:
public List<string> Members
{
get { return _Members; }
set { _Members = value; OnPropertyChanged(); }
}
public MainVM()
{
// Simulate Asychronous access, such as to a db.
Task.Run(() =>
{
Thread.Sleep(2000);
Members = new List<string>() {"Alpha", "Beta", "Gamma", "Omega"};
});
}
The code behind on the main page sets the datacontext (shared with all the child controls) as such:
public MainWindow()
{
InitializeComponent();
// Set the windows data context so all controls can have it.
DataContext = new MainVM();
}
The Mainpage Xaml to bind to members is like this
<Button Height="30"
Width="80"
Margin="10"
DataContext="{Binding Members}"
Content="{Binding Path=[0] }" />
<Button Height="30"
Width="80"
Margin="10"
DataContext="{Binding Members}"
Content="{Binding Path=[1] }" />
<Button Height="30"
Width="80"
Margin="10"
DataContext="{Binding Members}"
Content="{Binding Path=[2] }" />
<Button Height="30"
Width="80"
Margin="10"
DataContext="{Binding Members}"
Content="{Binding Path=[3] }" />
The result is this visually:
I based this on my blog article Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding for more info and a fuller example.
I think your best bet is going to be to use a collection of strings and bind to that collection. You can either change the collection when a toggle is switched, or keep 6 collections and bind to the collection that is for the toggle.
Xaml:
<ItemsControl x:Name="Buttons" ItemsSource="{Binding ButtonTextCollection}">
<ItemsControl.ItemsPanel>
<toolkit:WrapPanel/>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="100" Height="70" Content="{Binding}" Click="OnButtonClick"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Your code-behind would have the event handler for your button click
private void OnButtonClick(object sender, RoutedEventArgs e)
{
var text = ((Button) sender).Content.ToString();
// Send the text
}
Your ViewModel would hold the ButtonTextCollection property and would change based on the toggle.
public ICollection<string> ButtonTextCollection
{
get { return _buttonTextCollection; }
set
{
_buttonTextCollection = value;
OnPropertyChanged("ButtonTextCollection");
}
}
When you want to change the text, you would change the ButtonTextCollection
public void ChangeButtonText()
{
ButtonTextCollection = new Collection<string> {"A", "B",...};
}
I have a User Control,called dCB_Props that contains several objects, most importantly a ComboBox that's bound to an Observable Collection. Though the collection can take any object, it will normally take a UserControl called EditDeleteItem. I've set dCB_Props to use EditDeleteItem as an ItemsTemplate but the events aren't fired. If, on the other hand, I add an instance of EditDeleteItem then the events will get fired. I can't add items this way because the EditDeleteItem will host other controls and I'd need to use different DataTemplates.
EditDeleteItem has two Routed Events called EditClick and DeleteClick.
When the collection changes it fires an event that checks if the item added is of type EditDeleteItem. If so, then it adds handlers to the two aforementioned events.
Part of the xaml for EditDeleteClick:
<WrapPanel x:Name="wp" HorizontalAlignment="Right" Visibility="Hidden" VerticalAlignment="Center" Margin="0,0,5,0">
<Button x:Name="PART_Edit" Width="20" Height="20" Content="{DynamicResource dPen}" Style="{DynamicResource dTranspButton}" Click="btnEdit_Click"/>
<Button x:Name="PART_Delete" Width="20" Height="20" Content="{DynamicResource dCross}" Style="{DynamicResource dTranspButton}" Click="btnDelete_Click"/>
</WrapPanel>
<Label Content="{TemplateBinding Content}" Margin="2,0,45,0" Padding="0,0,0,0" HorizontalAlignment="Left" VerticalContentAlignment="Center"/>
Part of the xaml for dCB_Props:
<ComboBox HorizontalContentAlignment="Stretch" x:Name="PART_cb" Background="Transparent" Margin="0,0,0.367,0" d:LayoutOverrides="HorizontalAlignment" ItemsSource="{Binding Items, ElementName=dcb}" IsDropDownOpen="{Binding IsDropDownOpen,ElementName=dcb, Mode=TwoWay}" Grid.ColumnSpan="3" Style="{DynamicResource DaisyComboBox}" />
<Button x:Name="PART_Edit" Width="20" Height="20" Content="{DynamicResource dPen}" Visibility="Hidden" Style="{DynamicResource dTranspButton}" Margin="2.581,1.48,17.778,-1.48" Grid.Column="1" Click="btnEdit_Click"/>
<Button x:Name="PART_Delete" Width="20" Height="20" Content="{DynamicResource dCross}" Visibility="Hidden" Margin="22.602,1.48,-2.243,-1.48" Style="{DynamicResource dTranspButton}" Grid.Column="1" Click="btnDelete_Click"/>
<Button x:Name="PART_Add" Content="+" Grid.Column="3" Margin="0,0,0,0" Style="{DynamicResource dTranspButton}" Click="btnAdd_Click"/>
Note the above two are codes just for objects, I've left out Column Definitions, Event Triggers, etc.
Part of dCB_Props.xaml.cs code is:
public partial class dCB_Props : UserControl
{
public dCB_Props()
{
this.InitializeComponent();
Items= new ObservableCollection<object>();
Items.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Items_CollectionChanged);
}
void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
foreach (var o in e.NewItems)
{
if (o.GetType() == typeof(EditDeleteItem))
{
EditDeleteItem itm = (EditDeleteItem)o;
itm.EditClick += new RoutedEventHandler(ItemEdit_Click);
itm.DeleteClick += new RoutedEventHandler(ItemDelete_Click);
}
}
}
}
...//I've left some code here since I don't deem it's that important for the situation
private void ItemEdit_Click(object sender, RoutedEventArgs e)
{
DependencyObject d = GetTemplateChild("PART_cb");
if (d == null) return;
ComboBox cb = (ComboBox)d;
if (cb.SelectedItem != null) RaiseEvent(new RoutedEventArgs(EditClickEvent, e.OriginalSource));
}
}
The above works if I add an item of type EditDeleteItem and remove the ItemTemplate property for the Label that resides inside dCB_Props. It also works if I set the ItemTemplate, shown below, in EditDeleteItem's ContentTemplate. But, as mentioned, I need to use different Data Templates so I assume all Data Templates will have to reside in a Resource Dictionary and then I'd have to use a Template Selector.
Data Template:
<DataTemplate x:Shared="false" x:Key="TagTemplate">
<local:EditDeleteItem x:Name="edItem">
<local:EditDeleteItem.Content>
<StackPanel>
<TextBlock Text="{Binding Path=Content.Label}"/>
<CheckBox Content="Isolated" IsChecked="{Binding Content.IsIsolated}"/>
<CheckBox Content="Match Case" IsChecked="{Binding Content.MatchCase}"/>
<CheckBox Content="Include" IsChecked="{Binding Content.Include}"/>
</StackPanel>
</local:EditDeleteItem.Content>
</local:EditDeleteItem>
</DataTemplate>
I believe I need to use command bindings. But not really sure where to put the CommandBindings, and not so sure how to use them, though I've read a page or two.
Thanks,
Hassan
The events are fired, but you don't catch them, because subscription in Items_CollectionChanged never occurs if ItemTemplate is used.
You should understand how ItemsControl (and ComboBox) works with ItemsSource. ItemsControl use ItemContainerGenerator to populate its visual tree. Each item from ItemsSource wrap into container which derived from ContentControl. Then item is set as a Content, ItemTemplate is set as ContentTemplate and so on. When you put EditDeleteItem into ItemTemplate it becomes a part of visual tree but not an item. That's why there is no EditDeleteItem in e.NewItems and no subscription.
The right way is Commands, as you mentioned. You should declare two commands:
public class EditDeleteItem : UserControl
{
...
public static readonly RoutedUICommand EditCommand = new RoutedUICommand(...);
public static readonly RoutedUICommand DeleteCommand = new RoutedUICommand(...);
...
}
Now the part of template may look like:
<WrapPanel ...>
<Button ... Command="{x:Static EditDeleteItem.EditCommand}"/>
<Button ... Command="{x:Static EditDeleteItem.DeleteCommand}"/>
</WrapPanel>
Then you add command bindings to dCB_Props:
public partial class dCB_Props : UserControl
{
static dCB_Props()
{
...
CommandManager.RegisterClassCommandBinding(
typeof(dCB_Props),
new CommandBinding(EditDeleteItem.EditCommand, OnEditCommandExecuted));
CommandManager.RegisterClassCommandBinding(
typeof(dCB_Props),
new CommandBinding(EditDeleteItem.DeleteCommand, OnDeleteCommandExecuted));
...
}
...
}
You need to implement OnEditCommandExecuted and OnDeleteCommandExecuted in order to handle corresponding commands from EditDeleteItem.
I hope I understood your question correctly ;)