I have this assignment where I must write an interactive program where the user clicks the screen and puts a dot at the spot of his mouse click and then when he puts a second dot they must connect with a line.
<Window x:Class="courseWorkOOP.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:courseWorkOOP"
mc:Ignorable="d"
Title="Практикум ООП" Height="600" Width="800">
<Grid x:Name="myGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Canvas Name="myCanvas" Height="480" Width="640" MouseDown="Canvas_MouseDown_1" MouseMove="Canvas_MouseMove_1">
<Canvas.Background>
<SolidColorBrush Color="White" Opacity="0"/>
</Canvas.Background>
</Canvas>
<Button Click="btn_Click" HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="200" Content="Clear"/>
<!--<StackPanel HorizontalAlignment="Left">
<Button Name="btn" Click="btn_Click">Clear</Button>
</StackPanel>-->
</Grid>
This is my XAML
private void Canvas_MouseDown_1(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (e.ButtonState == MouseButtonState.Pressed)
{
currentPoint = e.GetPosition(this);
Ellipse currDot = new Ellipse() { Width = 10, Height = 10,Fill=Brushes.Black };
myCanvas.Children.Add(currDot);
Canvas.SetLeft(currDot, e.GetPosition(this).X);
Canvas.SetTop(currDot, e.GetPosition(this).Y);
double coordinateX = Canvas.GetLeft(currDot);
double coordinateY = Canvas.GetTop(currDot);
Line myLine = new Line() { X1 = coordinateX, Y1 = coordinateY,X2=coordinateY,Y2=coordinateX,Stroke=Brushes.Green,StrokeThickness=4 };
myCanvas.Children.Add(myLine);
}
}
private void btn_Click(object sender, RoutedEventArgs e)
{
myCanvas.Children.Clear();
}
private void Canvas_MouseMove_1(object sender, System.Windows.Input.MouseEventArgs e)
{
//if (e.LeftButton == MouseButtonState.Pressed)
//{
// Line line = new Line();
// line.Stroke = SystemColors.WindowFrameBrush;
// line.StrokeThickness = 20;
// line.X1 = currentPoint.X;
// line.Y1 = currentPoint.Y;
// line.X2 = e.GetPosition(this).X;
// line.Y2 = e.GetPosition(this).Y;
// currentPoint = e.GetPosition(this);
// myCanvas.Children.Add(line);
//}
}
And this is the C# part.
My question is how do I get two dots to connect with a line?
I've tried ClickCount but it did nothing, I might've used it incorrectly.
Before that I've tried initializing an integer value inside Canvas_MouseDown_1 and then made an If statement that basically said draw a line between this and that every two clicks but that didn't work either.
In order to connect the dots with a line, you need to keep track of the previous click point, so that you can connect the "currentPoint" to the "previousPoint". You also need a flag to only create the line once you have at least one point on the canvas.
private Point previousPoint;
private Point currentPoint;
private bool hasPoints;
private void Canvas_MouseDown_1(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (e.ButtonState == MouseButtonState.Pressed)
{
previousPoint = currentPoint;
currentPoint = e.GetPosition(this.myCanvas);
currentPoint.X -= 5; // Use 5, which is half the width/height of the dot
currentPoint.Y -= 5; // Use 5, which is half the width/height of the dot
Ellipse currDot = new Ellipse() { Width = 10, Height = 10, Fill = Brushes.Black };
myCanvas.Children.Add(currDot);
Canvas.SetLeft(currDot, currentPoint.X);
Canvas.SetTop(currDot, currentPoint.Y);
if (hasPoints)
{
// Add 4 to the line position, due to the stroke thickness being 4
Line myLine = new Line() { X1 = previousPoint.X + 4, Y1 = previousPoint.Y + 4, X2 = currentPoint.X + 4, Y2 = currentPoint.Y + 4, Stroke = Brushes.Green, StrokeThickness = 4 };
myCanvas.Children.Add(myLine);
}
hasPoints = true;
}
}
Example output:
EDITED AFTER COMMENT BELOW: If you want every two dots connecting, just make this simple logical change:
if (hasPoints)
{
// Add 4 to the line position...
Line myLine = new Line() { X1 ...
myCanvas.Children.Add(myLine);
hasPoints = false;
}
else
{
hasPoints = true;
}
Output from above code:
Your approach is fundamentally wrong. Before writing a WPF program you should study the documentation, and especially the data binding section. Unfortunately, the Microsoft-provided documentation isn't super great, so you should also read what you can find here and elsewhere on the web about the primary design pattern used for WPF programs, MVVM.
If you follow the MVVM pattern, your program will be simpler to write and simpler to understand, because you won't be struggling to figure out all your UI interactions at the same time that you're also struggling to figure out all the underlying data. MVVM separates these concerns ("separation of concerns" being one of the most important software practices in any context). Here is an example of what that might look like for your example…
First, you know you will have two kinds of graphical objects, so create models for those:
class CanvasItemViewModel
{
public Point Location { get; }
public CanvasItemViewModel(Point location)
{
Location = location;
}
}
class PointViewModel : CanvasItemViewModel
{
public PointViewModel(Point location) : base(location) { }
}
class LineViewModel : CanvasItemViewModel
{
public double X2 { get; }
public double Y2 { get; }
public LineViewModel(PointViewModel start, PointViewModel end) : base(start.Location)
{
X2 = end.Location.X - start.Location.X;
Y2 = end.Location.Y - start.Location.Y;
}
}
In this case, I know ahead of time that I'm going to want to be able to treat points and lines the same, when it comes to positioning them in the canvas, so I use a common base class to represent where on the canvas they will be.
Since the line will be positioned via its Location property, I can leave the X1 and Y1 properties out, and just set the X2 and Y2, based on the points the line will connect.
With these elements taken care of, now I need a way to manage the points and lines. That looks like this:
class MainViewModel
{
public CompositeCollection CanvasItems { get; } = new CompositeCollection();
public MainViewModel()
{
CanvasItems.Add(new CollectionContainer { Collection = _lines });
CanvasItems.Add(new CollectionContainer { Collection = _points });
}
private readonly ObservableCollection<PointViewModel> _points = new ObservableCollection<PointViewModel>();
private readonly ObservableCollection<LineViewModel> _lines = new ObservableCollection<LineViewModel>();
public void AddPoint(Point point)
{
PointViewModel pointModel = new PointViewModel(point);
_points.Add(pointModel);
if (_points.Count > 1)
{
_lines.Add(new LineViewModel(_points[_points.Count - 2], pointModel));
}
}
public void Clear()
{
_points.Clear();
_lines.Clear();
}
}
Here again, I know that I want to display the points and lines in the same control, so the collections are combined into a single CompositeCollection for the benefit of the canvas that will be displaying them. I maintain two separate collections though, to make it easier to manage the collections in the model code.
The line collection is included first in the composite collection, so that the points will draw on top of the lines. Of course, if you want to see the entire line on top of the points, you would simply swap the order of the two collections in the composite collection.
Observable collections are used because the collection contents will be changing based on user input, and this allows WPF to be notified and respond as needed automatically, without additional work on your part. I.e. this is a fundamental aspect of data binding (along with implementing INotifyPropertyChanged, something that's not actually necessary in this example, but which is used heavily in a typical WPF program).
The model code itself does nothing but add points and lines to the collection when necessary, and provide a means to clear both collections.
Note that up to this point, there's nothing that is really dependent on the UI. The classes do use the WPF Point and CompositeCollection types, but this is mainly out of convenience. The implementation isn't really inherently tied to WPF, and those could be abstracted out relatively easily.
With all the basic data structures defined, now is the time to shift the focus to the UI, starting with the XAML:
<Window x:Class="TestSO66159694ClickPointsAndLines.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:l="clr-namespace:TestSO66159694ClickPointsAndLines"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.Resources>
<!-- The templates will rely on the ItemsControl to correctly position them -->
<DataTemplate DataType="{x:Type l:PointViewModel}">
<Ellipse Width="10" Height="10" Fill="Black">
<Ellipse.RenderTransform>
<!-- Center the ellipse on its actual location -->
<TranslateTransform X="-5" Y="-5"/>
</Ellipse.RenderTransform>
</Ellipse>
</DataTemplate>
<DataTemplate DataType="{x:Type l:LineViewModel}">
<Line Stroke="Green" StrokeThickness="4"
X2="{Binding X2}" Y2="{Binding Y2}"/>
</DataTemplate>
</Grid.Resources>
<ItemsControl ItemsSource="{Binding CanvasItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" MouseDown="Canvas_MouseDown_1">
<Canvas.Background>
<SolidColorBrush Color="White" Opacity="0"/>
</Canvas.Background>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Location.X}"/>
<Setter Property="Canvas.Top" Value="{Binding Location.Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
<Button Click="btn_Click" HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="200" Content="Clear"/>
</Grid>
</Window>
This XAML has two main sections to it: the templates corresponding to the graphical elements, which give the actual visual representation for these; and the actual content of the window, which handles binding the collection and setting the event handlers for the user interface (i.e. mouse-down and button-click).
WPF will automatically find the template appropriate for each graphical element. The actual positioning of the element within the canvas is provided by the ItemContainerStyle; this is because an ItemsControl will wrap your actual content in a presenter which is the actual direct child of the canvas, for which the Canvas.Left and Canvas.Top properties apply.
The canvas itself is provided as the ItemsPanelTemplate for the ItemsControl object. The default for ItemsControl is StackPanel, but you can provide any panel template you want to customize the behavior.
Finally, there is the actual user input. With all of the ground-work done above, this is really simple:
public partial class MainWindow : Window
{
private readonly MainViewModel _mainViewModel = new MainViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _mainViewModel;
}
private void Canvas_MouseDown_1(object sender, MouseButtonEventArgs e)
{
IInputElement canvas = sender as IInputElement;
Point canvasLocation = e.GetPosition(canvas);
_mainViewModel.AddPoint(canvasLocation);
}
private void btn_Click(object sender, RoutedEventArgs e)
{
_mainViewModel.Clear();
}
}
Since the main view model object has all the real data management logic, all the view itself (i.e. the window) needs to do is get the mouse position and pass that along to the main view model to deal with. Likewise, clearing the list is delegated to the main view model as well.
The view itself knows nothing about the underlying data structures, nor should it. The view's job is to provide the layer in your problem that interacts directly with the user, presenting to the user the underlying data in a form that is useful and comprehensible to the user, and taking user input and passing that along to the underlying data structures so that it can accomplish whatever it needs to do.
In addition to simplifying the overall design of the program, and making it easier to think about each discrete function of the program separately, doing it this way makes it trivial to adjust the way the visuals are presented to the user, without touching the C# code at all. One just needs to update the templates according to whatever visual aspect is desired.
And of course, when doing things correctly, as above, it's also simple to update the underlying data logic without having to meddle with the view. For example, it turns out your original question was not clear enough and you only want to connect every pair of dots the user clicks, but not make a continuous line. That's an easy enough change, simply by modifying the AddPoint() method by adding a single additional condition to the if statement that adds the line:
public void AddPoint(Point point)
{
PointViewModel pointModel = new PointViewModel(point);
_points.Add(pointModel);
if (_points.Count > 1 && _points.Count % 2 == 0)
{
_lines.Add(new LineViewModel(_points[_points.Count - 2], pointModel));
}
}
I.e. instead of just checking that there are two or more points with _points.Count > 1, also limit adding a line to only when a new pair of points has been added, by including _points.Count % 2 == 0. No need for new variables or anything like that. Just take into account the current state of things and act on that.
Note that one of the reasons this change is so easy is that the code above doesn't abuse the user interface API to store the state of your underlying data, and so you have immediate access to the number of points that have been added. This approach would be significantly more challenging if you had to figure out from the state of the UI how many points the user had already added (hence the different approach taken by the other answer, which is to add even more state to the UI to try to keep track of what the user's doing).
Again, good WPF programs always follow the principle of Separation of Concerns. Indeed, any good program does, but with WPF the framework actually makes it much easier to do so, and rewards you when you do. I encourage you to keep that in mind as you continue to learn about programming.
I'm writing an UWP app in C#. I have a page with a ListView taking all space available.
The list of items is huge so I use IncrementalLoadingCollection to load items gradually in the ListView while user is scrolling.
The problem is when I start the app all items are loaded right away in the ListView. GetPagedItemsAsync is called over and over and the app crashes throwing Layout cycle detected.
The only way I get the scroll loading to work is by fixing the ListView or height to a certain value. With fixed height on the ListView GetPagedItemsAsync is only called once at initialization and the scroll loading works as expected.
The thing is I want the ListView to fill page content so I don't want to set height to a specific value.
Page content
<Grid
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<ListView
x:Name="channelList"
ItemsSource="{x:Bind Channels}"
DataFetchSize="100"
IncrementalLoadingTrigger="Edge"
IncrementalLoadingThreshold="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Page class
public sealed partial class ChannelsPage : Page
{
private IncrementalLoadingCollection<ChannelSource, ChannelModel> Channels { get; set; }
public ChannelsPage()
{
this.InitializeComponent();
Channels = new IncrementalLoadingCollection<ChannelSource, ChannelModel>();
}
private class ChannelSource : IIncrementalSource<ChannelModel>
{
private int Current { get; set; } = 0;
private int Limit { get; set; } = 100;
public async Task<IEnumerable<ChannelModel>> GetPagedItemsAsync(int pageIndex, int pageSize, CancellationToken cancellationToken = default(CancellationToken))
{
List<ChannelModel> channels = ChannelModel.List(false, Limit, Current);
Current += Limit;
return channels;
}
}
}
[Solved]
The ListView was expanding out of bound due to a parent ScrollViewer in another file. Because of this continuous expansion out of its parent the items was loading non-stop.
The key to unlocking this mystery is the IncrementalLoadingThreshold. In simple terms it says the system how many "pages" of content to preload. In case you use 5, the system will make sure to have 5 times the height of the control ready to view. Try decreasing the value to 1 and you should see the method will be called once or twice. Even lower values like 0.1 are also possible.
Secondly, ensure you do not put the ListView into any layout control which allows it to expand beyond any bounds. For example, putting the list in a Grid.Row which has RowDefinition of Height="*" will make the list as long as it needs - which means it will keep loading items until there are no more available.
Finally, you need to make sure to indicate when you are out of items. This is easy if you return an empty IEnumerable from the method.
I'm trying to create a UserControl that acts as a sort of segmented progress bar. Input would be a collection of objects, each object would have a category, a duration property, and status property. The UserControl should stretch the width and height of the parent control. Each item in the collection should represent a segment of the progress bar; color of the segment is related to the status, the width of the segment is related to the duration, and the text overlaid on the segment would be related to the category or something.
Example custom progress bar:
The text might be the collection item's ID, the top segment color would be related to status, the bottom color would be related to the category, and the width related to the duration.
Some of the options I've considered:
Make a stackpanel and somehow define each items width and wrap the whole thing in a viewbox to make it stretch the height and width. How could I control the text size, how do I make the content fit the height, how do I bind a stackpanel to a collection?
Make an attached property for a grid control that would dynamically create columns and map the collection items to the grids. Seems like a lot of work and I'm hoping theres a simpler solution since my requirements are pretty specific.
Maybe theres a way to override a uniform grid to make it non-uniform?
Maybe I should just go all code-behind and draw rectangles by iterating through my collection?
Either way, I am crossing my fingers that somebody might know a simple solution to my problem.
Here is a full working proposition of solution to the custom progress bar.
Code is here : http://1drv.ms/1QmAVuZ
1 . If all the steps are not the same width, I prefer to use Grid with columns and different widths
The columns are built dynamically based upon following class :
public class StepItem
{
public int Length { get; set; }
public int Index { get; set; }
public String Label { get; set; }
public Brush Brush { get; set; }
}
2. I chose to implement a CustomControl and inherit of ItemsControl
CustomControl because I don't want to take care of implementing of the parts of the template of the Progressbar.
ItemsControl because :
-I want to provide to ItemsSource property a collection of StepItems
-ItemsControl can have some DataTemplate as template for each item
-ItemsControl can have any Panel like Grid as template presenting the collection of items
3. The component has template in Generic.xaml
-layoutGrid wil have the "continuous rainbow"
-overlayGrid will be displayed partially over the steps depending on progression or totally over (if no progress)
-ItemsPresenter will present the collection of DataTemplates corresponding to each StepItem
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ProgressItemsControl}">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid x:Name="layoutGrid">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<Grid x:Name="overlayGrid" Width="100" HorizontalAlignment="Right" Background="White"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
4. Customisation of the ItemsPanel to use a Grid (instead of vertical layout)
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate >
<Grid x:Name="stepsGrid" IsItemsHost="True" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
5. In code behind of components, setting of the column width
int i = 0;
foreach (StepItem stepItem in ItemsSource)
{
total += stepItem.Length;
var columnDefinition = new ColumnDefinition() { Width = new GridLength(stepItem.Length, GridUnitType.Star) };
stepsGrid.ColumnDefinitions.Add(columnDefinition);
Grid.SetColumn(stepsGrid.Children[i], stepItem.Index);
i++;
}
6. Code behind for declaring Dependency properties that can be monitored
(excerpt)
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(int), typeof(ProgressItemsControl), new PropertyMetadata(0));
7. Usage of the component
<local:CustomProgressBar
x:Name="customProgressBar1"
HorizontalAlignment="Left" Height="50" Margin="32,49,0,0"
VerticalAlignment="Top" Width="379"/>
8. Feeding the component with data
private List<StepItem> stepItems = new List<StepItem>{
new StepItem{
Index=0,
Label="Step1",
Length=20,
Brush = new SolidColorBrush(Color.FromArgb(255,255,0,0)),
new StepItem{
Index=4,
Label="Step5",
Length=25,
Brush = new SolidColorBrush(Color.FromArgb(255,0,128,0)),
},
};
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
progressItemsControl1.ItemsSource = stepItems;
}
Regards
Simple question but I am totally confused. I am developing a wp7 app using C#. I want a listbox with input number of image item which source should be same i.e. the list box should contain 'n' Image control with source set to a single image where 'n' is number of Listbox Item enter by user. e.g. If the user input '10', then the listbox should have ten items. I want the listbox ItemsPanelTemplate as Wrap-panel. Can somebody suggest me how to get this?
Define a ListBox in your XAML something like this
<ListBox x:Name="ListBoxImages">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Imagesource}" Width="300"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and then set its Source in the code behind like this
int noOfImages = 10; //Take the input from user
List<ImageClass> imageList = new List<ImageClass>();
for(int i=0; i<noOfImages; i++)
imageList.Add(new ImageClass() { Imagesource = "/user.jpg" });
ListBoxImages.ItemsSource = imageList; //Set the source of the listbox here
where ImageClass is,
public class ImageClass
{
public String Imagesource { get; set; }
}
The above is a sample for your understanding. Please customize wisely to suit your needs
I am developing an application where I have a class called UIManager in which there is a method which has an array of data
public void DisplayCatalog(string[] displayName, BitmapImage[] icons)
{
DisplayItem.Clear();
for (int i = 0; i < displayName.Length; i++)
{
DisplayItem.Add(new ItemList { WidgetName = displayName[i], Icon = icons[i] });
}
NotifyPropertyChanged("UI");
}
Now I want this data ie;WidgetName to be displayed in my MainPage where I have used a Looping selector.
*<custom:LoopingSelector x:Name="selectorLeft" ItemMargin="5" ItemSize="145,145" Margin="6,0,-6,22">
<custom:LoopingSelector.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding WidgetName}"/>
</StackPanel>
</DataTemplate>
</custom:LoopingSelector.ItemTemplate>
</custom:LoopingSelector>
*
Also I need to scroll the looping selector Horizontally.
How can I achieve this...??? Any valuable solutions Please.......
I have used Horizontal Looping Selector but I am not getting how to bind the data from my UIManager class on to the Horizontal Looping Selector..
<toolkit:HorizontalLoopingSelector Grid.Row="0" Margin="12" Height="128" ItemSize="128,128" ItemTemplate="{StaticResource ?????}">
<toolkit:HorizontalLoopingSelector.DataSource>
????????
</toolkit:HorizontalLoopingSelector.DataSource>
</toolkit:HorizontalLoopingSelector>
u need to create a datasource class and bind the values to the looping selector source .its similar to binding source to the vertical looping selector.
Visit http://www.windowsphonegeek.com/articles/WP7-LoopingSelector-in-depth--Part1-Visual-structure-and-API
Check out this resource if you still require a horizontal loop selector:
http://blog.supaywasi.com/2011/06/horizontal-looping-selector/