Форум программистов, компьютерный форум, киберфорум
C#: WPF, UWP и Silverlight
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 5.00/13: Рейтинг темы: голосов - 13, средняя оценка - 5.00
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
WPF

TimeLine поля

07.08.2020, 07:18. Показов 3482. Ответов 56
Метки fps (Все метки)

Студворк — интернет-сервис помощи студентам
На просторах интернетов заметил интересную тему и решил её раскрутить, ибо показалась мне сложной .
Задача состоит в том, чтобы сделать аналог полей в видеоредакторах.
Почему эта тема сразу показалась мне сложной? — если до этого я работал с объектами и не более, то сейчас я даже не могу понять с чем мне работать.
1. Вверху должена быть 24 часовая полоса с делениями часового диапазона(Первый UC). И хорошо, если пользователь оставил свой видос вначале , но если он 5-ти секундный видос захочет переместить на 20 000 попугаев вправо(относительно лева), то тогда в оперативу загрузится 5 тысяч таких часовых полосок(UserControl'ов). Думаю кулер начнёт орать на всю комнату, что тебе пора выключать компутатор, или он позовёт своего соседа Экрана Синевицкого.
Следовательно, как я прикинул, в ходе должно участвовать, допустим, 3 таких UserControl'ов, которые будут меняться друг за другом создавая эдакую иллюзию множества.
2. Допустим, но тут у меня сразу появляется вопрос об взаимодействии с событием Scroll.
В интернетах я, в рамках своей неопытности видать, не нашёл много информации о ScrollBar'е. Я пытался при изменении его Value создать и подключить событие, которое будет менять значение положения примитивному прямоугольнику. Как я только не танцевал с бубном а всё равно ничего не получилось.

Подытожу: должно быть 2 UserControl'a — часовая шкала и сам прямоугольник, которые меняют своё положение и уходят за границы экрана в следствии передвижения ползунка ScrollBar'a. Они не просто должны уходить за рамки, а и перемещаться за рамки с противоположной стороны экрана и менять свой размер длины(продолжительности минут "видеоролика").

Информацию разместил на просторах GitHub:
http://github.com/limeniye/xsenio

Буду обновлять по мере каких-то результатов(которые будут, если меня подтолкнут).
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
07.08.2020, 07:18
Ответы с готовыми решениями:

Не понял как прикрутить timeline
Здесьнаходиться обзор классной timeline.Вот скачал я библиотеку, добавил в reference в silverlight(не .web).А как на тулбокс поместить эти...

Создать конструктор класса TimeLine с параметрами
Как создать конструктор класса TimeLine с параметрами, т.е. конструктор не по-умолчанию. То есть такой вариант конструктора меня не...

WPF Binding поля, в качестве поля - объекта класса
Есть вопрос, как можно выполнить биндинг поля объекта класса? Для примера есть такая конструкция: new MainClass(new PartA (Name =...

56
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16151 / 11272 / 2890
Регистрация: 21.04.2018
Сообщений: 33,145
Записей в блоге: 2
12.08.2020, 16:43
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от limeniye Посмотреть сообщение
нет опыта работы с .xml, а .json я в работе с HTTP сталкиваюсь часто.
Я посмотрел в интернетах про этот .xml и работа с ним +- такая же, но .json файлы легче.
С XML - чуть проще и нагляднее.
В любом случае, это просто временный, локальный вариант, так как я не знаю откуда вы берёте исходные данные.

Добавлено через 54 секунды
Цитата Сообщение от limeniye Посмотреть сообщение
Ох, старался сделать так, чтобы мог входить любой тип
Любой - это object.
Но разве нам нужен любой?
0
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
12.08.2020, 16:46  [ТС]
Цитата Сообщение от Элд Хасп
У меня тоже.
ТО что вы делает, класс JsonSerializer - для чего он?
Чтобы решать задачу, или создавать проблемы, которые потом "героически" решать?
Не ругайтесь

Ох, я что-то там действительно напортачил. Сейчас переделаю .
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16151 / 11272 / 2890
Регистрация: 21.04.2018
Сообщений: 33,145
Записей в блоге: 2
12.08.2020, 16:55
Давайте, сначала по структуре и источнику самих исходных данных.

Общую структуру я представляю как коллекцию полос.
В каждой полосе коллекция сегментов и ещё какие-то дополнительные атрибуты (имя и т.п.)

Всё что нам нужно в модели - это ОДИН метод который возвращает коллекцию полос с сегментами для указанного диапазона.

Чтобы не загружать задачу по ОТОБРАЖЕНИЮ коллекции полос, я не стал рассматривать вопрос получения исходных данных.
Это не столь важно (для этой задачи) и сделаете уже сами.
Поэтому в упрощённом виде, сделал внешний источник в удобном для редактировании виде.

В каком виде удобнее редактировать данные: в XML или JSON ?
В JSON намного проще сделать ошибку при "ручном" редактировании.
И потом будут непонятные ошибки.
Поэтому я и выбрал (для временного решения) XML-файл.

Добавлено через 58 секунд
Цитата Сообщение от limeniye Посмотреть сообщение
Не ругайтесь
Я не ругаюсь, ни сколько.
Просто задаю вопросы, чтобы вы задумались.

Добавлено через 2 минуты
JsonSerializer - должен решать задачу считывания любых данных, судя по его интерфейсам.
Но с одной стороны он не решает такую задачу, а с другой эта задача никак не связана с тем, что нам нужно.
Нам нужно считать конкретный тип с данными и всё.
Никакого обобщённого решения нам не нужно.
0
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
12.08.2020, 18:10  [ТС]
Элд Хасп,
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
       
        /// <summary> получение данных </summary>
        /// <param name="name">Название полосы </param>
        /// Например: "первый", "второй"
        private IList<StripSegmentDto> GetXmlInformation(string name)
        {
            IList<StripSegmentDto> list = null;
            
            //загружаем xml файл
            XDocument xDoc = XDocument.Load(@"../../strips.xml");
            double beginPoint, endPoint;
            //получаем все узлы в xml файле
            foreach (var elm in xDoc.Descendants("strips"))
            {
                foreach(var strip in elm)
                {
                    if(strip.Attribute("name") == name)
                    {
                        foreach (var segment in strip)
                        {
                              list.add(new StripSegmentDto(double.Parse(segment.Attribute("begin").Value,
                                segment.Attribute("end").Value)));
                        } 
                    }
                }
            }
            return list;
        }
C#
1
2
IList<StripSegmentDto> firstLine = GetXmlInformation("первый");
IList<StripSegmentDto> secondLine = GetXmlInformation("второй");
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16151 / 11272 / 2890
Регистрация: 21.04.2018
Сообщений: 33,145
Записей в блоге: 2
12.08.2020, 18:17
limeniye, чуть подождите - отвечу.

Добавлено через 46 секунд
Вы к моему варианту на Гите не подключились?
Чтобы не все коды здесь выкладывать.
0
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
12.08.2020, 19:16  [ТС]
Подключался.

Добавлено через 31 секунду
Обращение к Элд Хасп.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16151 / 11272 / 2890
Регистрация: 21.04.2018
Сообщений: 33,145
Записей в блоге: 2
12.08.2020, 20:29
Описываю пошагово.
1) Структуру файла данных слегка изменил:
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8" ?>
<root>
  <range id="11111" begin="140.45" end="300.3"/>
  <strips>
    <strip id="1" name="первый">
      <segment id="1" begin="123.45" end="145.3"/>
      <segment id="2" begin="175.45" end="195.3"/>
      <segment id="3" begin="230.45" end="245.3"/>
      <segment id="4" begin="253.45" end="265.3"/>
      <segment id="5555" begin="293.45" end="345.3"/>
    </strip>
    <strip id="3434" name="второй">
      <segment id="1" begin="123.45" end="145.3"/>
      <segment id="2" begin="175.45" end="195.3"/>
      <segment id="3" begin="230.45" end="245.3"/>
      <segment id="4" begin="253.45" end="265.3"/>
      <segment id="5555" begin="293.45" end="345.3"/>
    </strip>
  </strips>
</root>
2) Получил по нему классы для десериализации.

"Причесал" их:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml.Serialization;
 
namespace Models
{
 
    [Serializable()]
    [DesignerCategory("code")]
    [XmlType(AnonymousType = true)]
    [XmlRoot(Namespace = "", IsNullable = false, ElementName = "root")]
    public class RootXml
    {
 
        [XmlElement()]
        public SegmentXml range { get; set; }
 
        [XmlArrayItem("strip", IsNullable = false)]
        public List<StripXml> strips { get; set; }
    }
 
    [Serializable()]
    [DesignerCategory("code")]
    [XmlType(AnonymousType = true)]
    public class SegmentXml
    {
 
        [XmlAttribute()]
        public int id { get; set; }
 
        [XmlAttribute()]
        public double begin { get; set; }
 
        [XmlAttribute()]
        public double end { get; set; }
    }
 
    [Serializable()]
    [DesignerCategory("code")]
    [XmlType(AnonymousType = true)]
    public class StripXml
    {
        [XmlElement("segment")]
        public List<SegmentXml> segments { get; set; }
 
        [XmlAttribute()]
        public int id { get; set; }
 
        [XmlAttribute()]
        public string name { get; set; }
    }
}
3) Разбил Модель на три файла.
В первом StripsModel - Repos.cs конструктор и метод загрузки данных:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
 
namespace Models
{
    // Часть Модели отвечающая за загрузку/сохранение данных.
    public partial class StripsModel
    {
        /// <summary>Имя XML-файла с данными</summary>
        public string FileName { get; }
 
        /// <summary>Конструктор с заданием имени файла с данными.</summary>
        /// <param name="fileName">Имя XML-файла с данными</param>
        public StripsModel(string fileName)
        {
            if (string.IsNullOrWhiteSpace(fileName))
                throw new ArgumentNullException(nameof(fileName));
            FileName = fileName;
        }
 
        /// <summary>Полный контейнер с данными.</summary>
        protected RootXml RootXml { get; set; }
 
        /// <summary>Диапазон фильтрации из последнего запроса.</summary>
        protected SegmentXml Range => RootXml.range;
 
        /// <summary>Список полос с сегментами.</summary>
        protected List<StripXml> Strips => RootXml.strips;
 
        /// <summary>Сериализатор XML-файла.</summary>
        protected static readonly XmlSerializer serlz
            = new XmlSerializer(typeof(RootXml));
 
        /// <summary>Загрузка данных.</summary>
        public void Load()
        {
            using (FileStream file = File.OpenRead(FileName))
                RootXml = (RootXml)serlz.Deserialize(file);
 
            //// Сортировка всех полос
            //foreach (StripXml strip in Strips)
            //    strip.segments.Sort(CompareSegmentXml);
        }
 
        /// <summary>Сохранение данных.</summary>
        public void Save()
        {
            using (FileStream file = File.Create(FileName))
                serlz.Serialize(file, RootXml);
        }
 
        //static int CompareSegmentXml(SegmentXml left, SegmentXml right)
        //{
        //    if (left == null)
        //        return right == null ? 0 : -1;
        //    if (right == null)
        //        return 1;
        //    return left.begin.CompareTo(right.begin);
        //}
    }
}
4) Пошагово проверил загрузку XML-файла:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using Models;
using System.Windows;
 
namespace StripSegments
{
    /// <summary>
    /// Логика взаимодействия для App.xaml
    /// </summary>
    public partial class App : Application
    {
        public App()
        {// В этой строке точка останова. После останова дальше по F11.
            string fileName = "strips.xml";
            StripsModel model = new StripsModel(fileName);
            model.Load();
        }
 
    }
}
5) В файле StripsModel - GetCollectionl.cs метод фильтрации и конвертации данных:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
using System.Collections.Generic;
using System.Linq;
 
namespace Models
{
    /// <summary>Модель с исходными данными</summary>
    public partial class StripsModel
    {
 
        /// <summary>Синхронное получение всех Полос с Сегментами из заданного диапазона.</summary>
        /// <param name="range">Диапазон фильтрации Сегментов.</param>
        /// <returns>Неизменяемую коллекцию всех Полос с Сегментами из заданного диапазона.</returns>
        public IReadOnlyCollection<StripDto> GetStrips(SegmentDto range)
        {
 
            // Запоминание диапазона запроса.
            Range.begin = range.Begin;
            Range.end = range.End;
 
            // Создание списка полос.
            List<StripDto> list = new List<StripDto>(Strips.Count);
            foreach (StripXml strip in Strips)
                list.Add(new StripDto
                (
                    strip.id,
                    strip.name,
                    // Вызов метода фильтрующего Сегменты.
                    GetSegments(Range, strip.segments),
                    range
                ));
 
            // Возврат неизменяемой оболочки списка.
            return list.AsReadOnly();
        }
 
        /// <summary>Статический метод фильтрации Сегментов.</summary>
        /// <param name="range">Диапазон фильтрации.</param>
        /// <param name="segments">Последовательность Сегментов.</param>
        /// <returns></returns>
        public static IReadOnlyCollection<SegmentDto> GetSegments(SegmentXml range, IEnumerable<SegmentXml> segments)
        {
            // Получение Сегментов котрые начинаются до заданного 
            // дииапазона, но заканчиваются внутри него.
            var listMin = segments
                .OrderBy(sg => sg.end)
                .SkipWhile(sg => sg.end <= range.begin)
                .OrderBy(sg => sg.begin)
                .TakeWhile(sg => sg.begin < range.begin);
 
            // Получение Сегментов начинающихся внутри заданного Диапазона.
            var listMax = segments
                .OrderBy(sg => sg.begin)
                .SkipWhile(sg => sg.begin < range.begin)
                .TakeWhile(sg => sg.begin < range.end);
 
            // Соединение полученных последовательностей,
            // преобразование в последовательность типа SegmentDto,
            // сортировка по началу Сегмента,
            // получение списка и его неизменяемой оболочки.
            return listMin.Concat(listMax)
                .Select(sg => CopyToDto(sg))
                .OrderBy(sg => sg.Begin)
                .ToList().AsReadOnly();
        }
 
        // Получение Диапазона последнего запроса.
        public SegmentDto GetRange() => CopyToDto(Range);
 
        // Получение SegmentDto из SegmentXml
        public static SegmentDto CopyToDto(SegmentXml segment)
            => new SegmentDto(segment.id, segment.begin, segment.end);
    }
 
}
6) В файле StripsModel - Async Methods.cs асинхронные оболочки для синхронных методов:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System.Collections.Generic;
using System.Threading.Tasks;
 
namespace Models
{
    /// <summary>Модель с исходными данными</summary>
    public partial class StripsModel
    {
 
        /// <summary>Асинхронное  получение всех Полос с Сегментами из заданного диапазона.</summary>
        /// <param name="range">Диапазон фильтрации Сегментов.</param>
        /// <returns>Выполняемую задачу результатом которой является неизменяемая коллекция
        /// всех Полос с Сегментами из заданного диапазона.</returns>
        public Task<IReadOnlyCollection<StripDto>> GetStripsAsync(SegmentDto range)
            => Task.Factory.StartNew(GetStrips, range);
 
        /// <summary>Перегрузка для использовании в Task.</summary>
        /// <param name="range">Дипазон фильтрации Сегментов.</param>
        /// <returns>Неизменяемую коллекцию всех Полос с Сегментами из заданного диапазона.</returns>
        public IReadOnlyCollection<StripDto> GetStrips(object range)
            => GetStrips((SegmentDto)range);
 
        /// <summary>Асинхронная загрузка данных.</summary>
        /// <returns>Выполняемую задачу с загрузкой данных.</returns>
        public Task LoadAsync() => Task.Run(Load);
 
    }
 
}
Дебажить уже не стал - времени нет.
Если разберётесь сами проверьте работу хотя бы синхронных методов.

Фиксация 3c5ca049 от 12.08.2020 20:28:48:
Добавлен файл с исходными данными.
Добавлены классы для десериализации файла данных.
Добавлен класс для Полосы.
В Модели реализована загрузка из файла данных.
Класс Модели разбит на три файла.
Реализованы методы получения Полос с Сегментами из заданного диапазона.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16151 / 11272 / 2890
Регистрация: 21.04.2018
Сообщений: 33,145
Записей в блоге: 2
13.08.2020, 13:45
limeniye, начинаем создавать View.
Начнём с контрола для одной полосы.

1) Создаём UserControl с именем StripUC.
Вообще-то, по феншую здесь напрашивается Custom Control, но в дебри пока не будем вдаваться.
Для начала нам нужно только одно свойство принимающее последовательность Сегментов:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
 
namespace StripSegments
{
    /// <summary>
    /// Логика взаимодействия для StripUC.xaml
    /// </summary>
    public partial class StripUC : UserControl
    {
        public StripUC()
        {
            InitializeComponent();
        }
 
        /// <summary>Источник последовательности Сегментов.</summary>
        public IEnumerable<StripSegment> SegmentsSource
        {
            get { return (IEnumerable<StripSegment>)GetValue(SegmentsSourceProperty); }
            set { SetValue(SegmentsSourceProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for SegmentsSource.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SegmentsSourceProperty =
            DependencyProperty.Register(nameof(SegmentsSource), typeof(IEnumerable<StripSegment>), typeof(StripUC), new PropertyMetadata(null));
    }
}
2) В XAML Контрола задаём шаблоны:
для Сегмента - жёлтый прямоугольник: высотой как Контрол, длиной из свойства Length Сегмента;
для панели элементов - Canvas размерами как Контрол и с обрезкой элементов выступающих за границы;
для стиля элемента - привязку смещения влево к свойство Begin Сегмента.
Контрол содержит только один элемент - ItemsControl.
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<UserControl x:Name="PART_Main"
             x:Class="StripSegments.StripUC"
             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:StripSegments"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <DataTemplate x:Key="Segment.Template" DataType="{x:Type local:StripSegment}">
            <Rectangle Height="{Binding ActualHeight, ElementName=PART_Main, Mode=OneWay}"
                       Width="{Binding Length}"
                       Fill="Yellow" Opacity="0.5"/>
        </DataTemplate>
        <ItemsPanelTemplate x:Key="Strip.PanelTemplate">
            <Canvas Width="{Binding ActualWidth, ElementName=PART_Main, Mode=OneWay}"
                        Height="{Binding ActualHeight, ElementName=PART_Main, Mode=OneWay}"
                        ClipToBounds="True"/>
        </ItemsPanelTemplate>
        <Style x:Key="Segment.Item.Style" TargetType="ContentPresenter">
            <Setter Property="Canvas.Left" Value="{Binding Begin}"/>
        </Style>
    </UserControl.Resources>
 
    <ItemsControl ItemsSource="{Binding SegmentsSource, ElementName=PART_Main}"
                  ItemTemplate="{Binding Mode=OneWay, Source={StaticResource Segment.Template}}"
                  ItemContainerStyle="{Binding Mode=OneWay, Source={StaticResource Segment.Item.Style}}" 
                  ItemsPanel="{StaticResource Strip.PanelTemplate}"/>
 
</UserControl>
3) Для удобства проверки создаём вспомогательный тип:
C#
1
2
3
4
5
6
7
8
using System.Collections.Generic;
 
namespace StripSegments
{
    /// <summary>Вспомогательный класс
    /// для создания списка Сегментов в XAML.</summary>
    public class SegmentList : List<StripSegment>{}
}
4) Задаём XAML Окна:
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<Window x:Class="StripSegments.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:StripSegments"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="1200">
    <Window.Resources>
        <local:SegmentList x:Key="SegmentList">
            <local:StripSegment  Begin="-100" End="200"/>
            <local:StripSegment  Begin="300" End="600"/>
            <local:StripSegment  Begin="700" End="1100"/>
        </local:SegmentList>
    </Window.Resources>
    <Grid>
        <local:StripUC x:Name="stripView" Height="40" Margin="100"
                       SegmentsSource="{Binding Mode=OneWay, Source={StaticResource SegmentList}}"
                       BorderBrush="Green" BorderThickness="1"/>
    </Grid>
</Window>
Всё работает как и задумывалось.

Фиксация b50b9908 13.08.2020 13:44:51
Создан UserControl StripUC для представления Полосы с Сегментами.
0
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
13.08.2020, 14:55  [ТС]
Элд Хасп,
Меня немного до сих пор, всё же, смущает этот Range.
Вы указываете диапазон как SegmentDto. Сейчас то я уже понимаю что так Вы задаёте рамки видимого диапазона, но всё же это не сегмент.
Если я не ошибаюсь, то Cat и Dog это не одно и то же.
Приведу пример.
Допустим я хочу добавить строку, о которой я писал давно:
public Color Background {get;}.
И тогда видимый диапазон будет содержать ненужную информацию цвета.
Я новичок и не хочу придираться, но мне кажется там должен быть класс наследник, или что-то типо такого.
Вообще я слышал что наследование это очень плохо, потому что задаёт "жесткую привязку".
Отпишите, пожалуйста, что-то по этому пункту, а точней, что я не так понимаю.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16151 / 11272 / 2890
Регистрация: 21.04.2018
Сообщений: 33,145
Записей в блоге: 2
13.08.2020, 15:34
Цитата Сообщение от limeniye Посмотреть сообщение
Меня немного до сих пор, всё же, смущает этот Range.
Вы указываете диапазон как SegmentDto. Сейчас то я уже понимаю что так Вы задаёте рамки видимого диапазона, но всё же это не сегмент.
Что такое Сегмент Полосы.
Это участок от и до.
Могут ли в участке быть другие сегменты?
Конечно.

Поэтому, на взгляд использование для Range типа Сегмента, вполне корректно.

Цитата Сообщение от limeniye Посмотреть сообщение
Я новичок и не хочу придираться
Не надо стесьняться.
Я тоже новичок.
Могу чего не так сделать или понять.

Цитата Сообщение от limeniye Посмотреть сообщение
Приведу пример.
Допустим я хочу добавить строку, о которой я писал давно:
public Color Background {get;}.
Конкретно цвет задавать не стоит.
Это всё таки функция View.
Может можно задать некий Контент по которому View будет выбирать тот или иной цвет.
Но пока не понимаю для чего вы это хотите задать, поэтому детальней ответить не могу.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16151 / 11272 / 2890
Регистрация: 21.04.2018
Сообщений: 33,145
Записей в блоге: 2
13.08.2020, 17:53
limeniye, продолжаем.

1) Полоса это у нас не только последовательность Сегментов, но и имя, Диапазон, возможно в будущем ещё и какой-то Контент.
Поэтому для полосы надо создать свой тип.
В этом типе надо предусмотреть обновление данных из типа StripDto:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
using Common;
using Models;
using System;
using System.Collections.ObjectModel;
 
namespace StripSegments
{
    /// <summary>Класс Полосы.</summary>
    public class Strip : OnPropertyChangedClass, ICloneable<Strip>, IDto<StripDto>
    {
        #region Поля, хранящие значения одноимённых свойств
        private string _name;
        private StripDto _dto;
        private StripSegment _range;
        #endregion
 
        /// <summary>Имя Полосы.</summary>
        public string Name { get => _name; set => SetProperty(ref _name, value); }
 
        /// <summary>Сегменты Полосы.</summary>
        public ObservableCollection<StripSegment> Segments { get; }
            = new ObservableCollection<StripSegment>();
 
        /// <summary>Диапазон полосы.</summary>
        public StripSegment Range { get => _range; set => SetProperty(ref _range, value); }
 
        public StripDto Copy() => throw new NotImplementedException();
 
        public void CopyFrom(StripDto dto)
        {
            Name = dto.Name;
            Range.CopyFrom(dto.Range);
 
            // Изменение значений существующих элементов
            int i;
            for (i = 0; i < Segments.Count && i < dto.Segments.Count; i++)
                Segments[i].CopyFrom(dto.Segments[i]);
 
            // Удаление лишних элементов
            if (i < Segments.Count)
                for (; i < Segments.Count; i++)
                    Segments.RemoveAt(i);
 
            // Добавление нехватающих элементов
            else if (i < dto.Segments.Count)
                for (; i < dto.Segments.Count; i++)
                    Segments.Add(StripSegment.Create(dto.Segments[i]));
 
        }
 
        public void CopyTo(StripDto obj) => throw new NotImplementedException();
 
        #region Реализация интерфейса IDto<StripSegmentDto>
        public StripDto Dto { get => _dto; private set => SetProperty(ref _dto, value); }
        public void SetDto(StripDto newDto) => CopyFrom(Dto = newDto);
        #endregion
 
        #region Реализация интерфейса ICloneable<StripSegment>
        public Strip Clone() => (Strip)((ICloneable)this).Clone();
        object ICloneable.Clone() => MemberwiseClone();
        #endregion
    }
}
2) Теперь надо переделать наш контрол для приёма типа Strip:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
 
namespace StripSegments
{
    /// <summary>
    /// Логика взаимодействия для StripUC.xaml
    /// </summary>
    public partial class StripUC : UserControl
    {
        public StripUC()
        {
            InitializeComponent();
        }
 
        /// <summary>Полоса с данными.</summary>
        public Strip Strip
        {
            get { return (Strip)GetValue(StripProperty); }
            set { SetValue(StripProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for Strip.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StripProperty =
            DependencyProperty.Register(nameof(Strip), typeof(Strip), typeof(StripUC), new PropertyMetadata(null, StripChanged));
 
        private static void StripChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            StripUC stripUC = (StripUC)d;
            stripUC.SegmentsSource = stripUC.Strip?.Segments;
        }
 
        /// <summary>Источник последовательности Сегментов.
        /// Свойство Только Для Чтения.</summary>
        public IEnumerable<StripSegment> SegmentsSource
        {
            get { return (IEnumerable<StripSegment>)GetValue(SegmentsSourceProperty); }
            private set { SetValue(SegmentsSourcePropertyKey, value); }
        }
 
        // Using a DependencyProperty as the backing store for SegmentsSource.  This enables animation, styling, binding, etc...
        public static readonly DependencyPropertyKey SegmentsSourcePropertyKey =
            DependencyProperty.RegisterReadOnly(nameof(SegmentsSource), typeof(IEnumerable<StripSegment>), typeof(StripUC), new PropertyMetadata(null));
        public static readonly DependencyProperty SegmentsSourceProperty = SegmentsSourcePropertyKey.DependencyProperty;
    }
}
3) Но отчего ещё зависит отображение сегментов?
- у Сегментов абсолютные значения, а нам надо отображать относительно начала Диапазона.
- масштаб отображения у нас сейчас абсолютный. А нам надо такой который автоматически привяжет длину Диапазона к ширине контрола.

Сделаем для этого два конвертера.
Один преобразует длину Сегментов:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using System;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
 
namespace StripSegments
{
    public class LengthScaleConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            // Все параметры в массиве values:
            // 0 - Преобразуемая Длина
            // 1 - длина Диапазона
            // 2 - ширина Контрола
            if (values == null)
                return null;
 
            // Получение первых трёх значений и перевод их в double.
            double[] nums = values.Take(3).OfType<double>().ToArray();
            if (nums.Length != 3)
                return null;
 
            // Присваивание значений переменным для наглядности
            double lengthSource = nums[0];
            double lengthRange = nums[1];
            double width = nums[2];
 
            return lengthSource * width / lengthRange;
        }
 
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
 
}
Второй преобразует Начало Сегмента в смещение от начало полосы и умножает на масштаб:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
 
namespace StripSegments
{
    public class OffsetScaleConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            // Все параметры в массиве values:
            // 0 - Начало Сегмента
            // 1 - Смещение (Начало) Диапазона
            // 2 - длина Диапазона
            // 3 - ширина Контрола
            if (values == null)
                return null;
 
            // Получение первых трёх значений и перевод их в double.
            double[] nums = values.Take(4).OfType<double>().ToArray();
            if (nums.Length != 4)
                return null;
 
            // Присваивание значений переменным для наглядности
            double begin = nums[0];
            double offset = nums[1];
            double length = nums[2];
            double width = nums[3];
 
            return (begin - offset) * width / length;
        }
 
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
 
}
4) Добавим Конвертеры в XAML Контрола
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<UserControl x:Name="PART_Main"
             x:Class="StripSegments.StripUC"
             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:StripSegments"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <local:LengthScaleConverter x:Key="LenghtScaleConverter"/>
        <local:OffsetScaleConverter x:Key="OffsetScaleConverter"/>
        <DataTemplate x:Key="Segment.Template" DataType="{x:Type local:StripSegment}">
            <Rectangle Height="{Binding ActualHeight, ElementName=PART_Main, Mode=OneWay}"
                       Fill="Yellow" Opacity="0.5">
                <Rectangle.Width>
                    <MultiBinding Converter="{StaticResource LenghtScaleConverter}">
                        <Binding Path="Length"/>
                        <Binding Path="Strip.Range.Length" ElementName="PART_Main"/>
                        <Binding Path="ActualWidth" ElementName="PART_Main"/>
                    </MultiBinding>
                </Rectangle.Width>
            </Rectangle>
        </DataTemplate>
        <ItemsPanelTemplate x:Key="Strip.PanelTemplate">
            <Canvas Width="{Binding ActualWidth, ElementName=PART_Main, Mode=OneWay}"
                        Height="{Binding ActualHeight, ElementName=PART_Main, Mode=OneWay}"
                        ClipToBounds="True"/>
        </ItemsPanelTemplate>
        <Style x:Key="Segment.Item.Style" TargetType="ContentPresenter">
            <Setter Property="Canvas.Left" >
                <Setter.Value>
                    <MultiBinding Converter="{StaticResource OffsetScaleConverter}">
                        <Binding Path="Begin"/>
                        <Binding Path="Strip.Range.Begin" ElementName="PART_Main"/>
                        <Binding Path="Strip.Range.Length" ElementName="PART_Main"/>
                        <Binding Path="ActualWidth" ElementName="PART_Main"/>
                    </MultiBinding>
                </Setter.Value>
            </Setter>
        </Style>
    </UserControl.Resources>
 
    <ItemsControl ItemsSource="{Binding SegmentsSource, ElementName=PART_Main}"
                  ItemTemplate="{Binding Mode=OneWay, Source={StaticResource Segment.Template}}"
                  ItemContainerStyle="{Binding Mode=OneWay, Source={StaticResource Segment.Item.Style}}" 
                  ItemsPanel="{StaticResource Strip.PanelTemplate}"/>
 
</UserControl>
5) Проверяем в Окне:
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<Window x:Class="StripSegments.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:StripSegments"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="1200">
    <Window.Resources>
        <!--<local:SegmentList x:Key="SegmentList">
            <local:StripSegment  Begin="-100" End="200"/>
            <local:StripSegment  Begin="300" End="600"/>
            <local:StripSegment  Begin="700" End="1100"/>
        </local:SegmentList>-->
        
        <!--Можно менять значения и сразу видно
        как это влияет на Отображение-->
        <local:Strip x:Key="strip">
            <local:Strip.Range>
                <local:StripSegment Begin="100" End="800"/>
            </local:Strip.Range>
            <local:Strip.Segments>
                <local:StripSegment  Begin="0" End="200"/>
                <local:StripSegment  Begin="300" End="600"/>
                <local:StripSegment  Begin="700" End="1100"/>
            </local:Strip.Segments>
        </local:Strip>
    </Window.Resources>
    <Grid>
        <local:StripUC x:Name="stripView" Height="40" Margin="100"
                       BorderBrush="Green" BorderThickness="1"
                       Strip="{Binding Mode=OneWay, Source={StaticResource strip}}"/>
                        <!--SegmentsSource="{Binding Mode=OneWay, Source={StaticResource SegmentList}}"-->
   </Grid>
</Window>
Всё работает как и задумывалось.

Фиксация 56c37281 от 13.08.2020 17:52:41
StripUC переделан на получение данных от типа Strip.
Добавлены конвертеры для автоматического учёта смещения Сегментов и масштабирования их размера.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16151 / 11272 / 2890
Регистрация: 21.04.2018
Сообщений: 33,145
Записей в блоге: 2
14.08.2020, 09:13
limeniye, создаём контрол для отображения списка полос.
Много описывать не буду.
Делается примерно также как предыдущий.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
 
namespace StripSegments
{
    /// <summary>
    /// Логика взаимодействия для StripsUC.xaml
    /// </summary>
    public partial class StripsUC : UserControl
    {
        public StripsUC()
        {
            InitializeComponent();
        }
 
        /// <summary>Источник последовательностм полос.</summary>
        public IEnumerable<Strip> StripsSource
        {
            get { return (IEnumerable<Strip>)GetValue(StripsSourceProperty); }
            set { SetValue(StripsSourceProperty, value); }
        }
 
        // Using a DependencyProperty as the backing store for StripsSource.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StripsSourceProperty =
            DependencyProperty.Register(nameof(StripsSource), typeof(IEnumerable<Strip>), typeof(StripsUC), new PropertyMetadata(null));
    }
}
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<UserControl x:Name="PART_Main"
             x:Class="StripSegments.StripsUC"
             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:StripSegments"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <DataTemplate x:Key="Strip.ItemTemplate" DataType="{x:Type local:Strip}">
            <local:StripUC Margin="5" BorderBrush="Green" BorderThickness="2"
                           Height="40" Strip="{Binding}"/>
        </DataTemplate>
    </UserControl.Resources>
 
    <ItemsControl ItemsSource="{Binding StripsSource, ElementName=PART_Main}"
                  ItemTemplate="{StaticResource Strip.ItemTemplate}"
                  HorizontalContentAlignment="Stretch"/>
 
</UserControl>
Отладка контрола:
C#
1
2
3
4
5
6
7
8
using System.Collections.Generic;
 
namespace StripSegments
{
    /// <summary>Вспомогательный класс
    /// для создания списка Полос в XAML.</summary>
    public class StripList : List<Strip> { }
}
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<Window x:Class="StripSegments.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:StripSegments"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="1200">
    <Window.Resources>
 
        <!--Можно менять значения и сразу видно
        как это влияет на Отображение-->
        <local:StripList x:Key="stripList">
            <local:Strip>
                <local:Strip.Range>
                    <local:Segment Begin="100" End="800"/>
                </local:Strip.Range>
                <local:Segment Begin="0" End="200"/>
                <local:Segment Begin="300" End="600"/>
                <local:Segment Begin="700" End="1100"/>
            </local:Strip>
            <local:Strip>
                <local:Strip.Range>
                    <local:Segment Begin="100" End="800"/>
                </local:Strip.Range>
                <local:Segment Begin="50" End="250"/>
                <local:Segment Begin="300" End="400"/>
                <local:Segment Begin="500" End="650"/>
                <local:Segment Begin="750" End="1000"/>
            </local:Strip>
        </local:StripList>
 
    </Window.Resources>
    <Grid>
        <local:StripsUC x:Name="stripView" Margin="100"
                       BorderBrush="Green" BorderThickness="1"
                       StripsSource="{Binding Mode=OneWay, Source={StaticResource stripList}}"/>
    </Grid>
</Window>
Фиксация 07143d42 от 14.08.2020 8:57:15:
Добавлен Контрол для отображения списка Полос.
Добавлено через 3 минуты
Можно немного изменить создание списка в XAML
XML
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    <Window.Resources>
 
        <!--Можно менять значения и сразу видно
        как это влияет на Отображение-->
        <local:Segment x:Key="range"  Begin="50" End="800"/>
        <local:StripList x:Key="stripList">
            <local:Strip Range="{StaticResource range}">
                <local:Segment Begin="0" End="200"/>
                <local:Segment Begin="300" End="600"/>
                <local:Segment Begin="700" End="1100"/>
            </local:Strip>
            <local:Strip Range="{StaticResource range}">
                <local:Segment Begin="50" End="250"/>
                <local:Segment Begin="300" End="400"/>
                <local:Segment Begin="500" End="650"/>
                <local:Segment Begin="750" End="1000"/>
            </local:Strip>
        </local:StripList>
 
    </Window.Resources>
Добавлено через 11 минут
Теперь надо приступить к созданию View с возможностью промотки.
Изначально планировалось проматывать скруллом.
Но возникает вопрос по Thumb, а это основной элемент скрулла.
Надо задать Thumb размер и положение.
Подразумевается, что размер Thumb - это пропорциональный размер отображаемого участка к ко всему возможному диапазону.
Но возможный диапазон в задаче - бесконечность....

А положение Thumb - отражается относительно положения в возможном диапазоне.

Поэтому надо придумать какой-то алгоритм подходящий для задачи для определения размера и положения Thumb.

После этого можно будет задать ему события и по ним изменять диапазон отображения Полос.

Так, что limeniye - думайте над алгоритмом.
0
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
16.08.2020, 16:20  [ТС]
Элд Хасп, пытаюсь добавить TimeBar по примеру, но первым блин -- комом.

1. Создал этой полосе свой лист class TimerBarList : List<Strip> { }
2. Создал TimerBarUC и в Xaml изменил просто цвет с жёлтого на зелёный, для примера.
XML
1
2
<Rectangle Height="{Binding ActualHeight, ElementName=PART_ic, Mode=OneWay}"
                Fill="Gray" Opacity="0.5">
3. Скопировал код из StripUC.xaml.cs, и сделал грязное дело -- откопипастил в TimerBar.xaml.cs слегка видоизменив.
4. Добавил в StripsUC.xaml
XML
1
2
<local:TimerBarUC Margin="5" BorderBrush="Green" BorderThickness="2"
                           Height="40" Strip="{Binding}"/>
5. В MainWindows.xaml
XML
1
2
3
4
5
6
7
8
9
<local:TimerBarList x:Key="timerBarList">
            <local:Strip>
                <local:Strip.Range>
                    <local:Segment Begin="100" End="800"/>
                </local:Strip.Range>
                <local:Segment Begin="100" End="300"/>
                <local:Segment Begin="300" End="600"/>
            </local:Strip>
        </local:TimerBarList>
XML
1
2
3
<local:TimerBarUC x:Name="timerBarView" Margin="100" 
                          BorderBrush="Green" BorderThickness="1"
                          StripsSource="{Binding Mode=OneWay, Source={StaticResource timerBarList}}"/>
Добавлено через 12 минут
Элд Хасп, и чем глубже я пытаюсь повторить Ваш код, тем больше копипаста у меня получает. По всей сути у меня должен быть в итоге 1 компонент с строками и сегментами и с строкой и TimerBar'ами, а итоге у меня получается 2 разных компонента с куча одинаковых классов только для того, чтобы сделать немного другую реализацию
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16151 / 11272 / 2890
Регистрация: 21.04.2018
Сообщений: 33,145
Записей в блоге: 2
16.08.2020, 17:23
limeniye, я закончу свой пример сначала.
Так как с размером не определились, то пока я его буду задавать из XML
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8" ?>
<root>
  <step>10</step>
  <size id="111" begin="50" end="350"/>
  <range id="11111" begin="150" end="250"/>
  <strips>
    <strip id="1" name="первый">
      <segment id="1" begin="123.45" end="145.3"/>
      <segment id="2" begin="175.45" end="195.3"/>
      <segment id="3" begin="230.45" end="245.3"/>
      <segment id="4" begin="253.45" end="265.3"/>
      <segment id="5555" begin="293.45" end="320"/>
    </strip>
    <strip id="3434" name="второй">
      <segment id="1" begin="103" end="150"/>
      <segment id="2" begin="155" end="200"/>
      <segment id="3" begin="210" end="260"/>
      <segment id="4" begin="270" end="345"/>
    </strip>
  </strips>
</root>
Добавляем ViewModel со свойствами:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
using Common;
using Models;
using System;
using System.Collections.ObjectModel;
 
namespace StripSegments
{
    /// <summary>ViewModel - только свойства и реализация команд.</summary>
    public class StripsViewModelProperties : OnPropertyChangedClass
    {
        #region Поля для хранения значений свойств
        private double _step;
        private Segment _size = new Segment();
        private Segment _range = new Segment();
        #endregion
 
        /// <summary>Шаг изменения.</summary>
        public double Step { get => _step; set => SetProperty(ref _step, value); }
 
        /// <summary>Обший размер.</summary>
        public Segment Size { get => _size; set => SetProperty(ref _size, value); }
 
        /// <summary>Оторбражаемый диапазон.</summary>
        public Segment Range { get => _range; set => SetProperty(ref _range, value); }
 
        /// <summary>Коллекция полос.</summary>
        public ObservableCollection<Strip> Strips { get; } = new ObservableCollection<Strip>();
 
        private RelayActionCommand _nextStepCommand;
        /// <summary>Команда на один шаг вперёд.</summary>
        public RelayActionCommand NextStepCommand => _nextStepCommand
            ?? (_nextStepCommand = new RelayActionCommand(NextStepMetod, NextStepCanMetod));
 
        private bool NextStepCanMetod()
            => Range.End < Size.End;
 
        private void NextStepMetod()
        {
            GetStrips(new SegmentDto(Range.Begin + Step, Range.End + Step));
        }
 
        /// <summary>Метод изменения видимого диапазона. 
        /// Должен переопредляться в производном классе.</summary>
        /// <param name="segmentDto">Новый Диапазон.</param>
        protected virtual void GetStrips(SegmentDto segmentDto) { throw new NotImplementedException(); }
 
        private RelayActionCommand _prevStepCommand;
        /// <summary>Команда на один шаг назад.</summary>
        public RelayActionCommand PrevStepCommand => _prevStepCommand
            ?? (_prevStepCommand = new RelayActionCommand(PrevStepMetod, PrevStepCanMetod));
 
        private bool PrevStepCanMetod()
            => Range.Begin > Size.Begin;
 
        private void PrevStepMetod()
        {
            GetStrips(new SegmentDto(Range.Begin - Step, Range.End - Step));
        }
 
        /// <summary>Загрузка данных.
        /// Должен переопредляться в производном классе.</summary>
        public virtual void Load() { throw new NotImplementedException(); }
    }
 
}
Для ScrollBar нам нужно будет два конвертера для расчёта положения ползунка (это среднее от видимого диапазона) и для расчёта минимального/максимального значения ползунка.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;
 
namespace Common
{
    /// <summary>Мультиконвертер - возвращает среднее double значение массива полученных значений.</summary>
    public class DoubleMediumConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values == null)
                return null;
 
            int count = 0;
            double sum = 0;
            foreach (object value in values)
            {
                if (value is double val)
                {
                    count++;
 
                    sum += val;
                }
                else if (TypeDescriptor.GetConverter(typeof(double)).IsValid(value))
                {
                    count++;
 
                    sum += (double)TypeDescriptor.GetConverter(typeof(double)).ConvertFrom(value);
                }
            }
            if (count == 0)
                return null;
            return sum / count;
        }
 
 
 
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
 
}
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System;
using System.Globalization;
using System.Windows.Data;
 
namespace StripSegments
{
    /// <summary>Мультиконвертер для расчёта Maximum и Minimum ScrollBar.</summary>
    public class SizeToValueConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if ((string)parameter == "+")
                return (double)values[0] + (double)values[1] * 0.5;
            if ((string)parameter == "-")
                return (double)values[0] - (double)values[1] * 0.5;
            return null;
        }
 
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
 
}
Добавляем это всё в Окно:
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<Window x:Class="StripSegments.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:StripSegments" xmlns:comm="clr-namespace:Common;assembly=Common"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="1200"
        >
    <Window.Resources>
 
        <!--Можно менять значения и сразу видно
        как это влияет на Отображение-->
        <local:Segment x:Key="range" Begin="100" End="800"/>
        <local:Segment x:Key="size" Begin="-500" End="1800"/>
        <local:StripsViewModelProperties
            x:Key="viewModelDisigned"
            Range="{StaticResource range}"
            Size="{StaticResource size}"
            Step="10">
            <local:StripsViewModelProperties.Strips>
                <local:Strip Range="{StaticResource range}">
                    <local:Segment Begin="0" End="200"/>
                    <local:Segment Begin="300" End="600"/>
                    <local:Segment Begin="700" End="1100"/>
                </local:Strip>
                <local:Strip Range="{StaticResource range}">
                    <local:Segment Begin="50" End="250"/>
                    <local:Segment Begin="300" End="400"/>
                    <local:Segment Begin="500" End="650"/>
                    <local:Segment Begin="750" End="1000"/>
                </local:Strip>
            </local:StripsViewModelProperties.Strips>
        </local:StripsViewModelProperties>
        <comm:DoubleMediumConverter x:Key="DoubleMedium"/>
        <local:SizeToValueConverter x:Key="SzeConverter"/>
    </Window.Resources>
    <d:Window.DataContext>
        <Binding Mode="OneWay" Source="{StaticResource viewModelDisigned}"/>
    </d:Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition/>
            <ColumnDefinition Width="100"/>
        </Grid.ColumnDefinitions>
        <local:StripsUC x:Name="stripView" Grid.Row="1" Grid.Column="1" Margin="5"
                       BorderBrush="Green" BorderThickness="1"
                       StripsSource="{Binding Strips}"/>
        <TextBlock Margin="5" Text="{Binding Size.Begin}"/>
        <TextBlock Margin="5" Grid.Column="1" HorizontalAlignment="Left" Text="{Binding Range.Begin}"/>
        <TextBlock Margin="5" Grid.Column="1" HorizontalAlignment="Right" Text="{Binding Range.End}"/>
        <TextBlock Margin="5" Grid.Column="2" Text="{Binding Size.End}"/>
        <ScrollBar Margin="5" Grid.Row="2" Grid.Column="1" Orientation="Horizontal"
                   SmallChange="{Binding Step}" ViewportSize="{Binding Range.Length}">
            <ScrollBar.Value>
                <MultiBinding Converter="{StaticResource DoubleMedium}" Mode="OneWay">
                    <Binding Path="Range.Begin"/>
                    <Binding Path="Range.End"/>
                </MultiBinding>
            </ScrollBar.Value>
            <ScrollBar.Minimum>
                <MultiBinding Converter="{StaticResource SzeConverter}" ConverterParameter="+" Mode="OneWay">
                    <Binding Path="Size.Begin"/>
                    <Binding Path="Range.Length"/>
                </MultiBinding>
            </ScrollBar.Minimum>
            <ScrollBar.Maximum>
                <MultiBinding Converter="{StaticResource SzeConverter}" ConverterParameter="-" Mode="OneWay">
                    <Binding Path="Size.End"/>
                    <Binding Path="Range.Length"/>
                </MultiBinding>
            </ScrollBar.Maximum>
            <ScrollBar.CommandBindings>
                <CommandBinding Command="{x:Static ScrollBar.LineLeftCommand}" CanExecute="LineLeftCommand_CanExecute" Executed="LineLeftCommand_Executed"/>
                <CommandBinding Command="{x:Static ScrollBar.LineRightCommand}" CanExecute="LineRightCommand_CanExecute" Executed="LineRightCommand_Executed"/>
            </ScrollBar.CommandBindings>
        </ScrollBar>
    </Grid>
</Window>
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System.Windows;
 
namespace StripSegments
{
    /// <summary>
    /// Логика взаимодействия для MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
 
        private void LineLeftCommand_CanExecute(object sender, System.Windows.Input.CanExecuteRoutedEventArgs e)
        {
            StripsViewModelProperties viewModel = (StripsViewModelProperties)DataContext;
            e.CanExecute = viewModel.PrevStepCommand.CanExecute(null);
            e.Handled = true;
        }
 
        private void LineLeftCommand_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
        {
            StripsViewModelProperties viewModel = (StripsViewModelProperties)DataContext;
            viewModel.PrevStepCommand.Execute(null);
            e.Handled = true;
        }
 
        private void LineRightCommand_CanExecute(object sender, System.Windows.Input.CanExecuteRoutedEventArgs e)
        {
            StripsViewModelProperties viewModel = (StripsViewModelProperties)DataContext;
            e.CanExecute = viewModel.NextStepCommand.CanExecute(null);
            e.Handled = true;
        }
 
        private void LineRightCommand_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
        {
            StripsViewModelProperties viewModel = (StripsViewModelProperties)DataContext;
            viewModel.NextStepCommand.Execute(null);
            e.Handled = true;
        }
    }
 
}
В Конструторе (Дизайнере) XAML вроде всё ОК.

Добавляем Модель в ViewModel
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
using Models;
using System;
using System.Collections.Generic;
using System.Windows;
 
namespace StripSegments
{
 
    public class StripsViewModel : StripsViewModelProperties
    {
        private readonly StripsModel Model;
 
        public StripsViewModel(StripsModel model) => Model = model;
 
        public override void Load()
        {
            Model.Load();
            Step = Model.GetStep();
            Size = Segment.Create(Model.GetSize());
            Range = Segment.Create(Model.GetRange());
            GetStrips(Model.GetRange());
        }
 
        protected async override void GetStrips(SegmentDto segmentDto)
        {
 
            IReadOnlyList<StripDto> strips = await Model.GetStripsAsync(segmentDto).ConfigureAwait(true);
 
            if (Application.Current.Dispatcher.CheckAccess())
                UpdateStrips(strips);
            else
                Application.Current.Dispatcher.BeginInvoke((Action<IReadOnlyList<StripDto>>)UpdateStrips, strips);
 
        }
 
        private void UpdateStrips(IReadOnlyList<StripDto> strips)
        {
            Range.CopyFrom(Model.GetRange());
 
 
            // Изменение значений существующих элементов
            int i;
            for (i = 0; i < Strips.Count && i < strips.Count; i++)
                Strips[i].SetDto(strips[i]);
 
            // Удаление лишних элементов
            if (i < Strips.Count)
                for (; i < Strips.Count; i++)
                    Strips.RemoveAt(i);
 
            // Добавление нехватающих элементов
            else if (i < strips.Count)
                for (; i < strips.Count; i++)
                    Strips.Add(Strip.Create(strips[i]));
        }
    }
 
}
В App прописываем все связи:
XML
1
2
3
4
5
6
7
<Application x:Class="StripSegments.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             Startup="Application_Startup"
             >
    <Application.Resources/>
</Application>
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using Models;
using System.Windows;
 
namespace StripSegments
{
    /// <summary>
    /// Логика взаимодействия для App.xaml
    /// </summary>
    public partial class App : Application
    {
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            string fileName = "strips.xml";
            StripsModel model = new StripsModel(fileName);
 
            StripsViewModel viewModel = new StripsViewModel(model);
 
            MainWindow = new MainWindow() { DataContext = viewModel };
 
            MainWindow.Show();
 
            viewModel.Load();
        }
    }
}
Запускаем, проверяем - всё работает.

Фиксация dd42034d от 16.08.2020 17:18:05
Добавлена ViewModel.
В Окне реализована промотка конопками ScrollBar.
Реализаци Решения завершена.
Добавлено через 1 минуту
На данный момент не реализована поддержка промотки захватом Thumb мышью.
Но лезть в дебри, пока не вижу необходимости.

Добавлено через 2 минуты
Цитата Сообщение от limeniye Посмотреть сообщение
и чем глубже я пытаюсь повторить Ваш код, тем больше копипаста у меня получает.
Вполне возможно.
Код лаконичный, хорошо структурированный.
А что в нём менять?
Только название типов, переменных?
0
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
17.08.2020, 01:43  [ТС]
Элд Хасп, по поводу вытягивания данных из .xml файла -- зачем это делать, когда проще сгенерировать?
Простой пример генерации листа сегментов:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    public class GetStripInformation
    {
        protected static readonly Random rand = new Random();
 
        /// <summary> Получение рандомных позиций сегментов </summary>
        /// <param name="count"> Целочисленное количество рандомных сегментов</param>
        public IList<SegmentDto> GetRandomList(int count)
        {
            var list = new List<SegmentDto>();
 
            if (count <= 0)
                throw new ArgumentOutOfRangeException(nameof(count), "Количество меньше одного");
            else
            {
                int temp = rand.Next(0, 200);
                int randEnd = rand.Next(temp + 50, temp + 250);
 
                for (int i = 0; i < count; i++)
                {
                    list.Add(new SegmentDto(temp, randEnd));
                    temp = rand.Next(randEnd, randEnd + 200);
                    randEnd = rand.Next(temp + 50, temp + 250);
                }
            }
 
            return list;
        }
    }
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    class Program
    {
        
        static void Main(string[] args)
        {
            GetStripInformation lol = new GetStripInformation();
            IList<SegmentDto> list = lol.GetRandomList(50);
 
            foreach(var kek in list)
            {
                Console.WriteLine(kek.Begin +", "+kek.End + "\n");
            }
        }
    }
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16151 / 11272 / 2890
Регистрация: 21.04.2018
Сообщений: 33,145
Записей в блоге: 2
17.08.2020, 09:18
Цитата Сообщение от limeniye Посмотреть сообщение
вытягивания данных из .xml файла -- зачем это делать, когда проще сгенерировать?
Во-первых, нужен не только список сегментов, а все данные из класса RootXml.
Во-вторых, можно, конечно и генерить, но мне для отладки проще сразу десериализовать RootXml - это всего две-три строки кода.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16151 / 11272 / 2890
Регистрация: 21.04.2018
Сообщений: 33,145
Записей в блоге: 2
17.08.2020, 15:48
limeniye, доделал поддержку перемещения ползунка мышью.

Фиксация 9e85d75a от 17.08.2020 15:47:10

Добавлена поддержка перемещения ползунка мышью.
Конвертеры для ScrollBar стали не нужны.
Добавлен проект из GitHub по ссылке в посте Освоение Attached Properties: Списочное свойство .
Всплывающие команды ScrollBar переправляются сразу в ViewModel для чего используется AP-свойство для привязки всплывающих команд.
Добавлен класс расширения XAML разметки SegmentDtoExt для создания в XAML экземпляров SegmentDto.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
17.08.2020, 15:48
Помогаю со студенческими работами здесь

Невозможно выполнить выборку по имени поля (по номеру поля все работает)
Вот кусок кода: conn = new SqlCeConnection(&quot;Data Source = Database.sdf;&quot;); conn.Open(); ...

Передача поля в контроллер из вьюшки без пападания поля во вьюшку
Здравствуйте. Имеется контроллер: public class TestController { public long personID; ...

Заполнение базы данных с определенного номера поля ключевого поля
Добрый день, написан код для заполнения таблицы на c#, но при запуске выдает ошибку, что не может создать ключевое поле, которое уже...

как сделать Поля таблицы на русском и скрыть поля счетчик в windows forms
как правильно сделать Поля таблицы на русском и скрыть поля счетчик в windows forms? SQL server использую

При достижении края поля объект должен перерисовываться с противоположной стороны поля
Доброе время суток коллеги. Перейду сразу к делу, имеется игровое поле на котором нанесены объекты, при достижении края поля объект должен...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
57
Ответ Создать тему
Новые блоги и статьи
[golang] Двоичная куча, min-heap
alhaos 20.05.2026
Двоичная куча Двоичная куча — структура данных, которая всегда держит самый важный элемент наготове. Представьте очередь к хилеру в игре, и очередь из игроков в приоритете те у кого меньше. . .
[golang] Breadth-First Search
alhaos 19.05.2026
BFS (Breadth-First Search) — это базовый алгоритм обхода графа в ширину, который поуровнево исследует все связанные вершины. Он начинает с выбранной точки и проверяет всех соседей, прежде чем. . .
[golang] Алгоритм «Хак Госпера»
alhaos 17.05.2026
Алгоритм «Хак Госпера» Хак Госпера (Gosper's Hack) — алгоритм нахождения следующего по величине числа с тем же количеством установленных бит. Придуман Биллом Госпером в 1970-х, опубликован в. . .
Рисование бинарного древа до 6-го колена на js, svg.
russiannick 17.05.2026
<svg width="335" height="240" viewBox="0 0 335 240" fill="#e5e1bb"> <style> <!]> </ style> <g id="bush"> </ g> </ svg> function fn(){ let rost;/ / высота древа let xx=165,yy=210,w=256;
FSharp: interface of module
DevAlt 16.05.2026
Интерфейс модуля F# позволяет управлять доступностью членов, содержащихся в реализации модуля. По-умолчанию все члены модуля доступны: module Foo let x = 10 let boo () = printfn "boo" . . .
Хитросплетение родственных связей пантеона греческих богов.
russiannick 14.05.2026
Однооконник, позволяющий узреть и изучить отдельных героев древней Греции. <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible". . .
[golang] Угол между стрелками часов
alhaos 12.05.2026
По заданным значениям часа и минуты необходимо определить значение меньшего угла между стрелками аналогового циферблата часов. import "math" func angleClock(hour int, minutes int) float64 { . . .
Debian 13: Установка Lazarus QT5
ВитГо 09.05.2026
Эта инструкция моя компиляция инструкций volvo https:/ / www. cyberforum. ru/ blogs/ 203668/ 10753. html и его же старой инструкции по установке Lazarus с gtk2. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru