-wpf canvas events firing only for first time - c#

I noticed a strange bug. When i bind a command to canvas MouseLeftButtonDown event it does not fire. I tried to debug and noticed that it fires, but only during initialization. I guess the crux is in binding. Here's the code:
<ItemsControl Grid.Row="1" ItemsSource="{Binding Polygons}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas>
<i:Interaction.Behaviors>
<behaviours:MouseBehaviour MouseX="{Binding MouseX, Mode=OneWayToSource}" MouseY="{Binding MouseY, Mode=OneWayToSource}" />
</i:Interaction.Behaviors>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<command:EventToCommand Command="{Binding SelectPointCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
/* some data template
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And command implementation:
public ICommand SelectPointCommand
{
get
{
if (!CanEdit)
return new RelayCommand(e => { });
ClickCounter++;
if (ClickCounter == 3)
{
ClickCounter = 0;
CanEdit = false;
}
return new RelayCommand(
() =>
{
Polygons.Add(new Polygon(ClickedPoints));
ClickedPoints.Clear();
});
}
}
I guessed problem here is in MouseBehaviour but deleting this piece of code also didnt help.
ps : I tried setting canvas Background property and it didnt work.
As well as setting command to this
SelectPointCommand = new RelayCommand(
() =>
{
System.Windows.MessageBox.Show("Test");
},
() => true);
EDIT
I tried to call method like this :
<Canvas Background="Transparent" MouseLeftButtonDown="UIElement_OnMouseLeftButtonDown">
</Canvas>
And the code behind:
private void UIElement_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
((MainViewModel)DataContext).SelectPointCommand.Execute(e);
}
Method UIElement_OnMouseLeftButtonDown isn't invoked anyway;
Changing Canvas to StackPanel had same result.

It is hard to check what is wrong as you don't post all your code. CanEdit property is changed in some other places? What is ClickCounter for?
I think the problem is with getter of SelectPointCommand. It is executed only once, at the moment of creating binding. I would also make use of CanExecute method of ICommand and store returned value of getter in private field. for example:
private ICommand _selectPointCommand;
ICommand SelectPointCommand
{
get
{
Console.WriteLine("This is executed once");
return _selectPointCommand;
}
set
{
if (_selectPointCommand != value)
{
_selectPointCommand = value;
OnPropertyChanged("SelectPointCommand");
}
}
}
In constructor of ViewModel:
SelectPointCommand = new RelayCommand(
(x) =>
{
Console.WriteLine("This is executed every click");
ClickCounter++;
if (ClickCounter == 3)
{
ClickCounter = 0;
CanEdit = false;
}
Polygons.Add(new Polygon(ClickedPoints));
ClickedPoints.Clear();
},
(x) => { return CanEdit; });

Okay guys, i fixed this bug. The problem was here:
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
Setting row's Height to "Auto" meant setting it to zero. So canvas just didn't exist! I left it like this :
<RowDefinition/>
After that everything worked just fine.

Related

How do I add the command binding to a dynamic button?

After extensive researching I have not found an answer to this problem. I have a list box whose ItemsSource is a collection of Button objects. When I add a button to the collection it appears properly but when clicked the command is not executed. I have already implemented RelayCommand and it is used throughout my code.
C# MVVM WPF
The View
<ListBox ItemsSource="{Binding Buttons}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Margin="5,5,5,5"
Content="{Binding Content}"
Command="{Binding ExecuteButtonCommand}"
CommandParameter="{Binding CommandParameter}"
/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The ViewModel
public RelayCommand _executeButtonCommand;
public ICommand ExecuteButtonCommand
{
get
{
if (_executeButtonCommand == null)
_executeButtonCommand = new RelayCommand(exec => this.ButtonCommands(param));
return _executeButtonCommand;
}
}
For Testing I have this code.
public void AddButtons()
{
Buttons= new ObservableCollection<Button>();
Button btn = new Button();
btn.Content = "Generate Files";
btn.Command = "{Binding ExecuteButtonCommand}";
btn.CommandParameter = "Files";
Buttons.Add(btn);
}
But I cannot assign the Command that way. The rest of the button works correctly. So I put the Command= in the view as you see above.
If this has been answered, then I can't find it. The nearest answer is nine years old and does not work.
Thanks for looking.
What is happening is that the ListBox's DataTemplate is trying to bind to a property called ExecuteButtonCommand which doesn't exist in Button object. And then, to bind the parameter, you need to point to your view's DataContext.
Change it to:
<ListBox.ItemTemplate>
<DataTemplate>
<Button Margin="5,5,5,5"
Content="{Binding Content}"
Command="{Binding Command}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=DataContext.MyParameter}"
/>
</DataTemplate>
</ListBox.ItemTemplate>
For clarification, I created a property called "MyParameter" in my ViewModel. Also, in your codebehind, change your button creation code to:
Buttons = new ObservableCollection<Button>();
Button btn = new Button();
btn.Content = "Generate Files";
btn.Command = ExecuteButtonCommand;
Buttons.Add(btn);
And your ExecuteButtonCommand to simply:
public ICommand ExecuteButtonCommand
{
get
{
if (_executeButtonCommand == null)
_executeButtonCommand = new RelayCommand(ButtonCommands);
return _executeButtonCommand;
}
}
I wanted to close this out with the final result in case someone else is searching for the same answer.
Mari set me straight which led to this example below as the final result. There is no "Code Behind." Generation of the buttons is done in the view model. After a button is created it is added to the button collection which is the source for the ListBox. I am only including the code specific to the question.
This is how it ended up.
The View
<ListBox ItemsSource="{Binding Buttons, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="AliceBlue"
BorderBrush="Transparent"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectedItem="">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Button Margin="5,5,5,5"
Content="{Binding Content}"
Command="{Binding Command}"
CommandParameter="{Binding CommandParameter}"
/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The ViewModel - A switch statement is used to determine what button needs to be generated. I gave the button a name because I wanted to be able to find it in the collection and set the Enabled property. But that didn't work and I still haven't found an answer.
public void AddButton(string param)
{
Button btn = new Button();
switch (param)
{
case "Files":
btn.Content = "Do Files";
btn.CommandParameter = "Files";
btn.Name = "Files";
break;
//More items here
}
btn.Command = ExecuteButtonCommand; //The ICommand name. I made this harder than it needed to be!
Buttons.Add(btn);
}
public RelayCommand _executeButtonCommand;
public ICommand ExecuteButtonCommand
{
get
{
if (_executeButtonCommand == null)
_executeButtonCommand = new RelayCommand(param => this.ButtonCommands(param));
return _executeButtonCommand;
}
}
I hope that can help someone.

Binding property of usercontrol to data

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!

Eventhandler for WPF control in DataTemplate

I've been working with WPF and I have experienced a problem related with DataTemplates.
I have a view called DetailPage.xaml and this view uses a DataTemplate called Detail.xaml. I added a textbox to this DataTemplate and I want to handle the TextChanged event. So I made something like this:
<DataTemplate x:Name="DetailContent">
<Grid Margin="5" DataContext="{Binding Items[0]}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition MaxHeight="80"/>
</Grid.RowDefinitions>
<StackPanel Width="432">
<TextBox Name="NumeroParadaTB" Text="{Binding NumeroParada}" MaxLength="5" TextChanged="NumeroParadaTB_TextChanged" />
</StackPanel>
</Grid>
</DataTemplate>
Then I created and event handler in DetailPage.xaml.cs, like the following:
protected async void NumeroParadaTB_TextChanged(object sender, TextChangedEventArgs e)
{
string nroParada = ((TextBox)sender).Text;
if(!string.IsNullOrEmpty(nroParada) && nroParada.IsDigitsOnly() && nroParada.Length == 5)
{
}
}
But when running, and error is thrown saying that the event handler doesn't exist. I guess I'm using the eventhandler in a wrong way.
Thanks!
Since you're using data binding, I assume, that you have some class with NumeroParada property:
public class SomeClass : INotifyPropertyChanged
{
/* other code here */
public string NumeroParada
{
get { return numeroParada; }
set
{
if (numeroParada != value)
{
numeroParada = value;
OnPropertyChanged("NumeroParada");
}
}
}
private string numeroParada;
}
Setter of this property will fire, when UI will update the binding source. This is your "TextChanged" event.
Note, that by default, TextBox updates Text property, when loosing focus. If you want to perform any action when user changes text, update your binding definition:
Text="{Binding NumeroParada, UpdateSourceTrigger=PropertyChanged}"
So far so good. But this code:
if(!string.IsNullOrEmpty(nroParada) && nroParada.IsDigitsOnly() && nroParada.Length == 5)
suggests, that you're trying to implement validation of value, entered by user.
Validation in WPF is rather big theme, I'd recommend you to read something like this to select validation approach.
Instead of adding an Event handler, you can use Event to Command logic. Create a Command in ViewModel and bind it to the TextChanged event.
<TextBox Text="{Binding SearchText, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding MyCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
Interaction triggers available in System.Windows.Interactivity assembly.

Data binding in Windows Phone 8 app using ListBox

Here is my code. What it doing. I have one textbox(button) with text Book name, when I click on it I can change text in textbox using binding. But now I add another textbox for author name and I dont know how bind it. If I use same method like for Book name its not works or text from book name is too in book author. All text are changed via popup setting page.
My source here: https://dl.dropbox.com/u/40039421/App1.rar.
Image here: https://dl.dropbox.com/u/40039421/helpp.png
public partial class MainPage : INotifyPropertyChanged
{
private ObservableCollection<Book> _books = new ObservableCollection<Book>();
public ObservableCollection<Book> AllBooks
{
get { return _books; }
set { _books = value; }
}
...
private void InitializeData()
{
var bookName1 = new Book { Id = 1, BookName = "Small red apple",AuthorName = "Daniel"};
...
AllBooks.Add(bookName1);
...
OnPropertyChanged("AllBooks");
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(null, new PropertyChangedEventArgs(propertyName));
}
}
private void btnGreenBook_Click(object sender, RoutedEventArgs e)
{
var button = sender as Button;
if (button != null)
{
Popup popup = new Popup();
popup.VerticalOffset = 30;
PopupSettingPage control = new PopupSettingPage();
popup.Child = control;
popup.IsOpen = true;
control.btnSave.Click += (s, args) =>
{
var id = Convert.ToInt32(button.Tag);
var book = AllBooks.FirstOrDefault(sub => sub.Id == id);
if (book != null)
{
book.BookName = control.BookName;
book.OnPropertyChanged("BookName");
}
popup.IsOpen = false;
};
...
Ohh dear, it was a simple mistake :)
You forgot to add the AuthorName in the Xaml of your PopupSettingsPage.xaml
<TextBox x:Name="tbInputAuthorName" Text="{Binding AuthorName, Mode=TwoWay}" VerticalAlignment="Center"/>
And then in MainPage do this
book.BookName = control.BookName;
book.OnPropertyChanged("BookName");
book.AuthorName = control.AuthorName;
book.OnPropertyChanged("AuthorName");
Additional answer based on your comments:
In order to achieve that, You have to remove the second stackpanel in the listbox. Instead, use the WrapPanel control. Search for how to use WrapPanel for WindowsPhone.
And then you have to find some way to set the backgrounds as red or green. Good luck
OK I use wrap panel but problem is still here. I have two panorama items (I post code from item 1 and item 2 have same code only is showing on second panorama page) and problem is still if I save some value to one of item in panorama item 1 this value is too in panorama item 2 and I dont know how make it. Logic is click on panorama 1 item change values in panoam 1 items, click on panorama 2 items change panorama 2 items values. Its look like panoram item 1 have same ID like panoram item 2. Here is full source : https://dl.dropbox.com/u/40039421/App1SecondEdit.rar
<Grid x:Name="LayoutRoot" Background="Transparent">
<!--Panorama control-->
<phone:Panorama>
<!--Panorama item 1-->
<phone:PanoramaItem Header="Test">
<Grid x:Name="PanelPanoramaItem1"
Grid.Row="1"
Margin="25,0,12,0">
<ListBox ItemsSource="{Binding AllBooks}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Button Width="140"
Height="140"
toolkit:TiltEffect.IsTiltEnabled="True" Margin="0,0,0,5" Click="Button_Click_1" Tag="{Binding Id}" >
<Button.Template>
<ControlTemplate>
<Grid Background="Chartreuse">
<TextBlock Foreground="Black" Text="{Binding BookName}" />
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</phone:PanoramaItem>
<!-- END Panorama item 1 -->

Hide listview header programmatically

I have a ListView like:
Col1 Col2 Col3
1 A I
2 B II
3 C III
I use 2 buttons. When I click on the first button the Col3 should collapse and it should be visible when a click in the second button.
Any idea on how to do such a ListView in WPF?
Use of Thumb will solve the problem.
Just as
<ListView x:Name="MyListView"IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Items}", Mode=Default,
Source={StaticResource DataProvider}}"
Thumb.DragDelta="Thumb_DragDelta">
public Window1()
{
InitializeComponent();
MyListView.AddHandler(Thumb.DragDeltaEvent,
new DragDeltaEventHandler(Thumb_DragDelta),
true );
}
void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
Thumb senderAsThumb = e.OriginalSource as Thumb;
GridViewColumnHeader header = senderAsThumb.TemplatedParent as GridViewColumnHeader;
if (header.Column.ActualWidth < MIN_WIDTH)
{
header.Column.Width = MIN_WIDTH;
}
if (header.Column.ActualWidth > MAX_WIDTH)
{
header.Column.Width = MAX_WIDTH;
}
}
Could you provide some xaml-code of what your listview looks like?
You could bind a RelayCommand to your button and pass the ListView as a parameter. Then you could set Visibility = False.
<Button Command="{Binding MyButtonCommand}
CommandParameter="{Binding ElementName=Col3}" />
This would be your cs:
ICommand _myButtonCommand;
public ICommand MyButtonCommand
{
get
{
if (_myButtonCommand== null) _myButtonCommand= new RelayCommand(param => HideList(param ));
return _myButtonCommand;
}
}
void HideList(object param){
(param as ListView).Visibility = False;
}
I'm talking about RelayCommand as in Josh Smith's example: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
you can dl the code there.
I guess you could achieve a similar result in xaml only with triggers, however I'm not as experienced with that subject.
I am using the code as
<Grid>
<ListView HorizontalContentAlignment="Stretch" Margin="38,12,31,110">
<ListView.View>
<GridView>
<GridViewColumn Header="COL1" Width="100"/>
<GridViewColumn Header="COL2" Width="100"/>
<GridViewColumn Header="COL3" Width="100"/>
</GridView>
</ListView.View>
</ListView>
<Button Height="25" HorizontalAlignment="Left" Margin="105,0,0,51"
Name="Collapse" VerticalAlignment="Bottom" Width="75"
Command="{Binding MyButtonCommand}"
CommandParameter="{Binding ElementName = COL3}">Collapse</Button>
<Button Height="25" HorizontalAlignment="Right" Margin="0,0,111,51" Name="Expand"
VerticalAlignment="Bottom" Width="75">Expand</Button>
</Grid>
and in CS
ICommand _myButtonCommand;
public ICommand MyButtonCommand
{
get
{
if (_myButtonCommand== null) _myButtonCommand = new RelayCommand(param => HideList(param ));
return _myButtonCommand;
}
}
void HideList( object param )
{
( param as ListView ).Visibility = Visibility.Hidden;
}
Can u give me a better idea?
I'd have put this answer as a comment of your post, but I'm not able to comment yet, so...
You have to name (use the "Name" Property) the element that you want to access via "Binding ElementName" or you won't be able to get it. In your case you should explicitly create the GridViewColumnHeader, because GridViewColumn has no Visibilty property:
<GridViewColumnHeader Name="COL3">COL3</GridViewColumnHeader>
You probably also have to explicitly create the content of your GridViewColumn if you want to make it disappear, though. This means you have to use GridViewColumn.DisplayMemberBinding or GridViewColumn.CellTemplate and give those a Name as well or access them via RelativeSource.
Have a look at this for the possibilities: http://www.wpfwiki.com/Default.aspx?Page=WPF%20Q5.3&AspxAutoDetectCookieSupport=1
However, have you thought about using an expander, yet?

Categories

Resources