{"id":328439,"date":"2022-01-23T15:00:18","date_gmt":"2022-01-23T15:00:18","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=328439"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=328439","title":{"rendered":"<span>\u0414\u0436\u0435\u043d\u0442\u0435\u043b\u044c\u043c\u0435\u043d\u0441\u043a\u0438\u0439 \u043d\u0430\u0431\u043e\u0440 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f WPF-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" height=\"650\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/358\/ef7\/654\/358ef7654ac5c440c391fe90705361a6.png\" data-width=\"1065\"\/><figcaption><\/figcaption><\/figure>\n<p><a class=\"anchor\" name=\"%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5\" id=\"\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435\"><\/a><\/p>\n<h2>\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0414\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043b\u0435\u0437\u043d\u0430 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c, \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 WPF. \u041e\u043d\u0430 \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e\u043c. \u0417\u0434\u0435\u0441\u044c \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u044b \u043b\u0438\u0448\u044c \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u044b \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u044b \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u043d\u0430 WPF. \u041f\u043e \u0441\u0443\u0442\u0438, \u0441\u0442\u0430\u0442\u044c\u044f \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u043d\u0430\u0431\u043e\u0440 \u0440\u0435\u0446\u0435\u043f\u0442\u043e\u0432 \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0445 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 WPF \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043e\u043f\u044b\u0442\u043d\u044b\u0435 WPF-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0432\u0440\u044f\u0434 \u043b\u0438 \u043d\u0430\u0439\u0434\u0443\u0442 \u0447\u0442\u043e-\u0442\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0435 \u0434\u043b\u044f \u0441\u0435\u0431\u044f. \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u043f\u0440\u0438\u0432\u043e\u0434\u044f\u0442\u0441\u044f \u0447\u0430\u0441\u0442\u0438 \u043a\u043e\u0434\u0430 \u0438\u0437 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0441\u043b\u0443\u0436\u0438\u0442 \u0434\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u043a\u043b\u0430\u043f\u0430\u043d\u0430 (\u043d\u0443\u0436\u043d\u043e \u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u044f \u0434\u0430\u0442\u0447\u0438\u043a\u043e\u0432 \u0434\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0438 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u044c \u0438\u0445 \u043d\u0430 \u044d\u043a\u0440\u0430\u043d). \u041e\u0442\u043c\u0435\u0447\u0443, \u0447\u0442\u043e \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b\u0435 \u043f\u0430\u043a\u0435\u0442\u044b \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0441 \u0446\u0435\u043b\u044c\u044e \u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439 \u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u044f.<\/p>\n<h2>\u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435<\/h2>\n<ul>\n<li>\n<p><a href=\"#%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5\" rel=\"noopener noreferrer nofollow\">\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#%D0%98%D0%BD%D1%84%D1%80%D0%B0%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0\" rel=\"noopener noreferrer nofollow\">\u0418\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430<\/a><\/p>\n<ul>\n<li>\n<p><a href=\"#%D0%98%D1%81%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D1%8F\" rel=\"noopener noreferrer nofollow\">\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#ioc\" rel=\"noopener noreferrer nofollow\">\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 IoC<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#%D0%BC%D0%B0%D0%BF%D0%BF%D0%B8%D0%BD%D0%B3\" rel=\"noopener noreferrer nofollow\">\u041c\u0430\u043f\u043f\u0438\u043d\u0433 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432<\/a><\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><a href=\"#MVVM\" rel=\"noopener noreferrer nofollow\">\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f MVVM &#8212; \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u0430<\/a><\/p>\n<ul>\n<li>\n<p><a href=\"#%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C\" rel=\"noopener noreferrer nofollow\">\u041c\u043e\u0434\u0435\u043b\u044c<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#%D0%BF%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5\" rel=\"noopener noreferrer nofollow\">\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#%D0%B2%D0%B0%D0%BB%D0%B8%D0%B4%D0%B0%D1%86%D0%B8%D1%8F\" rel=\"noopener noreferrer nofollow\">\u0412\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#%D0%BA%D0%BE%D0%BC%D0%B0%D0%BD%D0%B4%D1%8B\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u043c\u0430\u043d\u0434\u044b<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B5%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D0%B5\" rel=\"noopener noreferrer nofollow\">\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445<\/a><\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><a href=\"#%D0%92%D0%B8%D0%B7%D1%83%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%D1%81%D1%82%D0%B8%D0%BB%D1%8C\" rel=\"noopener noreferrer nofollow\">\u0412\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u043c\u044b \u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f<\/a><\/p>\n<ul>\n<li>\n<p><a href=\"#%D1%81%D1%82%D0%B8%D0%BB%D1%8C\" rel=\"noopener noreferrer nofollow\">\u0421\u0442\u0438\u043b\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%BA%D0%B8\" rel=\"noopener noreferrer nofollow\">\u0413\u0440\u0430\u0444\u0438\u043a\u0438<\/a><\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><a href=\"#%D0%B7%D0%B0%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5\" rel=\"noopener noreferrer nofollow\">\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/a><\/p>\n<\/li>\n<\/ul>\n<p><a class=\"anchor\" name=\"%D0%98%D0%BD%D1%84%D1%80%D0%B0%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0\" id=\"\u0418\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430\"><\/a><\/p>\n<h2>\u0418\u043d\u0444\u0440\u0430\u0441\u0442\u0443\u0440\u043a\u0442\u0443\u0440\u0430<\/h2>\n<p>\u041f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u043b\u043e\u043c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043d\u044b\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u042f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <a href=\"https:\/\/www.reactiveui.net\/\" rel=\"noopener noreferrer nofollow\">ReactiveUI<\/a> \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043e\u043d\u0430 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0441\u0442\u0435\u043f\u0435\u043d\u0438 \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 boilerplate-\u043a\u043e\u0434\u0430 \u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0432 \u0441\u0435\u0431\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0442\u0430\u043a\u0438\u0445, \u043a\u0430\u043a \u0432\u043d\u0443\u0442\u0440\u0438\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043d\u0430\u044f \u0448\u0438\u043d\u0430, \u043b\u043e\u0433\u0433\u0435\u0440, \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a \u0438 \u043f\u0440\u043e\u0447\u0435\u0435. \u041e\u0441\u043d\u043e\u0432\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043d\u0435\u043f\u043b\u043e\u0445\u043e \u0438\u0437\u043b\u043e\u0436\u0435\u043d\u044b <a href=\"https:\/\/habr.com\/ru\/post\/303650\/\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a>. ReactiveUI \u0438\u0441\u043f\u043e\u0432\u0435\u0434\u0443\u0435\u0442 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434, \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0432 \u0432\u0438\u0434\u0435 <a href=\"https:\/\/github.com\/dotnet\/reactive\" rel=\"noopener noreferrer nofollow\">Reactive Extensions<\/a>. \u041f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430 \u044f \u043e\u043f\u0438\u0448\u0443 \u043d\u0438\u0436\u0435 \u0432 <a href=\"#MVVM\" rel=\"noopener noreferrer nofollow\">\u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u0430 MVVM<\/a>.<\/p>\n<p><a class=\"anchor\" name=\"%D0%98%D1%81%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D1%8F\" id=\"\u0418\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f\"><\/a><\/p>\n<h3>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439<\/h3>\n<p>\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0439 exception handler, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0438\u0448\u0435\u0442 \u043e\u0448\u0438\u0431\u043a\u0438 c \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043b\u043e\u0433\u0433\u0435\u0440\u0430. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432 \u043a\u043b\u0430\u0441\u0441\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f App \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u043c\u0435\u0442\u043e\u0434 <code>OnStartup<\/code>, \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u043f\u0440\u0435\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0441\u043e\u0431\u044b\u0442\u0438\u044f <a href=\"https:\/\/docs.microsoft.com\/ru-ru\/dotnet\/api\/system.windows.application.startup?view=windowsdesktop-6.0\" rel=\"noopener noreferrer nofollow\">StartupEvent<\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0438\u0437 \u043c\u0435\u0442\u043e\u0434\u0430 <code>Application.Run<\/code><\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"cs\">public partial class App : Application { private readonly ILogger _logger;  public App() { Bootstrapper.BuildIoC(); \/\/ \u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c IoC  _logger = Locator.Current.GetService&lt;ILogger>(); }  private void LogException(Exception e, string source) { _logger?.Error($\"{source}: {e.Message}\", e); }  private void SetupExceptionHandling() { \/\/ \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u043d\u0430\u0448 Observer-\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 RxApp.DefaultExceptionHandler = new ApcExceptionHandler(_logger); }  protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); SetupExceptionHandling(); } }  public class ApcExceptionHandler: IObserver&lt;Exception> { private readonly ILogger _logger;  public ApcExceptionHandler(ILogger logger) { _logger = logger; }  public void OnCompleted() { if (Debugger.IsAttached) Debugger.Break(); }  public void OnError(Exception error) { if (Debugger.IsAttached) Debugger.Break(); _logger.Error($\"{error.Source}: {error.Message}\", error); }  public void OnNext(Exception value) { if (Debugger.IsAttached) Debugger.Break();  _logger?.Error($\"{value.Source}: {value.Message}\", value); } }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041b\u043e\u0433\u0433\u0435\u0440 \u043f\u0438\u0448\u0435\u0442 \u0432 \u0444\u0430\u0439\u043b \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/nlog-project.org\/\" rel=\"noopener noreferrer nofollow\">NLog<\/a> \u0438 \u0432\u043e \u0432\u043d\u0443\u0442\u0440\u0438\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043d\u0443\u044e \u0448\u0438\u043d\u0443 <code>MessageBus<\/code>, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043c\u043e\u0433\u043b\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u044c \u043b\u043e\u0433\u0438 \u0432 UI<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"cs\">public class AppLogger: ILogger {    \/\/\u042d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043b\u043e\u0433\u0433\u0435\u0440\u0430 NLog private NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();       public AppLogger()   {   }       public void Info(string message)      {         _logger.Info(message);           MessageBus.Current.SendMessage(new ApplicationLog(message));      }         public void Error(string message, Exception exception = null)   {         _logger.Error(exception, message);     \/\/\u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432 \u0448\u0438\u043d\u0443     MessageBus.Current.SendMessage(new ApplicationLog(message));      } }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041d\u0435\u043e\u0431\u043e\u0445\u043e\u0434\u0438\u043c\u043e, \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 ReactiveUI <a href=\"https:\/\/www.reactiveui.net\/docs\/handbook\/message-bus\/\" rel=\"noopener noreferrer nofollow\">\u0441\u043e\u0432\u0435\u0442\u0443\u044e\u0442<\/a> \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432 <code>MessageBus<\/code> \u0432 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u0442\u0430\u043a \u043a\u0430\u043a <code>MessageBus<\/code> &#8212; \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u0430\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u043e\u0442\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u043c \u043c\u0435\u0441\u0442\u043e\u043c \u0443\u0442\u0435\u0447\u0435\u043a \u043f\u0430\u043c\u044f\u0442\u0438. \u041f\u0440\u043e\u0441\u043b\u0443\u0448\u0438\u0432\u0430\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438\u0437 \u0448\u0438\u043d\u044b \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u043c\u0435\u0442\u043e\u0434\u043e\u043c <code>MessugeBus.Current.Listen<\/code><\/p>\n<pre><code class=\"cs\">MessageBus.Current.Listen&lt;ApplicationLog>().ObserveOn(RxApp.MainThreadScheduler).Subscribe(Observer.Create&lt;ApplicationLog>((log) => { LogContent += logMessage; }));<\/code><\/pre>\n<p><a class=\"anchor\" name=\"ioc\" id=\"ioc\"><\/a><\/p>\n<h3>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 IoC<\/h3>\n<p>\u0414\u0430\u043b\u0435\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043c IoC, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0431\u043b\u0435\u0433\u0447\u0438\u0442 \u043d\u0430\u043c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0436\u0438\u0437\u0435\u043d\u043d\u044b\u043c \u0446\u0438\u043a\u043b\u043e\u043c \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432. ReactiveUI \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 <a href=\"https:\/\/github.com\/reactiveui\/splat\" rel=\"noopener noreferrer nofollow\">Splat<\/a>. \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0432\u044b\u0437\u043e\u0432\u0430 \u043c\u0435\u0442\u043e\u0434\u0430 <code>Register()<\/code> \u043f\u043e\u043b\u044f <code>Locator.CurrentMutable<\/code>, \u0430 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 &#8212; <code>GetService()<\/code> \u043f\u043e\u043b\u044f <code>Locator.Current<\/code>. <br \/>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:<\/p>\n<pre><code class=\"cs\">Locator.CurrentMutable.Register(() => new AppLogger(), typeof(ILogger)); var logger = Locator.Current.GetService&lt;ILogger>();<\/code><\/pre>\n<p>\u041f\u043e\u043b\u0435 <code>Locator.Current<\/code> \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043e \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 DI\/IoC \u0434\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0445 Splat \u0438\u043c\u0435\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u043a\u0435\u0442\u044b. \u042f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e Autofac c \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0430\u043a\u0435\u0442\u0430 <a href=\"https:\/\/github.com\/reactiveui\/splat\/tree\/main\/src\/Splat.Autofac\" rel=\"noopener noreferrer nofollow\">Splat.Autofac<\/a>. \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044e \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0432\u044b\u043d\u0435\u0441 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441.<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"cs\">public static class Bootstrapper { public static void BuildIoC() { \/*  * \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 Autofac.  * \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u044b \u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f  *\/ var builder = new ContainerBuilder(); RegisterServices(builder); RegisterViews(builder); \/\/ \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c Autofac \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0432 Splat var autofacResolver = builder.UseAutofacDependencyResolver(); builder.RegisterInstance(autofacResolver);  \/\/ \u0412\u044b\u0437\u044b\u0432\u0430\u0435\u043c InitializeReactiveUI(), \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0439 Service Locator autofacResolver.InitializeReactiveUI(); var lifetimeScope = builder.Build(); autofacResolver.SetLifetimeScope(lifetimeScope); }  private static void RegisterServices(ContainerBuilder builder) { builder.RegisterModule(new ApcCoreModule()); builder.RegisterType&lt;AppLogger>().As&lt;ILogger>(); \/\/ \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u043f\u0440\u043e\u0444\u0438\u043b\u0438 ObjectMapper \u043f\u0443\u0442\u0435\u043c \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u0431\u043e\u0440\u043a\u0438 var typeAdapterConfig = TypeAdapterConfig.GlobalSettings; typeAdapterConfig.Scan(Assembly.GetExecutingAssembly()); }  private static void RegisterViews(ContainerBuilder builder) { builder.RegisterType&lt;MainWindow>().As&lt;IViewFor&lt;MainWindowViewModel>>(); builder.RegisterType&lt;MessageWindow>().As&lt;IViewFor&lt;&lt;MessageWindowViewModel>>().AsSelf(); builder.RegisterType&lt;MainWindowViewModel>(); builder.RegisterType&lt;MessageWindowViewModel>(); } }<\/code><\/pre>\n<\/div>\n<\/details>\n<p><a class=\"anchor\" name=\"%D0%BC%D0%B0%D0%BF%D0%BF%D0%B8%D0%BD%D0%B3\" id=\"\u043c\u0430\u043f\u043f\u0438\u043d\u0433\"><\/a><\/p>\n<h3>\u041c\u0430\u043f\u043f\u0438\u043d\u0433 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432<\/h3>\n<p>\u041c\u0430\u043f\u043f\u0435\u0440 \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u043d\u0430\u043c \u043c\u0438\u043d\u0438\u043c\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u0434 \u043f\u043e \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u044e \u043e\u0434\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0432 \u0434\u0440\u0443\u0433\u043e\u0439. \u042f \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0441\u044f \u043f\u0430\u043a\u0435\u0442\u043e\u043c <a href=\"https:\/\/github.com\/MapsterMapper\/Mapster\" rel=\"noopener noreferrer nofollow\">Mapster<\/a>. \u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0438\u043c\u0435\u0435\u0442 FluetAPI, \u043b\u0438\u0431\u043e \u0430\u0442\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u043a \u043a\u043b\u0430\u0441\u0441\u0430\u043c \u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043a\u043e\u0434\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044e \u043c\u0430\u043f\u043f\u0438\u043d\u0433\u0430 \u043d\u0430 \u0441\u0442\u0430\u0434\u0438\u0438 \u0441\u0431\u043e\u0440\u043a\u0438, \u0447\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u043e\u043a\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0434\u043d\u0438\u0445 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0432 \u0434\u0440\u0443\u0433\u0438\u0435. \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044e \u044f \u0440\u0435\u0448\u0438\u043b \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u043e\u043b\u0436\u0435\u043d \u0440\u0435\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <code>IRegister<\/code>:<\/p>\n<pre><code class=\"cs\">public class ApplicationMapperRegistration: IRegister { public void Register(TypeAdapterConfig config) { config.NewConfig&lt;IPositionerDevice, DeviceViewModel>() .ConstructUsing(src => new DeviceViewModel(src.Mode, src.IsConnected, src.DeviceId, src.Name)); config.NewConfig&lt;DeviceIndicators, DeviceViewModel>(); } }<\/code><\/pre>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u0441 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043e\u0439 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0432\u0441\u0451. \u0414\u0440\u0443\u0433\u0438\u0445 \u043c\u043e\u043c\u0435\u043d\u0442\u043e\u0432 \u0437\u0430\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u044e\u0449\u0438\u0445 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u044f \u043d\u0435 \u043d\u0430\u0448\u0451\u043b. \u0414\u0430\u043b\u0435\u0435 \u043e\u043f\u0438\u0448\u0443 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u043c\u0435\u043d\u0442\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 UI \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p><a class=\"anchor\" name=\"MVVM\" id=\"MVVM\"><\/a><\/p>\n<h2>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f MVVM &#8212; \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u0430<\/h2>\n<p><a class=\"anchor\" name=\"%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C\" id=\"\u043c\u043e\u0434\u0435\u043b\u044c\"><\/a><\/p>\n<p>\u041a\u0430\u043a \u044f \u043f\u0438\u0441\u0430\u043b \u0432\u044b\u0448\u0435, \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e <a href=\"https:\/\/www.reactiveui.net\/\" rel=\"noopener noreferrer nofollow\">ReactivUI<\/a>, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0449\u0438\u0439 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 UI \u0432 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u0442\u0438\u043b\u0435. \u041d\u0438\u0436\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043c\u043e\u043c\u0435\u043d\u0442\u044b \u043f\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044e \u043a\u043e\u0434\u0430 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439.<\/p>\n<h3>\u041c\u043e\u0434\u0435\u043b\u044c<\/h3>\n<p>\u041a\u043b\u0430\u0441\u0441\u044b \u043c\u043e\u0434\u0435\u043b\u0435\u0439, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u0432 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u0445, \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u044e\u0442\u0441\u044f \u043e\u0442 <code>ReactiveObject<\/code>. \u0415\u0441\u0442\u044c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"https:\/\/github.com\/Fody\/Fody\" rel=\"noopener noreferrer nofollow\">Fody<\/a>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0430\u0442\u0442\u0440\u0438\u0431\u0443\u0442\u0430 <code>Reactive<\/code> \u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u043c\u043e\u0434\u0435\u043b\u0438 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u043c\u0438. \u041c\u043e\u0436\u043d\u043e \u0438 \u0431\u0435\u0437 \u043d\u0435\u0435, \u043d\u043e \u043f\u043e \u043c\u043e\u0435\u043c\u0443 \u043c\u043d\u0435\u043d\u0438\u044e, \u043e\u043d\u0430 \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043a\u043e\u0434 \u0431\u043e\u043b\u0435\u0435 \u0447\u0438\u0442\u0430\u0435\u043c \u0437\u0430 \u0441\u0447\u0451\u0442 \u0441\u043e\u043a\u0440\u0430\u0449\u0435\u043d\u0438\u044f boilerplate-\u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0439. \u0421\u0432\u044f\u0437\u044b\u0432\u0430\u043d\u0438\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432 \u043c\u043e\u0434\u0435\u043b\u0438 \u0441\u043e \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u043b\u0438\u0431\u043e \u0432 XML \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0435, \u043b\u0438\u0431\u043e \u0432 \u043a\u043e\u0434\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u043e\u0432. <br \/>\u041d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u043c\u043e\u0434\u0435\u043b\u0438 \u043a\u043b\u0430\u043f\u0430\u043d\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u044f \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u0434\u0430\u0442\u0447\u0438\u043a\u043e\u0432.<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"cs\">public class DeviceViewModel: ReactiveObject {     public DeviceViewModel()   {   }         [Reactive]   public float Current { get; set; }         [Reactive]      public float Pressure { get; set; }       [Reactive]      public float Position { get; set; }       [Reactive]      public DateTimeOffset DeviceTime { get; set; }  [Reactive] public bool Connected { get; set; }  public ReactiveCommand&lt;Unit, bool> ConnectToDevice; public readonly ReactiveCommand&lt;float, float> SetValvePosition; }  <\/code><\/pre>\n<\/div>\n<\/details>\n<p><a class=\"anchor\" name=\"%D0%BF%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5\" id=\"\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\"><\/a><\/p>\n<h3>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f  \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f<\/h3>\n<p>\u0412 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u043b\u0435\u043d\u0438\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u043a\u043e\u043c\u0430\u043d\u0434 \u0438 \u043f\u043e\u043b\u044f \u043c\u043e\u0434\u0435\u043b\u0438 \u043a \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"cs\">public partial class MainWindow { public MainWindow() { InitializeComponent();  ViewModel = Locator.Current.GetService&lt;DeviceViewModel>(); DataContext = ViewModel;  \/*  * \u0414\u0430\u043d\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u043c\u043e\u0434\u0435\u043b\u0438 \u043a \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043c \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f  * DisposeWith \u0432 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c \u0434\u043b\u044f \u043e\u0447\u0438\u0441\u0442\u043a\u0438 \u043f\u0440\u0438\u0432\u044f\u0437\u043e\u043a \u043f\u0440\u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f  *\/ this.WhenActivated(disposable => { \/*  * \u041f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 Text \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 TextBox \u043a \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u043c\u043e\u0434\u0435\u043b\u0438.  * OneWayBind - \u043e\u0434\u043d\u043e\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u0430\u044f \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0430, Bind - \u0434\u0432\u0443\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u0430\u044f  *\/ this.OneWayBind(ViewModel, vm => vm.Pressure, v => v.Pressure1Indicator.Text) .DisposeWith(disposable);          \/\/ \u0414\u0432\u0443\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u0430\u044f \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u043e\u0437\u0438\u0446\u0438\u0438 \u043a\u043b\u0430\u043f\u0430\u043d\u0430. \u041a\u043e\u043d\u0432\u0435\u0440\u0442\u043e\u0440\u044b \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u043c\u043e\u0434\u0435\u043b\u0438 \u0438 \u0432 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0438: FloatToStringConverter, StringToFloatConverter this.Bind(ViewModel, vm => vm.Position, v => v.Position.Text, FloatToStringConverter, StringToFloatConverter) .DisposeWith(disposable); this.OneWayBind(ViewModel, vm => vm.Current, v => v.Current.Text) .DisposeWith(disposable); this.OneWayBind(ViewModel, vm => vm.ConnectedDevice.DeviceTime, v => v.DeviceDate.SelectedDate, val => val.Date) .DisposeWith(disposable); this.OneWayBind(ViewModel, vm => vm.ConnectedDevice.DeviceTime, v => v.DeviceTime.SelectedTime, val => val.DateTime) .DisposeWith(disposable);          \/* \u041f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u043a\u043e\u043c\u0430\u043d\u0434 \u043a \u043a\u043d\u043e\u043f\u043a\u0430\u043c *\/ this.BindCommand(ViewModel, vm => vm.ConnectToDevice, v => v.ConnectDevice, nameof(ConnectDevice.Click)) .DisposeWith(disposable); this.BindCommand(ViewModel, vm => vm.SetValvePosition, v => v.SetValvePosition, vm => vm.ConnectedDevice.AssignedPosition, nameof(SetValvePosition.Click)) .DisposeWith(disposable); }); }  private string FloatToStringConverter(float value) { return value.ToString(\"F2\", CultureInfo.InvariantCulture); }    private float StringToFloatConverter(string input) { float result;  if (!float.TryParse(input, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) { result = 0; }  return result; } }<\/code><\/pre>\n<\/div>\n<\/details>\n<p><a class=\"anchor\" name=\"%D0%B2%D0%B0%D0%BB%D0%B8%D0%B4%D0%B0%D1%86%D0%B8%D1%8F\" id=\"\u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f\"><\/a><\/p>\n<h3>\u0412\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f<\/h3>\n<p>\u0412\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u043c\u043e\u0434\u0435\u043b\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u0443\u0442\u0435\u043c \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044f \u043a\u043b\u0430\u0441\u0441\u0430 \u043e\u0442 <code>ReactiveValidationObject<\/code>, \u0432 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u0440\u0430\u0432\u0438\u043b\u043e \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:<\/p>\n<pre><code class=\"cs\">this.ValidationRule(e => e.Position, val => float.TryParse(val, NumberStyles.Float, CultureInfo.InvariantCulture, out _), \"\u0414\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u0432\u043e\u0434 \u0446\u0438\u0444\u0440\");<\/code><\/pre>\n<p>\u0414\u043b\u044f \u0432\u044b\u0432\u043e\u0434\u0430 \u043e\u0448\u0438\u0431\u043e\u043a \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044f \u0432 UI \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0443 \u0432 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0438, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u043a \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0443 <code>TextBlock:<\/code><\/p>\n<pre><code>&lt;TextBlock x:Name=\"ValidationErrors\" FontSize=\"10\" Foreground=\"Red\"\/><\/code><\/pre>\n<pre><code class=\"cs\">this.BindValidation(ViewModel, v => v.Position, v => v.ValidationErrors.Text) .DisposeWith(disposable); \/\/ \u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438 \u043e\u0448\u0438\u0431\u043a\u0438 this.WhenAnyValue(x => x.ValidationErrors.Text, text => !string.IsNullOrWhiteSpace(text)) .BindTo(this, x => x.ValidationErrors.Visibility) .DisposeWith(disposable);<\/code><\/pre>\n<p><a class=\"anchor\" name=\"%D0%BA%D0%BE%D0%BC%D0%B0%D0%BD%D0%B4%D1%8B\" id=\"\u043a\u043e\u043c\u0430\u043d\u0434\u044b\"><\/a><\/p>\n<h3>\u041a\u043e\u043c\u0430\u043d\u0434\u044b<\/h3>\n<p>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 UI \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e, \u043a\u043e\u043c\u0430\u043d\u0434. \u0418\u0445 \u0440\u0430\u0431\u043e\u0442\u0430 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0445\u043e\u0440\u043e\u0448\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u0430 <a href=\"https:\/\/habr.com\/ru\/post\/305350\/\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a>, \u044f \u043b\u0438\u0448\u044c \u043f\u0440\u0438\u0432\u0435\u0434\u0443 \u043f\u0440\u0438\u043c\u0435\u0440. \u041f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u043a \u0441\u043e\u0431\u044b\u0442\u0438\u044e \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u0430 \u0432\u044b\u0448\u0435 \u0432 \u043a\u043b\u0430\u0441\u0441\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f. \u0421\u0430\u043c\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"cs\">ConnectToDevice = ReactiveCommand.CreateFromTask(async () => { bool isAuthorized = await Authorize.Execute();  return isAuthorized; }, this.WhenAnyValue(e => e.CanConnect));  \/* \u041d\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u0442\u044c\u0441\u044f \u043a\u0430\u043a \u0438 \u043d\u0430 \u043b\u044e\u0431\u043e\u0439 Observable \u043e\u0431\u044a\u0435\u043a\u0442.    \u041f\u043e\u0441\u043b\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 \u0447\u0438\u0442\u0430\u0435\u043c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0438 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u044f \u0441\u0435\u043d\u0441\u043e\u0440\u043e\u0432. *\/ ConnectToDevice .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(async result => { ConnectedDevice.IsConnected = result; await ReadDeviceInfo.Execute(); await ReadDeviceIndicators.Execute(); });<\/code><\/pre>\n<p>\u041c\u0435\u0442\u043e\u0434 <code>CreateFromTask<\/code> \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u043a\u0430\u043a \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u043a \u043a\u043b\u0430\u0441\u0441\u0443 <code>ReactiveCommand<\/code> \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0430\u043a\u0435\u0442\u0430 <a href=\"https:\/\/www.nuget.org\/packages\/System.Reactive.Linq\" rel=\"noopener noreferrer nofollow\">System.Reactive.Linq<\/a><strong><br \/><\/strong><code>\u0421anConnect<\/code> &#8212; \u0444\u043b\u0430\u0433 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u0439 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u044b<\/p>\n<pre><code class=\"cs\">_canConnect = this.WhenAnyValue(e => e.SelectedDevice, e => e.IsCommandExecuting, (device, isExecuting) => device!=null &amp;&amp; !isExecuting) .ToProperty(this, e => e.CanConnect);<\/code><\/pre>\n<pre><code class=\"cs\">public bool CanExecuteCommand => _canExecuteCommand?.Value == true; private readonly ObservableAsPropertyHelper&lt;bool> _canConnect; public bool CanConnect => _canConnect?.Value == true;<\/code><\/pre>\n<p>\u0418\u043d\u043e\u0433\u0434\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442\u044c Observable &#8212; \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0432 \u043e\u0434\u0438\u043d. \u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u044d\u0442\u043e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>Observable.Merge<\/code><\/p>\n<pre><code class=\"cs\">\/* \u0422\u0443\u0442 \u043c\u044b \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u043b\u0438 \u0444\u043b\u0430\u0433\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u043c\u0430\u043d\u0434, \u0447\u0442\u043e\u0431\u044b \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u0435\u043d\u0438\u0435 \u043b\u044e\u0431\u043e\u0439 \u0438\u0437 \u043d\u0438\u0445 \u0447\u0435\u0440\u0435\u0437 \u0444\u043b\u0430\u0433IsCommandExecuting  *\/ _isCommandExecuting = Observable.Merge(SetValvePosition.IsExecuting, ConnectToDevice.IsExecuting, Authorize.IsExecuting, ReadDeviceIndicators.IsExecuting, ReadDeviceInfo.IsExecuting, PingDevice.IsExecuting) .ToProperty(this, e => e.IsCommandExecuting );<\/code><\/pre>\n<p><a class=\"anchor\" name=\"%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B5%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D0%B5\" id=\"\u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0435\u0434\u0430\u043d\u043d\u044b\u0435\"><\/a><\/p>\n<h3>\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445<\/h3>\n<p>\u0411\u044b\u0432\u0430\u044e\u0442 \u0441\u043b\u0443\u0447\u0430\u0438, \u043a\u043e\u0433\u0434\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0442\u0430\u0431\u043b\u0438\u0447\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 <code>DataGrid<\/code> \u0441 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f. <code>ReactiveCollection<\/code> \u0432 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043d\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442, \u0442\u0430\u043a \u043a\u0430\u043a \u043d\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u043e\u0431 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438. \u0412 ReactiveUI \u0438 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u043b\u0443\u0447\u0430\u044f \u0435\u0441\u0442\u044c <a href=\"https:\/\/www.reactiveui.net\/docs\/handbook\/collections\/\" rel=\"noopener noreferrer nofollow\">\u0440\u0435\u0448\u0435\u043d\u0438\u0435<\/a>. \u0412 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0435 \u0435\u0441\u0442\u044c \u0434\u0432\u0430 \u043a\u043b\u0430\u0441\u0441\u0430 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0439:<\/p>\n<p>1. \u041e\u0431\u044b\u0447\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a <code>SourceList&lt;T><\/code>  <br \/>2. \u0421\u043b\u043e\u0432\u0430\u0440\u044c <code>SourceCache&lt;TObject, TKey><\/code>  <\/p>\n<p>\u042d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u043a\u043b\u0430\u0441\u0441\u043e\u0432 \u0445\u0440\u0430\u043d\u044f\u0442 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438 \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u043c\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435. \u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u0443\u0431\u043b\u0438\u043a\u0443\u044e\u0442\u0441\u044f \u043a\u0430\u043a <code>IObservable&lt;ChangeSet><\/code>, <code>ChangeSet<\/code>&#8212; \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0431 \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u043c\u044b\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0445. \u0414\u043b\u044f \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0432 <code>IObservable&lt;ChangeSet><\/code>  \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 <code>Connect<\/code>. \u0412 \u0441\u0432\u043e\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u044f \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0432 \u0432\u0438\u0434\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u0431 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435: \u0432\u0435\u0440\u0441\u0438\u044f \u043f\u0440\u043e\u0448\u0438\u0432\u043a\u0438, id \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u0434\u0430\u0442\u0430 \u043a\u0430\u043b\u0438\u0431\u0440\u043e\u0432\u043a\u0438 \u0438 \u043f\u0440\u043e\u0447\u0435\u0435.<\/p>\n<p>\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435:<\/p>\n<pre><code class=\"cs\">this.OneWayBind(ViewModel, vm => vm.ConnectedDevice.DeviceInfo, v => v.DeviceInfo.ItemsSource)   .DisposeWith(disposable);<\/code><\/pre>\n<pre><code class=\"xml\">&lt;DataGrid x:Name=\"DeviceInfo\" AutoGenerateColumns=\"False\" Margin=\"0,0,0,3\" Background=\"Transparent\" CanUserAddRows=\"False\" HeadersVisibility=\"None\">  &lt;DataGrid.Columns>     &lt;DataGridTextColumn Binding=\"{Binding Key}\" FontWeight=\"Bold\" IsReadOnly=\"True\"\/>     &lt;DataGridTextColumn Binding=\"{Binding Value}\" IsReadOnly=\"True\"\/>   &lt;\/DataGrid.Columns> &lt;\/DataGrid><\/code><\/pre>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438 \u0434\u043b\u044f \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438<\/p>\n<pre><code class=\"cs\">public ReadOnlyObservableCollection&lt;VariableInfo> DeviceInfoBind; public SourceCache&lt;VariableInfo, string> DeviceInfoSource = new(e => e.Key);<\/code><\/pre>\n<p>\u0412 \u043c\u043e\u0434\u0435\u043b\u0438 \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043a \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438:<\/p>\n<pre><code class=\"cs\">ConnectedDevice.DeviceInfoSource .Connect() .ObserveOn(RxApp.MainThreadScheduler) .Bind(out ConnectedDevice.DeviceInfoBind) .Subscribe();<\/code><\/pre>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u043c \u043e\u0431\u0437\u043e\u0440 MVVM &#8212; \u0440\u0435\u0446\u0435\u043f\u0442\u043e\u0432 \u0438 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0440\u0438\u044f\u0442\u043d\u0435\u0435 UI \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p><a class=\"anchor\" name=\"%D0%92%D0%B8%D0%B7%D1%83%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%D1%81%D1%82%D0%B8%D0%BB%D1%8C\" id=\"\u0412\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u044b\u0439\u0441\u0442\u0438\u043b\u044c\"><\/a><\/p>\n<h2>\u0412\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u043c\u044b \u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f<\/h2>\n<p><a class=\"anchor\" name=\"%D1%81%D1%82%D0%B8%D0%BB%D1%8C\" id=\"\u0441\u0442\u0438\u043b\u044c\"><\/a><\/p>\n<h3>\u0421\u0442\u0438\u043b\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/h3>\n<p>\u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0442 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a  \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u044b\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 \u043a\u0430\u043a \u043f\u043b\u0430\u0442\u043d\u044b\u0445, \u0442\u0430\u043a \u0438 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b\u0445. \u042f \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0441\u044f \u043d\u0430 <a href=\"https:\/\/github.com\/MaterialDesignInXAML\/MaterialDesignInXamlToolkit\" rel=\"noopener noreferrer nofollow\">Material Design In XAML Toolkit<\/a> + <a href=\"https:\/\/spiegelp.github.io\/MaterialDesignExtensions\/\" rel=\"noopener noreferrer nofollow\">Material Design Extensions<\/a> \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043e\u043d\u0438 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b \u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u0430, \u0438 \u0432 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0435, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441\u043e\u0431\u043e\u0439 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0434\u043b\u044f \u043c\u043e\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u0414\u0430\u043d\u043d\u044b\u0439 \u043f\u0430\u043a\u0435\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u043d\u0430\u0431\u043e\u0440 \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u044b\u0445 \u0441\u0442\u0438\u043b\u0435\u0439 Materail Design \u0434\u043b\u044f \u0431\u0430\u0437\u043e\u0432\u044b\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f. \u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0441\u043a\u0443\u0434\u043d\u043e\u0432\u0430\u0442\u0430, \u043d\u043e \u0435\u0441\u0442\u044c \u0434\u0435\u043c\u043e &#8212; \u043f\u0440\u043e\u0435\u043a\u0442 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e, \u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u043a\u0430\u043a \u0438 \u0447\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442. \u0427\u0442\u043e\u0431\u044b \u0432\u0441\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u043e \u0442\u0435\u043c\u044b \u0438\u0437 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0442\u0443\u043b\u043a\u0438\u0442\u0430 \u043d\u0443\u0436\u043d\u043e \u0432 \u0440\u0435\u0441\u0443\u0440\u0441\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u0442\u0438\u043b\u0438:<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"xml\">&lt;Application x:Class=\"Apc.Application2.App\"                           xmlns=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\/presentation\"                           xmlns:x=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\"                           xmlns:materialDesign=\"http:\/\/materialdesigninxaml.net\/winfx\/xaml\/themes\"                           StartupUri=\"Views\/MainWindow.xaml\">       &lt;Application.Resources> &lt;ResourceDictionary> &lt;ResourceDictionary.MergedDictionaries> &lt;!-- \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0442\u0435\u043c\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u0441\u0442\u0438\u043b\u0438 \u0438\u0437 Material Design Extensions --> &lt;ResourceDictionary Source=\"pack:\/\/application:,,,\/MaterialDesignThemes.Wpf;component\/Themes\/Generic.xaml\" \/> &lt;ResourceDictionary Source=\"pack:\/\/application:,,,\/MaterialDesignThemes.Wpf;component\/Themes\/MaterialDesignTheme.Defaults.xaml\" \/> &lt;ResourceDictionary Source=\"pack:\/\/application:,,,\/MaterialDesignExtensions;component\/Themes\/Generic.xaml\" \/> &lt;ResourceDictionary Source=\"pack:\/\/application:,,,\/MaterialDesignExtensions;component\/Themes\/MaterialDesignLightTheme.xaml\" \/>  &lt;!-- \u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0435 \u0446\u0432\u0435\u0442\u043e\u0432\u044b\u0435 \u0441\u0442\u0438\u043b\u0438 --> &lt;ResourceDictionary> &lt;ResourceDictionary.MergedDictionaries> &lt;ResourceDictionary Source=\"pack:\/\/application:,,,\/MaterialDesignColors;component\/Themes\/MaterialDesignColor.Blue.xaml\" \/> &lt;\/ResourceDictionary.MergedDictionaries> &lt;SolidColorBrush x:Key=\"PrimaryHueLightBrush\" Color=\"{StaticResource Primary100}\" \/> &lt;SolidColorBrush x:Key=\"PrimaryHueLightForegroundBrush\" Color=\"{StaticResource Primary100Foreground}\" \/> &lt;SolidColorBrush x:Key=\"PrimaryHueMidBrush\" Color=\"{StaticResource Primary500}\" \/> &lt;SolidColorBrush x:Key=\"PrimaryHueMidForegroundBrush\" Color=\"{StaticResource Primary500Foreground}\" \/> &lt;SolidColorBrush x:Key=\"PrimaryHueDarkBrush\" Color=\"{StaticResource Primary600}\" \/> &lt;SolidColorBrush x:Key=\"PrimaryHueDarkForegroundBrush\" Color=\"{StaticResource Primary600Foreground}\" \/> &lt;\/ResourceDictionary>   &lt;\/ResourceDictionary.MergedDictionaries>                  &lt;\/ResourceDictionary>            &lt;\/Application.Resources> &lt;\/Application><\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u043c\u0438\u043c\u043e \u044d\u0442\u043e\u0433\u043e \u043d\u0443\u0436\u043d\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043b\u0438 \u043a\u043b\u0430\u0441\u0441 MaterialWindow. \u042f \u0434\u043e\u0431\u0430\u0432\u0438\u043b \u043d\u043e\u0432\u044b\u0439 \u0441\u0432\u043e\u0439 \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441<code>MaterialReactiveWindow<\/code><\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"cs\">public class MaterialReactiveWindow&lt;TViewModel> : MaterialWindow, IViewFor&lt;TViewModel> where TViewModel : class { \/\/\/ &lt;summary> \/\/\/ \u0421\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043c\u043e\u0434\u0435\u043b\u044c \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \/\/\/ &lt;\/summary> public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register( \"ViewModel\", typeof(TViewModel), typeof(ReactiveWindow&lt;TViewModel>), new PropertyMetadata(null));  public TViewModel? BindingRoot => ViewModel;  public TViewModel? ViewModel { get => (TViewModel)GetValue(ViewModelProperty); set => SetValue(ViewModelProperty, value); }  object? IViewFor.ViewModel { get => ViewModel; set => ViewModel = (TViewModel?)value; } }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0412 XAML &#8212; \u0444\u0430\u0439\u043b\u0430\u0445 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 Material Design \u0438 Material Design Extensions:<\/p>\n<pre><code class=\"xml\">xmlns:md=\"http:\/\/materialdesigninxaml.net\/winfx\/xaml\/themes\" xmlns:mde=\"clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions\"<\/code><\/pre>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0438\u0437 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438:<\/p>\n<pre><code class=\"xml\">&lt;!-- BusyOverlay, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u0435\u043b\u0430\u0435\u0442 \u043e\u043a\u043d\u043e \u043d\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u043c \u0438 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u0437\u043d\u0430\u0447\u043e\u043a \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0434\u043e\u043b\u0433\u043e\u0438\u0433\u0440\u0430\u044e\u0449\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u044b --> --> &lt;mde:BusyOverlay x:Name=\"BusyOverlay\">&lt;\/mde:BusyOverlay> &lt;!-- TimePicker \u0438\u0437 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 --> &lt;md:TimePicker x:Name=\"DeviceTime\"\/> &lt;!-- \u0412 \u043a\u043d\u043e\u043f\u043a\u0435 \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u044b   \u0432 \u0432\u0438\u0434\u0435 \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u0430 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 ButtonProgressAssist.      \u0414\u043b\u044f \u0434\u0430\u043d\u043d\u043e\u0439 \u043a\u043d\u043e\u043f\u043a\u0438 \u043c\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e \u043f\u043e\u043a\u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u0441\u0435\u043d\u0441\u043e\u0440\u043e\u0432 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. --> &lt;Button x:Name=\"RefreshIndicators\" md:ButtonProgressAssist.Value=\"-1\"     md:ButtonProgressAssist.IsIndicatorVisible=\"{Binding Path=IsCommandExecuting}\"     md:ButtonProgressAssist.IsIndeterminate=\"True\">   &lt;Button.Content>     &lt;!-- \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0438\u043a\u043e\u043d\u043a\u0443 \u0434\u043b\u044f \u043a\u043d\u043e\u043f\u043a\u0438 \u0438\u0437 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 -->     &lt;md:PackIcon Kind=\"Refresh\" \/>   &lt;\/Button.Content> &lt;\/Button><\/code><\/pre>\n<p><a class=\"anchor\" name=\"%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%BA%D0%B8\" id=\"\u0433\u0440\u0430\u0444\u0438\u043a\u0438\"><\/a><\/p>\n<h3>\u0413\u0440\u0430\u0444\u0438\u043a\u0438<\/h3>\n<p>\u041c\u043d\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0431\u044b\u043b\u0430 \u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0446\u0430\u0446\u0438\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u0442\u0435\u043a\u0443\u0449\u0438\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0434\u0430\u0442\u0447\u0438\u043a\u043e\u0432 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438. \u041f\u043e\u0441\u043b\u0435 \u043e\u0431\u0437\u043e\u0440\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0433\u0440\u0430\u0444\u0438\u043a\u043e\u0432 \u044f \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0441\u044f \u043d\u0430 <a href=\"https:\/\/github.com\/ScottPlot\/ScottPlot\" rel=\"noopener noreferrer nofollow\">ScottPlot<\/a> \u0438 <a href=\"https:\/\/github.com\/beto-rodriguez\/LiveCharts2\" rel=\"noopener noreferrer nofollow\">LiveCharts2<\/a>. \u041e\u0431\u0430 \u043f\u0430\u043a\u0435\u0442\u0430 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0432\u0438\u0434\u044b \u0433\u0440\u0430\u0444\u0438\u043a\u043e\u0432 \u0438 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c \u043e\u0442 \u043b\u0438\u043d\u0438\u0439 \u0434\u043e \u043a\u0440\u0443\u0433\u043e\u0432\u044b\u0445 \u0434\u0438\u0430\u0433\u0440\u0430\u043c \u0438 \u044f\u043f\u043e\u043d\u0441\u043a\u0438\u0445 \u0441\u0432\u0435\u0447. \u041f\u0440\u0438\u0447\u0435\u043c \u0432 ScottPlot \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441 \u0433\u0440\u0430\u0444\u0438\u043a\u043e\u043c (\u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u0435 \u0438 \u043f\u0440.) \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u043e-\u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0431\u0435\u0437 \u0432\u0441\u044f\u043a\u043e\u0433\u043e \u0442\u044e\u043d\u0438\u043d\u0433\u0430. \u041d\u043e \u0432 \u043d\u0435\u0439 \u043c\u043d\u0435 \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c Realtime \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u0433\u0440\u0430\u0444\u0438\u043a\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u0432 \u0438\u0442\u043e\u0433\u0435 \u043f\u0440\u0438\u0448\u0435\u043b \u043a LiveChart2. \u0414\u0430\u043d\u043d\u0430\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0438\u043c\u0435\u0435\u0442 \u043f\u043b\u0430\u0442\u043d\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043e\u0431\u043b\u0430\u0434\u0430\u0435\u0442 \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u043d\u043e\u0439 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u044e \u0438 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432. \u0412 \u0441\u0432\u043e\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b \u0434\u0432\u0430 \u0442\u0438\u043f\u0430 \u0433\u0440\u0430\u0444\u0438\u043a\u043e\u0432: \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u043b\u0438\u043d\u0435\u0439\u043d\u044b\u0439 \u0434\u043b\u044f \u0432\u044b\u0432\u043e\u0434\u0430 \u0438\u0441\u0442\u043e\u0440\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441 \u0434\u0430\u0442\u0447\u0438\u043a\u043e\u0432 \u0438 \u0440\u0430\u0434\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u0434\u043b\u044f \u0438\u043d\u0434\u0438\u043a\u0430\u0446\u0438\u0438 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f. \u041e\u043d\u0438 \u0431\u044b\u043b\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u044b \u0432 \u0432\u0438\u0434\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043e\u0432. \u0418\u0442\u0430\u043a, \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u0434\u0432\u0443\u043c\u0435\u0440\u043d\u044b\u0439 \u0433\u0440\u0430\u0444\u0438\u043a \u0432 \u0432\u0438\u0434\u0435 \u043b\u0438\u043d\u0438\u0438:<\/p>\n<pre><code class=\"xml\">&lt;reactiveui:ReactiveUserControl x:Class=\"Apc.Application2.Views.PlotControl\"             x:TypeArguments=\"models:PlotControlViewModel\"             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:reactiveui=\"http:\/\/reactiveui.net\"             xmlns:models=\"clr-namespace:Apc.Application2.Models\"             xmlns:lvc=\"clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF\"             mc:Ignorable=\"d\"             d:DesignHeight=\"300\" d:DesignWidth=\"300\">      &lt;Grid>           &lt;lvc:CartesianChart x:Name=\"Plot\" Background=\"White\" ZoomMode=\"Both\"\/>   &lt;\/Grid> &lt;\/reactiveui:ReactiveUserControl><\/code><\/pre>\n<p>\u041a\u043b\u0430\u0441\u0441 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0442\u0440\u0438\u0432\u0438\u0430\u043b\u0435\u043d :<\/p>\n<details class=\"spoiler\">\n<summary>\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"cs\">public partial class PlotControl { public PlotControl() { InitializeComponent(); ViewModel = Locator.Current.GetService&lt;PlotControlViewModel>();  this.WhenActivated(disposable => { this.OneWayBind(ViewModel, vm => vm.Series, v => v.Plot.Series) .DisposeWith(disposable); this.OneWayBind(ViewModel, vm => vm.XAxes, v => v.Plot.XAxes) .DisposeWith(disposable); this.OneWayBind(ViewModel, vm => vm.YAxes, v => v.Plot.YAxes) .DisposeWith(disposable); this.OneWayBind(ViewModel, vm => vm.LegendPosition, v => v.Plot.LegendPosition) .DisposeWith(disposable); }); } }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0422\u0443\u0442 \u044f \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043e\u0441\u0435\u0439 \u0438 \u043b\u0435\u0433\u0435\u043d\u0434\u044b \u0433\u0440\u0430\u0444\u0438\u043a\u0430 \u0447\u0435\u0440\u0435\u0437 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u043c\u043e\u0434\u0435\u043b\u0438.<\/p>\n<details class=\"spoiler\">\n<summary>\u041c\u043e\u0434\u0435\u043b\u044c<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"cs\">public class PlotControlViewModel: ReactiveObject { public PlotControlViewModel() { _values = new Collection&lt;ObservableCollection&lt;DateTimePoint>>();  Series = new ObservableCollection&lt;ISeries>();  XAxes = new [] { new Axis { \/\/ Labeler \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0447\u0438\u0441\u043b\u043e\u0432\u044b\u0445 \u043c\u0435\u0442\u043e\u043a \u043e\u0441\u0438           Labeler = value => new DateTime((long) value).ToString(\"HH:mm:ss\"), UnitWidth = TimeSpan.FromSeconds(1).Ticks, MinStep = TimeSpan.FromSeconds(1).Ticks, \/\/ \u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u043b\u0438\u043d\u0438\u0439 \u0441\u0435\u0442\u043a\u0438 ShowSeparatorLines = true, SeparatorsPaint = new SolidColorPaint { Color = SKColors.DarkGray, StrokeThickness = 1 }, \/\/ \u0428\u0440\u0438\u0444\u0442 \u043c\u0435\u0442\u043e\u043a \u043e\u0441\u0438           TextSize = 11,           NamePaint = new SolidColorPaint { Color = SKColors.Black, FontFamily = \"Segoe UI\", },  } };  YAxes = new[] { new Axis { Labeler = value => $\"{value:F1}\", TextSize = 11, NameTextSize = 11,  UnitWidth = 0.5, MinStep = 0.5,  ShowSeparatorLines = true, SeparatorsPaint = new SolidColorPaint { Color = SKColors.DarkGray, StrokeThickness = 1 },            NamePaint = new SolidColorPaint { Color = SKColors.Black, FontFamily = \"Segoe UI\", } } }; }  public ObservableCollection&lt;ISeries> Series { get; } private readonly Collection&lt;ObservableCollection&lt;DateTimePoint>> _values;  [Reactive] public Axis[] XAxes { get; set; }  [Reactive] public Axis[] YAxes { get; set; }      public string Title { get; set; }  [Reactive] public LegendPosition LegendPosition { get; set; }  public int AddSeries(string name, SKColor color, float width) { var newValues = new ObservableCollection&lt;DateTimePoint>(); _values.Add(newValues); var lineSeries = new LineSeries&lt;DateTimePoint> { Values = newValues, Fill = null, Stroke = new SolidColorPaint(color, width), Name = name, GeometrySize = 5, LineSmoothness = 0 }; Series.Add(lineSeries);  return Series.IndexOf(lineSeries); }  public void AddData(int index, DateTime time, double value) { if (index >= _values.Count) { return; } _values[index].Add(new DateTimePoint(time, value)); }  public void ClearData(int index) { if (index >= _values.Count) { return; } _values[index].Clear(); } }<\/code><\/pre>\n<\/div>\n<\/details>\n<p><code>CartesianChart<\/code> \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u0432\u0438\u0434\u0435 \u0441\u0435\u0440\u0438\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043f\u0440\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0433\u0440\u0430\u0444\u0438\u043a\u0430 \u043c\u0435\u0442\u043e\u0434\u043e\u043c <code>AddSeries()<\/code>. \u041c\u0435\u0442\u043e\u0434 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0438\u043d\u0434\u0435\u043a\u0441 \u0441\u0435\u0440\u0438\u0438 \u0432 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438. \u0415\u0433\u043e \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0434\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u043d\u0443\u0436\u043d\u0443\u044e \u0441\u0435\u0440\u0438\u044e. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0435\u0441\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043d\u0430\u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0435\u0440\u0438\u0439 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u0433\u0440\u0430\u0444\u0438\u043a\u0435.<\/p>\n<details class=\"spoiler\">\n<summary>\u041f\u0440\u0438\u043c\u0435\u0440<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"cs\">\/\/ \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u0433\u0440\u0430\u0444\u0438\u043a \u0434\u0430\u0432\u043b\u0435\u043d\u0438\u044f. \u0411\u0443\u0434\u0435\u0442 \u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u0434\u0432\u0435 \u043b\u0438\u043d\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 int pressure1Index = PressurePlot.ViewModel.AddSeries(\"\u0414\u0430\u0432\u043b\u0435\u043d\u0438\u04351\", new SKColor(25, 118, 210), 2); int pressure2Index = PressurePlot.ViewModel.AddSeries(\"\u0414\u0430\u0432\u043b\u0435\u043d\u0438\u04352\", new SKColor(229, 57, 53), 2);  \/\/...   \/\/ \u041f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c\u0441\u044f \u043d\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u0447\u0442\u0435\u043d\u0438\u044f \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u0439 \u0434\u0430\u0442\u0447\u0438\u043a\u043e\u0432 \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0430 \u0433\u0440\u0430\u0444\u0438\u043a ViewModel?.ReadDeviceIndicators .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(indicators => { var currentTime = _clockProvider.Now(); PressurePlot?.ViewModel?.AddData(pressure1Index, currentTime, indicators.Pressure1); PressurePlot?.ViewModel?.AddData(pressure2Index, currentTime, indicators.Pressure2); }).DisposeWith(disposable);<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0414\u043b\u044f \u0432\u044b\u0432\u043e\u0434\u0430 \u043b\u0438\u043d\u0438\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f <code>LineSeries<\/code> c \u0442\u043e\u0447\u043a\u0430\u043c\u0438 <code>DateTimePoint<\/code>, \u0442\u0430\u043a \u043a\u0430\u043a \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u044c \u0433\u0440\u0430\u0444\u0438\u043a\u0438  \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0432\u0440\u0435\u043c\u0435\u043d\u0438. \u041a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044f <code>Series<\/code> \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f <code>Observable<\/code>, \u0447\u0442\u043e\u0431\u044b \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043d\u0430 \u0433\u0440\u0430\u0444\u0438\u043a\u0435. \u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u043e\u0441\u0438 \u0433\u0440\u0430\u0444\u0438\u043a\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b \u043c\u0430\u0441\u0441\u0438\u0432\u043e\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 <code>Axis<\/code>, \u0447\u0442\u043e \u043f\u043e\u0437\u0432\u043b\u044f\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043e\u0441\u0438 \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0441\u0435\u0440\u0438\u0439. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432 \u0441\u0435\u0440\u0438\u0438 \u0435\u0441\u0442\u044c \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 <code>ScalesXAt<\/code>, <code>ScalesYAt<\/code>, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0438\u043d\u0434\u0435\u043a\u0441 \u043e\u0441\u0438. <br \/>\u041d\u0430\u043f\u0440\u043c\u0435\u0440, \u0433\u0440\u0430\u0444\u0438\u043a \u0434\u0430\u0432\u043b\u0435\u043d\u0438\u044f, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0449\u0438\u0439 \u0434\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b, \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438:<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" height=\"348\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/741\/8f5\/48e\/7418f548e1409eb76f9b93bc3ee4ff94.png\" data-width=\"1699\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0420\u0430\u0434\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u0433\u0440\u0430\u0444\u0438\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 <code>PieChart<\/code><\/p>\n<pre><code class=\"xml\">&lt;lvc:PieChart x:Name=\"Gauge\" Width=\"200\"\/><\/code><\/pre>\n<details class=\"spoiler\">\n<summary>\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"cs\">public partial class GaugeControl { public GaugeControl() { InitializeComponent(); ViewModel = new GaugeControlViewModel();  this.WhenActivated(disposable => { this.OneWayBind(ViewModel, vm => vm.Total, v => v.Gauge.Total) .DisposeWith(disposable); this.OneWayBind(ViewModel, vm => vm.InitialRotation, v => v.Gauge.InitialRotation) .DisposeWith(disposable); this.Bind(ViewModel, vm => vm.Series, v => v.Gauge.Series) .DisposeWith(disposable); }); }  public double Total { get { return ViewModel.Total; } set { ViewModel.Total = value; } }  public double InitialRotation { get => ViewModel?.InitialRotation ?? 0.0;  set { ViewModel.InitialRotation = value; } }      \/* \u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0435\u043a\u0443\u0449\u0435\u0435 \u0437\u0430\u0447\u0435\u043d\u0438\u0435,       \u0442\u043e \u0432\u043c\u0435\u0441\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430, \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u044e \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 *\/ public double this[int index] { get => ViewModel.LastValues[index].Value ?? 0.0; set { ViewModel.LastValues[index].Value = Math.Round(value, 2); } } }<\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>\u041c\u043e\u0434\u0435\u043b\u044c<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"cs\">public class GaugeControlViewModel: ReactiveObject { public GaugeControlViewModel() { }  public void InitSeries(SeriesInitialize[] seriesInitializes, Func&lt;ChartPoint, string> labelFormatter = null) { var builder = new GaugeBuilder { LabelsSize = 18, InnerRadius = 40, CornerRadius = 90, BackgroundInnerRadius = 40, Background = new SolidColorPaint(new SKColor(100, 181, 246, 90)), LabelsPosition = PolarLabelsPosition.ChartCenter, LabelFormatter = labelFormatter ?? (point => point.PrimaryValue.ToString(CultureInfo.InvariantCulture)), OffsetRadius = 0, BackgroundOffsetRadius = 0 }; LastValues = new(seriesInitializes.Length);  foreach (var init in seriesInitializes) { var defaultSeriesValue = new ObservableValue(0); builder.AddValue(defaultSeriesValue, init.Name, init.DrawColor); LastValues.Add(defaultSeriesValue); }  Series = builder.BuildSeries(); }  [Reactive] public IEnumerable&lt;ISeries> Series { get; set; }  [Reactive] public double Total { get; set; }  [Reactive] public double InitialRotation { get; set; }  [Reactive] public List&lt;ObservableValue> LastValues { get; private set; } }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0418\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440\u044b \u0434\u0430\u0432\u043b\u0435\u043d\u0438\u044f, \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u044d\u0442\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0430 \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438:<\/p>\n<figure class=\"\"><img decoding=\"async\" src=\"\/img\/image-loader.svg\" height=\"251\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7cf\/440\/766\/7cf440766aa7eeee032717d119b34819.png\" data-width=\"412\"\/><figcaption><\/figcaption><\/figure>\n<p>\u042f \u0438\u0445 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u043b \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0430 <code>Card<\/code> \u0438\u0437 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 MaterialDesign. \u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0442\u043c\u0435\u0442\u0435\u0442\u044c, \u0447\u0442\u043e <code>PieChart<\/code> \u043d\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0445 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0448\u043a\u0430\u043b\u0443 \u0441 \u043c\u0435\u0442\u043a\u0430\u043c\u0438. \u0415\u0441\u0442\u044c <code>PolarChart<\/code> \u0441 \u0448\u043a\u0430\u043b\u043e\u0439, \u043d\u043e \u043e\u043d \u043d\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043d\u0430\u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c &#171;\u043f\u0438\u0440\u043e\u0433&#187;. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0442\u0443\u0442 \u043d\u0443\u0436\u043d\u043e \u043f\u0438\u0441\u0430\u0442\u044c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u0443\u044e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e.<\/p>\n<p>\u041a\u0430\u043a \u044f \u0433\u043e\u0432\u043e\u0440\u0438\u043b, \u043f\u043b\u0430\u0442\u043d\u0430\u044f \u0432\u0435\u0440\u0438\u044f \u043e\u0431\u0435\u0449\u0430\u0435\u0442 \u043b\u0443\u0447\u0448\u0443\u044e \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0433\u0440\u0430\u0444\u0438\u043a\u043e\u0432, \u043d\u043e \u043c\u0435\u043d\u044f \u0432\u043f\u043e\u043b\u043d\u0435 \u0443\u0434\u043e\u0432\u043b\u0435\u0442\u0432\u043e\u0440\u0438\u043b\u0430 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 1 \u0440\u0430\u0437 \u0432  3-4 \u0441\u0435\u043a\u0443\u043d\u0434\u044b.<\/p>\n<p><a class=\"anchor\" name=\"%D0%B7%D0%B0%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5\" id=\"\u0437\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\"><\/a><\/p>\n<h2>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0412 \u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0435\u0440\u0435\u043d\u044b\u0435 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0440\u0438\u0435\u043c\u044b, \u043e\u0431\u043b\u0435\u0433\u0447\u0430\u0431\u0449\u0438\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0443 WPF-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u0423\u0434\u0435\u043b\u0435\u043d\u043e \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043d\u044b\u043c \u043c\u043e\u043c\u0435\u043d\u0442\u0430\u043c: \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 IoC, \u043b\u043e\u0433\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u043c\u0430\u043f\u043f\u0438\u043d\u0433 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d \u0441\u043f\u043e\u0441\u043e\u0431 \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044f \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f UI c \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 \u0438\u0437 Material Design \u0432\u043c\u0435\u0441\u0442\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0445 \u0441\u0435\u0440\u044b\u0445 \u043a\u043d\u043e\u043f\u043e\u043a \u0438 \u043f\u043e\u043b\u0435\u0439. \u0412\u0441\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b \u0438 \u0441 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u043c \u043a\u043e\u0434\u043e\u043c. \u041a\u043e\u043d\u0435\u0447\u043d\u043e \u043f\u043e \u0441\u0432\u043e\u0438\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044f\u043c \u043e\u043d\u0438 \u043d\u0435 \u0434\u043e\u0442\u044f\u0433\u0438\u0432\u0430\u044e\u0442 \u0434\u043e \u043f\u043b\u0430\u0442\u043d\u044b\u0445 \u0442\u0430\u043a\u0438\u0445 \u043f\u0430\u043a\u0435\u0442\u043e\u0432, \u043a\u0430\u043a Telerik \u0438 SyncFusion, \u043d\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0432\u043f\u043e\u043b\u043d\u0435 \u0434\u043e\u0441\u0442\u043e\u0439\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043a\u043e\u0433\u0434\u0430 \u043f\u043e\u043a\u0443\u043f\u043a\u0430 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0445 \u0432\u044b\u0448\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043d\u0435 \u043e\u043f\u0440\u0430\u0432\u0434\u0430\u043d\u0430. \u0422\u0430\u043a\u0436\u0435 \u0437\u0430\u043c\u0435\u0447\u0443, \u0447\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 Reactive Extensions, LiveCharts2, \u0432 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0435, \u043d\u0435 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u043e desktop-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u043c\u0438, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u044b \u0438 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u044b \u0438 \u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u043e\u0431\u043b\u0430\u0441\u0442\u044f\u0445 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, Michael Shpilt <a href=\"https:\/\/michaelscodingspot.com\/c-job-queues-with-reactive-extensions-and-channels\/\" rel=\"noopener noreferrer nofollow\">\u043e\u043f\u0438\u0441\u0430\u043b<\/a> \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e Job Queue \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Reactive Extensions.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"v-portal\" style=\"display:none;\"><\/div>\n<\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/647259\/\"> https:\/\/habr.com\/ru\/post\/647259\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p><a class=\"anchor\" name=\"%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5\" id=\"\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435\">\u043d\u0438\u0435&#187;><\/a><\/p>\n<h2>\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0414\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043b\u0435\u0437\u043d\u0430 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c, \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 WPF. \u041e\u043d\u0430 \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e\u043c. \u0417\u0434\u0435\u0441\u044c \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u044b \u043b\u0438\u0448\u044c \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u044b \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u044b \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u043d\u0430 WPF. \u041f\u043e \u0441\u0443\u0442\u0438, \u0441\u0442\u0430\u0442\u044c\u044f \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u043d\u0430\u0431\u043e\u0440 \u0440\u0435\u0446\u0435\u043f\u0442\u043e\u0432 \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0445 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 WPF \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043e\u043f\u044b\u0442\u043d\u044b\u0435 WPF-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0432\u0440\u044f\u0434 \u043b\u0438 \u043d\u0430\u0439\u0434\u0443\u0442 \u0447\u0442\u043e-\u0442\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0435 \u0434\u043b\u044f \u0441\u0435\u0431\u044f. \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u043f\u0440\u0438\u0432\u043e\u0434\u044f\u0442\u0441\u044f \u0447\u0430\u0441\u0442\u0438 \u043a\u043e\u0434\u0430 \u0438\u0437 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0441\u043b\u0443\u0436\u0438\u0442 \u0434\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u043a\u043b\u0430\u043f\u0430\u043d\u0430 (\u043d\u0443\u0436\u043d\u043e \u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u044f \u0434\u0430\u0442\u0447\u0438\u043a\u043e\u0432 \u0434\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0438 \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u044c \u0438\u0445 \u043d\u0430 \u044d\u043a\u0440\u0430\u043d). \u041e\u0442\u043c\u0435\u0447\u0443, \u0447\u0442\u043e \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u044b\u0435 \u043f\u0430\u043a\u0435\u0442\u044b \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0441 \u0446\u0435\u043b\u044c\u044e \u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439 \u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u044f.<\/p>\n<h2>\u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435<\/h2>\n<ul>\n<li>\n<p><a href=\"#%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5\" rel=\"noopener noreferrer nofollow\">\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#%D0%98%D0%BD%D1%84%D1%80%D0%B0%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0\" rel=\"noopener noreferrer nofollow\">\u0418\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430<\/a><\/p>\n<ul>\n<li>\n<p><a href=\"#%D0%98%D1%81%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D1%8F\" rel=\"noopener noreferrer nofollow\">\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#ioc\" rel=\"noopener noreferrer nofollow\">\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 IoC<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#%D0%BC%D0%B0%D0%BF%D0%BF%D0%B8%D0%BD%D0%B3\" rel=\"noopener noreferrer nofollow\">\u041c\u0430\u043f\u043f\u0438\u043d\u0433 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432<\/a><\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><a href=\"#MVVM\" rel=\"noopener noreferrer nofollow\">\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f MVVM &#8212; \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u0430<\/a><\/p>\n<ul>\n<li>\n<p><a href=\"#%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C\" rel=\"noopener noreferrer nofollow\">\u041c\u043e\u0434\u0435\u043b\u044c<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#%D0%BF%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5\" rel=\"noopener noreferrer nofollow\">\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#%D0%B2%D0%B0%D0%BB%D0%B8%D0%B4%D0%B0%D1%86%D0%B8%D1%8F\" rel=\"noopener noreferrer nofollow\">\u0412\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#%D0%BA%D0%BE%D0%BC%D0%B0%D0%BD%D0%B4%D1%8B\" rel=\"noopener noreferrer nofollow\">\u041a\u043e\u043c\u0430\u043d\u0434\u044b<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B5%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D0%B5\" rel=\"noopener noreferrer nofollow\">\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445<\/a><\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><a href=\"#%D0%92%D0%B8%D0%B7%D1%83%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%D1%81%D1%82%D0%B8%D0%BB%D1%8C\" rel=\"noopener noreferrer nofollow\">\u0412\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u043c\u044b \u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f<\/a><\/p>\n<ul>\n<li>\n<p><a href=\"#%D1%81%D1%82%D0%B8%D0%BB%D1%8C\" rel=\"noopener noreferrer nofollow\">\u0421\u0442\u0438\u043b\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%BA%D0%B8\" rel=\"noopener noreferrer nofollow\">\u0413\u0440\u0430\u0444\u0438\u043a\u0438<\/a><\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><a href=\"#%D0%B7%D0%B0%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5\" rel=\"noopener noreferrer nofollow\">\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/a><\/p>\n<\/li>\n<\/ul>\n<p><a class=\"anchor\" name=\"%D0%98%D0%BD%D1%84%D1%80%D0%B0%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0\" id=\"\u0418\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430\">\u0443\u043a\u0442\u0443\u0440\u0430&#187;><\/a><\/p>\n<h2>\u0418\u043d\u0444\u0440\u0430\u0441\u0442\u0443\u0440\u043a\u0442\u0443\u0440\u0430<\/h2>\n<p>\u041f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u043b\u043e\u043c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043d\u044b\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u042f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <a href=\"https:\/\/www.reactiveui.net\/\" rel=\"noopener noreferrer nofollow\">ReactiveUI<\/a> \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043e\u043d\u0430 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0441\u0442\u0435\u043f\u0435\u043d\u0438 \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 boilerplate-\u043a\u043e\u0434\u0430 \u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0432 \u0441\u0435\u0431\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0442\u0430\u043a\u0438\u0445, \u043a\u0430\u043a \u0432\u043d\u0443\u0442\u0440\u0438\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043d\u0430\u044f \u0448\u0438\u043d\u0430, \u043b\u043e\u0433\u0433\u0435\u0440, \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0449\u0438\u043a \u0438 \u043f\u0440\u043e\u0447\u0435\u0435. \u041e\u0441\u043d\u043e\u0432\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043d\u0435\u043f\u043b\u043e\u0445\u043e \u0438\u0437\u043b\u043e\u0436\u0435\u043d\u044b <a href=\"https:\/\/habr.com\/ru\/post\/303650\/\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a>. ReactiveUI \u0438\u0441\u043f\u043e\u0432\u0435\u0434\u0443\u0435\u0442 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434, \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0432 \u0432\u0438\u0434\u0435 <a href=\"https:\/\/github.com\/dotnet\/reactive\" rel=\"noopener noreferrer nofollow\">Reactive Extensions<\/a>. \u041f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430 \u044f \u043e\u043f\u0438\u0448\u0443 \u043d\u0438\u0436\u0435 \u0432 <a href=\"#MVVM\" rel=\"noopener noreferrer nofollow\">\u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u0430 MVVM<\/a>.<\/p>\n<p><a class=\"anchor\" name=\"%D0%98%D1%81%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D1%8F\" id=\"\u0418\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f\">\u0435\u043d\u0438\u044f&#187;><\/a><\/p>\n<h3>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439<\/h3>\n<p>\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0439 exception handler, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0438\u0448\u0435\u0442 \u043e\u0448\u0438\u0431\u043a\u0438 c \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043b\u043e\u0433\u0433\u0435\u0440\u0430. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432 \u043a\u043b\u0430\u0441\u0441\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f App \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u043c\u0435\u0442\u043e\u0434 <code>OnStartup<\/code>, \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u043f\u0440\u0435\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0441\u043e\u0431\u044b\u0442\u0438\u044f <a href=\"https:\/\/docs.microsoft.com\/ru-ru\/dotnet\/api\/system.windows.application.startup?view=windowsdesktop-6.0\" rel=\"noopener noreferrer nofollow\">StartupEvent<\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0438\u0437 \u043c\u0435\u0442\u043e\u0434\u0430 <code>Application.Run<\/code><\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"cs\">public partial class App : Application { private readonly ILogger _logger;  public App() { Bootstrapper.BuildIoC(); \/\/ \u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c IoC  _logger = Locator.Current.GetService&lt;ILogger>(); }  private void LogException(Exception e, string source) { _logger?.Error($\"{source}: {e.Message}\", e); }  private void SetupExceptionHandling() { \/\/ \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u043d\u0430\u0448 Observer-\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439 RxApp.DefaultExceptionHandler = new ApcExceptionHandler(_logger); }  protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); SetupExceptionHandling(); } }  public class ApcExceptionHandler: IObserver&lt;Exception> { private readonly ILogger _logger;  public ApcExceptionHandler(ILogger logger) { _logger = logger; }  public void OnCompleted() { if (Debugger.IsAttached) Debugger.Break(); }  public void OnError(Exception error) { if (Debugger.IsAttached) Debugger.Break(); _logger.Error($\"{error.Source}: {error.Message}\", error); }  public void OnNext(Exception value) { if (Debugger.IsAttached) Debugger.Break();  _logger?.Error($\"{value.Source}: {value.Message}\", value); } }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041b\u043e\u0433\u0433\u0435\u0440 \u043f\u0438\u0448\u0435\u0442 \u0432 \u0444\u0430\u0439\u043b \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/nlog-project.org\/\" rel=\"noopener noreferrer nofollow\">NLog<\/a> \u0438 \u0432\u043e \u0432\u043d\u0443\u0442\u0440\u0438\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043d\u0443\u044e \u0448\u0438\u043d\u0443 <code>MessageBus<\/code>, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043c\u043e\u0433\u043b\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u044c \u043b\u043e\u0433\u0438 \u0432 UI<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"cs\">public class AppLogger: ILogger {    \/\/\u042d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043b\u043e\u0433\u0433\u0435\u0440\u0430 NLog private NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();       public AppLogger()   {   }       public void Info(string message)      {         _logger.Info(message);           MessageBus.Current.SendMessage(new ApplicationLog(message));      }         public void Error(string message, Exception exception = null)   {         _logger.Error(exception, message);     \/\/\u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432 \u0448\u0438\u043d\u0443     MessageBus.Current.SendMessage(new ApplicationLog(message));      } }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041d\u0435\u043e\u0431\u043e\u0445\u043e\u0434\u0438\u043c\u043e, \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 ReactiveUI <a href=\"https:\/\/www.reactiveui.net\/docs\/handbook\/message-bus\/\" rel=\"noopener noreferrer nofollow\">\u0441\u043e\u0432\u0435\u0442\u0443\u044e\u0442<\/a> \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432 <code>MessageBus<\/code> \u0432 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u0442\u0430\u043a \u043a\u0430\u043a <code>MessageBus<\/code> &#8212; \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u0430\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0430\u044f, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u043e\u0442\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u043c \u043c\u0435\u0441\u0442\u043e\u043c \u0443\u0442\u0435\u0447\u0435\u043a \u043f\u0430\u043c\u044f\u0442\u0438. \u041f\u0440\u043e\u0441\u043b\u0443\u0448\u0438\u0432\u0430\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438\u0437 \u0448\u0438\u043d\u044b \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u043c\u0435\u0442\u043e\u0434\u043e\u043c <code>MessugeBus.Current.Listen<\/code><\/p>\n<pre><code class=\"cs\">MessageBus.Current.Listen&lt;ApplicationLog>().ObserveOn(RxApp.MainThreadScheduler).Subscribe(Observer.Create&lt;ApplicationLog>((log) => { LogContent += logMessage; }));<\/code><\/pre>\n<p><a class=\"anchor\" name=\"ioc\" id=\"ioc\"><\/a><\/p>\n<h3>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 IoC<\/h3>\n<p>\u0414\u0430\u043b\u0435\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043c IoC, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0431\u043b\u0435\u0433\u0447\u0438\u0442 \u043d\u0430\u043c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0436\u0438\u0437\u0435\u043d\u043d\u044b\u043c \u0446\u0438\u043a\u043b\u043e\u043c \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432. ReactiveUI \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 <a href=\"https:\/\/github.com\/reactiveui\/splat\" rel=\"noopener noreferrer nofollow\">Splat<\/a>. \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0432\u044b\u0437\u043e\u0432\u0430 \u043c\u0435\u0442\u043e\u0434\u0430 <code>Register()<\/code> \u043f\u043e\u043b\u044f <code>Locator.CurrentMutable<\/code>, \u0430 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 &#8212; <code>GetService()<\/code> \u043f\u043e\u043b\u044f <code>Locator.Current<\/code>. <br \/>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:<\/p>\n<pre><code class=\"cs\">Locator.CurrentMutable.Register(() => new AppLogger(), typeof(ILogger)); var logger = Locator.Current.GetService&lt;ILogger>();<\/code><\/pre>\n<p>\u041f\u043e\u043b\u0435 <code>Locator.Current<\/code> \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043e \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 DI\/IoC \u0434\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0445 Splat \u0438\u043c\u0435\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u043a\u0435\u0442\u044b. \u042f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e Autofac c \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0430\u043a\u0435\u0442\u0430 <a href=\"https:\/\/github.com\/reactiveui\/splat\/tree\/main\/src\/Splat.Autofac\" rel=\"noopener noreferrer nofollow\">Splat.Autofac<\/a>. \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044e \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0432\u044b\u043d\u0435\u0441 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441.<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"cs\">public static class Bootstrapper { public static void BuildIoC() { \/*  * \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 Autofac.  * \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u044b \u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f  *\/ var builder = new ContainerBuilder(); RegisterServices(builder); RegisterViews(builder); \/\/ \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c Autofac \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0432 Splat var autofacResolver = builder.UseAutofacDependencyResolver(); builder.RegisterInstance(autofacResolver);  \/\/ \u0412\u044b\u0437\u044b\u0432\u0430\u0435\u043c InitializeReactiveUI(), \u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0439 Service Locator autofacResolver.InitializeReactiveUI(); var lifetimeScope = builder.Build(); autofacResolver.SetLifetimeScope(lifetimeScope); }  private static void RegisterServices(ContainerBuilder builder) { builder.RegisterModule(new ApcCoreModule()); builder.RegisterType&lt;AppLogger>().As&lt;ILogger>(); \/\/ \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u043f\u0440\u043e\u0444\u0438\u043b\u0438 ObjectMapper \u043f\u0443\u0442\u0435\u043c \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u0431\u043e\u0440\u043a\u0438 var typeAdapterConfig = TypeAdapterConfig.GlobalSettings; typeAdapterConfig.Scan(Assembly.GetExecutingAssembly()); }  private static void RegisterViews(ContainerBuilder builder) { builder.RegisterType&lt;MainWindow>().As&lt;IViewFor&lt;MainWindowViewModel>>(); builder.RegisterType&lt;MessageWindow>().As&lt;IViewFor&lt;&lt;MessageWindowViewModel>>().AsSelf(); builder.RegisterType&lt;MainWindowViewModel>(); builder.RegisterType&lt;MessageWindowViewModel>(); } }<\/code><\/pre>\n<\/div>\n<\/details>\n<p><a class=\"anchor\" name=\"%D0%BC%D0%B0%D0%BF%D0%BF%D0%B8%D0%BD%D0%B3\" id=\"\u043c\u0430\u043f\u043f\u0438\u043d\u0433\">\u0438\u043d\u0433&#187;<\/a><\/p>\n<h3>\u041c\u0430\u043f\u043f\u0438\u043d\u0433 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432<\/h3>\n<p>\u041c\u0430\u043f\u043f\u0435\u0440 \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u043d\u0430\u043c \u043c\u0438\u043d\u0438\u043c\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u0434 \u043f\u043e \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u044e \u043e\u0434\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0432 \u0434\u0440\u0443\u0433\u043e\u0439. \u042f \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0441\u044f \u043f\u0430\u043a\u0435\u0442\u043e\u043c <a href=\"https:\/\/github.com\/MapsterMapper\/Mapster\" rel=\"noopener noreferrer nofollow\">Mapster<\/a>. \u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0438\u043c\u0435\u0435\u0442 FluetAPI, \u043b\u0438\u0431\u043e \u0430\u0442\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u043a \u043a\u043b\u0430\u0441\u0441\u0430\u043c \u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c. \u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043a\u043e\u0434\u043e\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044e \u043c\u0430\u043f\u043f\u0438\u043d\u0433\u0430 \u043d\u0430 \u0441\u0442\u0430\u0434\u0438\u0438 \u0441\u0431\u043e\u0440\u043a\u0438, \u0447\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u043e\u043a\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0434\u043d\u0438\u0445 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0432 \u0434\u0440\u0443\u0433\u0438\u0435. \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044e \u044f \u0440\u0435\u0448\u0438\u043b \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u043e\u043b\u0436\u0435\u043d \u0440\u0435\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <code>IRegister<\/code>:<\/p>\n<pre><code class=\"cs\">public class ApplicationMapperRegistration: IRegister { public void Register(TypeAdapterConfig config) { config.NewConfig&lt;IPositionerDevice, DeviceViewModel>() .ConstructUsing(src => new DeviceViewModel(src.Mode, src.IsConnected, src.DeviceId, src.Name)); config.NewConfig&lt;DeviceIndicators, DeviceViewModel>(); } }<\/code><\/pre>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u0441 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u043e\u0439 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0432\u0441\u0451. \u0414\u0440\u0443\u0433\u0438\u0445 \u043c\u043e\u043c\u0435\u043d\u0442\u043e\u0432 \u0437\u0430\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u044e\u0449\u0438\u0445 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u044f \u043d\u0435 \u043d\u0430\u0448\u0451\u043b. \u0414\u0430\u043b\u0435\u0435 \u043e\u043f\u0438\u0448\u0443 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u043c\u0435\u043d\u0442\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 UI \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p><a class=\"anchor\" name=\"MVVM\" id=\"MVVM\"><\/a><\/p>\n<h2>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f MVVM &#8212; \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u0430<\/h2>\n<p><a class=\"anchor\" name=\"%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C\" id=\"\u043c\u043e\u0434\u0435\u043b\u044c\">\u043b\u044c&#187;><\/a><\/p>\n<p>\u041a\u0430\u043a \u044f \u043f\u0438\u0441\u0430\u043b \u0432\u044b\u0448\u0435, \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e <a href=\"https:\/\/www.reactiveui.net\/\" rel=\"noopener noreferrer nofollow\">ReactivUI<\/a>, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0449\u0438\u0439 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 UI \u0432 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u0442\u0438\u043b\u0435. \u041d\u0438\u0436\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043c\u043e\u043c\u0435\u043d\u0442\u044b \u043f\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044e \u043a\u043e\u0434\u0430 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0439.<\/p>\n<h3>\u041c\u043e\u0434\u0435\u043b\u044c<\/h3>\n<p>\u041a\u043b\u0430\u0441\u0441\u044b \u043c\u043e\u0434\u0435\u043b\u0435\u0439, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u0432 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f\u0445, \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u044e\u0442\u0441\u044f \u043e\u0442 <code>ReactiveObject<\/code>. \u0415\u0441\u0442\u044c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"https:\/\/github.com\/Fody\/Fody\" rel=\"noopener noreferrer nofollow\">Fody<\/a>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0430\u0442\u0442\u0440\u0438\u0431\u0443\u0442\u0430 <code>Reactive<\/code> \u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u043c\u043e\u0434\u0435\u043b\u0438 \u0440\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u043c\u0438. \u041c\u043e\u0436\u043d\u043e \u0438 \u0431\u0435\u0437 \u043d\u0435\u0435, \u043d\u043e \u043f\u043e \u043c\u043e\u0435\u043c\u0443 \u043c\u043d\u0435\u043d\u0438\u044e, \u043e\u043d\u0430 \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043a\u043e\u0434 \u0431\u043e\u043b\u0435\u0435 \u0447\u0438\u0442\u0430\u0435\u043c \u0437\u0430 \u0441\u0447\u0451\u0442 \u0441\u043e\u043a\u0440\u0430\u0449\u0435\u043d\u0438\u044f boilerplate-\u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0439. \u0421\u0432\u044f\u0437\u044b\u0432\u0430\u043d\u0438\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432 \u043c\u043e\u0434\u0435\u043b\u0438 \u0441\u043e \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u043b\u0438\u0431\u043e \u0432 XML \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0435, \u043b\u0438\u0431\u043e \u0432 \u043a\u043e\u0434\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u043e\u0432. <br \/>\u041d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u043c\u043e\u0434\u0435\u043b\u0438 \u043a\u043b\u0430\u043f\u0430\u043d\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u0438\u044f \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u0434\u0430\u0442\u0447\u0438\u043a\u043e\u0432.<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"cs\">public class DeviceViewModel: ReactiveObject {     public DeviceViewModel()   {   }         [Reactive]   public float Current { get; set; }         [Reactive]      public float Pressure { get; set; }       [Reactive]      public float Position { get; set; }       [Reactive]      public DateTimeOffset DeviceTime { get; set; }  [Reactive] public bool Connected { get; set; }  public ReactiveCommand&lt;Unit, bool> ConnectToDevice; public readonly ReactiveCommand&lt;float, float> SetValvePosition; }  <\/code><\/pre>\n<\/div>\n<\/details>\n<p><a class=\"anchor\" name=\"%D0%BF%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5\" id=\"\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435\">\u0432\u043b\u0435\u043d\u0438\u0435&#187;<\/a><\/p>\n<h3>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f  \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f<\/h3>\n<p>\u0412 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u043b\u0435\u043d\u0438\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u043a\u043e\u043c\u0430\u043d\u0434 \u0438 \u043f\u043e\u043b\u044f \u043c\u043e\u0434\u0435\u043b\u0438 \u043a \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f<\/p>\n<details class=\"spoiler\">\n<summary>\u041a\u043e\u0434<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"cs\">public partial class MainWindow { public MainWindow() { InitializeComponent();  ViewModel = Locator.Current.GetService&lt;DeviceViewModel>(); DataContext = ViewModel;  \/*  * \u0414\u0430\u043d\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u043c\u043e\u0434\u0435\u043b\u0438 \u043a \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043c \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f  * DisposeWith \u0432 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c \u0434\u043b\u044f \u043e\u0447\u0438\u0441\u0442\u043a\u0438 \u043f\u0440\u0438\u0432\u044f\u0437\u043e\u043a \u043f\u0440\u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f  *\/ this.WhenActivated(disposable => { \/*  * \u041f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 Text \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 TextBox \u043a \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u043c\u043e\u0434\u0435\u043b\u0438.  * OneWayBind - \u043e\u0434\u043d\u043e\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u0430\u044f \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0430, Bind - \u0434\u0432\u0443\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u0430\u044f  *\/ this.OneWayBind(ViewModel, vm => vm.Pressure, v => v.Pressure1Indicator.Text) .DisposeWith(disposable);          \/\/ \u0414\u0432\u0443\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u0430\u044f \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u043e\u0437\u0438\u0446\u0438\u0438 \u043a\u043b\u0430\u043f\u0430\u043d\u0430. \u041a\u043e\u043d\u0432\u0435\u0440\u0442\u043e\u0440\u044b \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u043c\u043e\u0434\u0435\u043b\u0438 \u0438 \u0432 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0438: FloatToStringConverter, StringToFloatConverter this.Bind(ViewModel, vm => vm.Position, v => v.Position.Text, FloatToStringConverter, StringToFloatConverter) .DisposeWith(disposable); this.OneWayBind(ViewModel, vm => vm.Current, v => v.Current.Text) .DisposeWith(disposable); this.OneWayBind(ViewModel, vm => vm.ConnectedDevice.DeviceTime, v => v.DeviceDate.SelectedDate, val => val.Date) .DisposeWith(disposable); this.OneWayBind(ViewModel, vm => vm.ConnectedDevice.DeviceTime, v => v.DeviceTime.SelectedTime, val => val.DateTime) .DisposeWith(disposable);          \/* \u041f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u043a\u043e\u043c\u0430\u043d\u0434 \u043a \u043a\u043d\u043e\u043f\u043a\u0430\u043c *\/ this.BindCommand(ViewModel, vm => vm.ConnectToDevice, v => v.ConnectDevice, nameof(ConnectDevice.Click)) .DisposeWith(disposable); this.BindCommand(ViewModel, vm => vm.SetValvePosition, v => v.SetValvePosition, vm => vm.ConnectedDevice.AssignedPosition, nameof(SetValvePosition.Click))<\/code><\/pre>\n<\/div>\n<\/details>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-328439","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/328439","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=328439"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/328439\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=328439"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=328439"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=328439"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}